Next.jsのApp RouterでContentfulのlive preview modeを使用する。
当ブログはNext.jsのApp Routerを使用していますが、こちらにContentfulのlive preview modeを導入したので、メモ書きを。
※当ブログではContentfulの投稿データを取得するためにcreateClientを利用しているため、createClientを用いた方法について記述いたします。
まず、contentfulの管理画面から右上のSettings→Content previewと進みます。
「Create preview platform」を押下し、入力欄を入力します。
「Preview platform name」 は必須のため、previewと入力しました。
Preview URLはサイトのURLと/api/draft?secret=<CONTENTFUL_PREVIEW_SECRET>&slug={entry.fields.slug} をつなげたものを入力します。
https://your-blog.vercel.app/api/draft?secret=<CONTENTFUL_PREVIEW_SECRET>&slug={entry.fields.slug}
CONTENTFUL_PREVIEW_SECRET に設定した値は、Next.jsの環境変数に入れておく必要があります。
その後Contentful Live Preview SDK (https://github.com/contentful/live-preview)をインストールします。
npm install @contentful/live-preview
Next.jsの Draft Mode を利用し、プレビュー用の Route Handlers を作成し、プレビューデータを取得できるようにします。 Preview URLで指定したURLが対応します。api/draft/route.tsにファイルを作成し、下記のように記述します。
import { createClient } from "contentful";
import { cookies, draftMode } from "next/headers";
import { redirect } from "next/navigation";
import { IBlogFields } from "../../../@types/generated/contentful";
export const dynamic = "force-dynamic";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const secret = searchParams.get("secret");
const slug = searchParams.get("slug");
if (!secret || !slug) {
return new Response("Missing parameters", { status: 400 });
}
if (secret !== process.env.CONTENTFUL_PREVIEW_SECRET) {
return new Response("Invalid token", { status: 401 });
}
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID as string,
accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN as string,
host: "preview.contentful.com",
});
const res = await client.getEntries<IBlogFields>({
content_type: "blog",
"fields.slug": slug,
});
const blog = res.items[0];
if (!blog) {
return new Response("Blog not found", { status: 404 });
}
draftMode().enable();
// This is a hack due to a bug with cookies and NextJS, this code might not be required in the future
const cookieStore = cookies();
const cookie = cookieStore.get("__prerender_bypass");
cookies().set({
name: "__prerender_bypass",
value: cookie?.value || "",
httpOnly: true,
path: "/",
secure: true,
sameSite: "none",
});
redirect(`/post/${slug}`);
}
Next.js側のバグもあり、一部ハックのような書き方も含まれますが、この部分でCONTENTFUL_PREVIEW_TOKEN を利用し、下書きの投稿データが存在するかどうか確かめています。
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID as string,
accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN as string,
host: "preview.contentful.com",
});
Draft Modeが有効だという情報をcookieにつめて、その後投稿ページにリダイレクトしています。
draftMode().enable();
redirect(`/post/${slug}`);
ContentfulLivePreviewProvider を使用するためのProviderコンポーネントを作成します。
"use client";
import { ContentfulLivePreviewProvider } from "@contentful/live-preview/react";
export const LivePreviewProvider = ({
isEnabled,
children,
}: {
isEnabled: boolean;
children: React.ReactNode;
}) => {
return (
<ContentfulLivePreviewProvider
locale="ja-JP"
enableInspectorMode={isEnabled}
enableLiveUpdates={isEnabled}
debugMode={isEnabled}
>
{children}
</ContentfulLivePreviewProvider>
);
};
大元のlayout.tsxでLivePreviewProvider を下記のように使用します。
import "styles/globals.css";
import { LivePreviewProvider } from "components/LIvePreviewProvider";
import { draftMode } from "next/headers";
import { ReactNode } from "react";
export default function RootLayout({ children }: { children: ReactNode }) {
const { isEnabled } = draftMode();
return (
<html lang="ja">
<body>
<LivePreviewProvider isEnabled={isEnabled}>
{children}
</LivePreviewProvider>
</body>
</html>
);
}
cookieを参照してDraft Modeが有効か否かを判定しています。
const { isEnabled } = draftMode();
記事ページで下記のように実装。
Draft Modeが有効か否かで取得する投稿データが異なるため、createClient のパラメーターのaccessTokenとhostをisEnabled を参照して分岐する必要があります。
const Page = async ({ params }: { params: { slug: string } }) => {
const { isEnabled } = draftMode();
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID as string,
accessToken: isEnabled
? (process.env.CONTENTFUL_PREVIEW_TOKEN as string)
: (process.env.CONTENTFUL_ACCESS_TOKEN as string),
host: isEnabled ? "preview.contentful.com" : "cdn.contentful.com",
});
const res = await client.getEntries<IBlogFields>({
content_type: "blog",
"fields.slug": params.slug,
});
const blog = res.items[0];
if (!blog) return notFound();
return <Contents blog={blog} />;
};
export default Page;
子コンポーネントのContentsでは、下記の書き方で下書き中のものを即時反映した投稿データを取得することができます。
livePostに入っている投稿データを記事ページで出力するように実装すると本番環境はそのままで、live preview modeで下書き記事を即時に確認できる実装となります。
"use client";
import { useContentfulLiveUpdates } from "@contentful/live-preview/react";
export const Contents = ({ blog }: { blog: Entry<IBlogFields> }) => {
const livePost = useContentfulLiveUpdates(blog);
..........
管理画面の記事作成画面から、右側の「Open Live Preview」を押下すると、live preview modeで記事を作成できます。下記URLからlive preview modeの使用感を確認できます。
https://kittsun.net/live-preview-sample.mp4
参考記事
https://www.newt.so/docs/tutorials/nextjs-preview-mode
参考動画