2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

1人フロントエンドAdvent Calendar 2024

Day 19

microCMSを使って、Next.jsのDraft Modeを学ぶ

Posted at

はじめに

Next.jsのDraft Modeと呼ばれる手法を使えば、ヘッドレスCMSの下書きコンテンツを公開されているアプリケーションで確認することができます。

この記事ではmicroCMSを使って、Draft Modeを使った機能を実装します。

コードの全貌はGitHubで確認できます。

microCMSの使用はこれが初めてなので、一部不要な作業を行なっている恐れがあります

Next.jsの環境作成

今回利用するNext.jsの環境についてこだわる箇所がないので、自動インストールで環境を準備します。

npx create-next-app@latest

スクリーンショット 2024-12-19 16.18.59.png

その後、favicon.icoの削除などNext.jsの要素を削除します。

app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
app/layout.tsx
import type { Metadata } from "next";
import { Noto_Sans_JP } from "next/font/google";
import "./globals.css";

const notoSansJP = Noto_Sans_JP({
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Draft Mode Example",
  description: "Example of using draft mode in Next.js",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ja">
      <body className={notoSansJP.className}>
        {children}
      </body>
    </html>
  );
}
app/page.tsx
export default function Home() {
  return (
    <main className="flex flex-col items-center justify-center w-screen h-screen">
      <section className="rounded-lg shadow-lg p-4 flex flex-col gap-4 bg-slate-300">
        <h1 className="text-lg font-bold">お知らせ</h1>
        <ul>
          <li>
            <div className="flex justify-between gap-2 items-end">
              <p>ファンタスティックな機能を追加しました</p>
              <p className="text-sm text-gray-800">2021-01-03</p>
            </div>
          </li>
          <li>
            <div className="flex justify-between gap-2 items-end">
              <p>お知らせのページを追加しました</p>
              <p className="text-sm text-gray-800">2021-01-02</p>
            </div>
          </li>
          <li>
            <div className="flex justify-between gap-2 items-end">
              <p>サイトを公開しました</p>
              <p className="text-sm text-gray-800">2021-01-01</p>
            </div>
          </li>
        </ul>
      </section>
    </main>
  );
}

app/page.tsxはmicroCMSから取得したお知らせを表示したいので、簡単に見た目を作っています。

microCMSの環境作成

まずは、アカウントの作成とサービスの作成を行います。
スクリーンショット 2024-12-19 16.20.49.png

次に、APIテンプレートから「お知らせ」を作成します。
スクリーンショット 2024-12-19 18.31.15.png

自動でお知らせが一つ作成されているはずです。それをNext.jsで確認するために、次はお知らせの一覧を取得する処理を記述します。
今回は簡単にデータを表示させるだけなのでfetchを用いてデータを取得します。fetchの他にもsdkからデータを取得することも可能なようです。

.env等にMICROCMS_API_KEYを登録すると以下のgetDataでお知らせの一覧を取得できます。

type News = {
  contents: {
    id: string;
    title: string;
    publishedAt?: string;
  }[];
  totalCount: number;
  offset: number;
  limit: number;
}

async function getData(): Promise<News> {
  const url = 'https://draft-mode.microcms.io/api/v1/news';

  const res = await fetch(url, {
    headers: {
      'X-MICROCMS-API-KEY': process.env.MICROCMS_API_KEY ?? '',
    },
  });

  return res.json();
}

const dtf = new Intl.DateTimeFormat('ja-JP', {
  year: 'numeric',
  month: 'numeric',
  day: 'numeric',
});

export default async function Home() {
  const { contents } = await getData();
  return (
    <main className="flex flex-col items-center justify-center w-screen h-screen">
      <section className="rounded-lg shadow-lg p-4 flex flex-col gap-4 bg-slate-300">
        <h1 className="text-lg font-bold">お知らせ</h1>
        <ul>
          {contents.map((content) => (
            <li key={content.id}>
              <div className="flex justify-between gap-2 items-end">
                <p>{content.title}</p>
                <p className="text-sm text-gray-800">
                  {content.publishedAt
                    ? dtf.format(new Date(content.publishedAt))
                    : 'draft'}
                </p>
              </div>
            </li>
          ))}
        </ul>
      </section>
    </main>
  );
}

次に下書き状態のお知らせを作ります。

スクリーンショット 2024-12-19 18.45.54.png

下書きのお知らせはそれぞれがdraftKeyを持っており、それをfetchのリクエスト時にパラメータとして含めると下書き状態のお知らせも併せて取得できるようです。draftKeyはお知らせの詳細ページの左上に書かれています。
スクリーンショット 2024-12-19 18.47.47.png

async function getData(): Promise<News> {
  const url = 'https://draft-mode.microcms.io/api/v1/news';

  const res = await fetch(`${url}?draftKey=D9No73sxg5`, {
    headers: {
      'X-MICROCMS-API-KEY': process.env.MICROCMS_API_KEY ?? '',
    },
  });

  return res.json();
}

先ほどのgetDataをこれに変更すると、下書き状態のお知らせも一覧に出てきたはずです。

Draft Mode

準備が完了しました。Draft Modeを利用して、特定のリクエストを送った時だけ指定した下書きのお知らせを含んだUIを表示させます。

まずは、Router Handlerで特定のリクエストを行う宛先を作成します。

app/api/draft-news/route.ts
import { draftMode } from 'next/headers'
 
export async function GET() {
  const draft = await draftMode()
  draft.enable()
  return new Response('Draft mode is enabled')
}

Draft Modeを有効にしてテキストレスポンスを返すだけのAPIです。

このAPIにアクセスしてみると、レスポンスヘッダーのSet-Cookie__prerender_bypassが含まれています。
スクリーンショット 2024-12-19 19.06.02.png
これがあることで後続の処理でNext.jsはDraft Modeが有効となっていることを判別します。
通常のレスポンスには含まれないことはdraft.enbale()を消すとわかります。

次に、このAPIにdraftKeysecretを受け取って、正常であれば/にリダイレクトするような処理を追加します。

import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const secret = searchParams.get('secret')
  const draftKey = searchParams.get('draftKey')

  if (secret !== process.env.MICROCMS_API_KEY || !draftKey) {
    return new Response('Invalid token', { status: 401 })
  }

  const draft = await draftMode()
  draft.enable()

  redirect('/?draftKey=' + draftKey)
}

secretを受け取って検証するようにしたのは、draftKeyを適当に入力して下書き状態のお知らせを閲覧されることを防ぐためです。

/draftKeyが渡ってくるようになりました。Draft Modeが有効なときはdraftKeyの下書きも表示するようにしましょう。

app/page.tsx
import { draftMode } from "next/headers";

type News = {
  contents: {
    id: string;
    title: string;
    publishedAt: string;
  }[];
  totalCount: number;
  offset: number;
  limit: number;
}

async function getData({ draftKey }: { draftKey?: string }): Promise<News> {
  const { isEnabled } = await draftMode()

  const url = isEnabled
    ?'https://draft-mode.microcms.io/api/v1/news?draftKey=' + draftKey
    : 'https://draft-mode.microcms.io/api/v1/news';

  const res = await fetch(url, {
    headers: {
      'X-MICROCMS-API-KEY': process.env.MICROCMS_API_KEY ?? '',
    },
  });

  return res.json();
}

const dtf = new Intl.DateTimeFormat('ja-JP', {
  year: 'numeric',
  month: 'numeric',
  day: 'numeric',
});

export default async function Home({
  searchParams,
}: {
  searchParams: Promise<{
    draftKey: string | undefined;
  }>;
}) {
  const draftKey = (await searchParams).draftKey;
  const { contents } = await getData({ draftKey });
  return (
    <main className="flex flex-col items-center justify-center w-screen h-screen">
      <section className="rounded-lg shadow-lg p-4 flex flex-col gap-4 bg-slate-300">
        <h1 className="text-lg font-bold">お知らせ</h1>
        <ul>
          {contents.map((content) => (
            <li key={content.id}>
              <div className="flex justify-between gap-2 items-end">
                <p>{content.title}</p>
                <p className="text-sm text-gray-800">
                  {content.publishedAt
                    ? dtf.format(new Date(content.publishedAt))
                    : 'draft'}
                </p>
              </div>
            </li>
          ))}
        </ul>
      </section>
    </main>
  );
}

Draft Modeが有効であることはisEnabledで判断します。isEnabledtrueな時にだけ受け取ったdraftKeyを含めたリクエストをmicroCMSに送信します。
app/api/draft-news/route.tsdraft.enable()を削除したときは動作しないことを安全のため確認してください。

これによって通常は投稿済みのお知らせしか表示されないが、お知らせを管理する人が限定的にmicroCMSのAPIキーとdraftKeyを用いて下書きのお知らせを含めた表示を確認する仕組みができました。

おわりに

私はCMSを使った経験がなかったので、ドキュメントを読むだけではイマイチ理解を進められませんでした。
実際に動かしてみることで、Draft Modeがどのようなものか、どのようなところが嬉しいのかが理解できてよかったです。
開発者とCMSの管理者が別である場合、お知らせを投稿する前の見た目の調整は大変なはずなので、この機能があるとかなり業務の効率化が図られるのではないでしょうか。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?