Table of Contents
はじめに
このブログはNext.jsで作っており、Vercel上で動いています。
そしてコンテンツの管理はHeadless CMSのContentfulで行っております。
記事自体はNext.jsのStatic Site Generation(SSG)を使って静的ページで表現していますが、それとは別でPreview Modeという機能を使って下書きしている記事をすぐに表示確認できるようにしてみます。
Next.jsでの処理
プレビュー用APIを作成
まずはNext.jsでプレビュー用のAPIを作成します。
これはContentfulからこのAPIを呼び出してプレビューを表示させるためのものです。
具体的にはContentfulのEditorにあるOpen previewボタンを押したときに呼び出されるAPIです。
ここではpages/api/preview.jsを作成します。
// Contentful SDKを利用
const client = require("contentful").createClient({
space: process.env.NEXT_CONTENTFUL_SPACE_ID,
accessToken: process.env.NEXT_CONTENTFUL_PREVIEW_ACCESS_TOKEN,
host: "preview.contentful.com"
});
export default async (req, res) => {
// シークレットトークンのチェック
if (req.query.secret !== process.env.NEXT_PREVIEW_SCRET_TOKEN || !req.query.slug) {
return res.status(401).json({ message: 'Invalid token' });
}
// リクエストされたslugの記事があるかチェック
const entry = await client.getEntries({
'content_type': 'post',
'fields.slug': req.query.slug
});
const post = entry.items[0];
if (!post) {
return res.status(401).json({ message: 'Invalid slug' });
}
// Preview Modeの有効化
res.setPreviewData({});
// リダイレクト
res.writeHead(307, { Location: `/post/${post.fields.slug}` });
res.end();
}
このAPIでは2つのパラメータを受け取っています。
- secret: Next.js側とContentful側で共有しているシークレットトークン
- slug: プレビューしたい記事のスラグ
するとContentful側から呼び出すAPIは
https://<自分のサイト>/api/preview?secret=<シークレットトークン>&slug=<プレビューしたい記事のスラグ>
といった形になります。
関数の内容としては
- ちゃんとContentfulからのアクセスか確認するためのシークレットトークンチェック
- リクエストされたスラグの記事がContentfulに存在するかのチェック
- Preview Modeの有効化
- 記事へのリダイレクト
となっています。
私の場合はPostというContent modelにslugというfieldを追加して運用しています。
記事ページをプレビューに対応させる
setPreviewData
で呼び出された記事ページはgetStaticProps
内でpreviewフラグを受け取ることができ、そのフラグで判断してContentfulのDRAFT状態を含んだプレビューデータを取得します。
私の場合はpages/post/[slug].jsに記述します。
//...
import { useRouter } from 'next/router';
import ErrorPage from 'next/error';
export default function Post({ post }) {
const router = useRouter()
// 記事データが取得できなかったら404を表示
if (!router.isFallback && !post) {
return <ErrorPage statusCode={404} />
}
// getStaticPropsの実行が終わるまでの表示
if(router.isFallback) {
return (
<div>読み込み中...</div>
)
}
return (
// コンテンツの表示
);
}
// PUBLISHEDのみのContent用クライアント
const client = require("contentful").createClient({
space: process.env.NEXT_CONTENTFUL_SPACE_ID,
accessToken: process.env.NEXT_CONTENTFUL_ACCESS_TOKEN
});
// DRAFTも含めたContent用クライアント
const previewClient = require("contentful").createClient({
space: process.env.NEXT_CONTENTFUL_SPACE_ID,
accessToken: process.env.NEXT_CONTENTFUL_PREVIEW_ACCESS_TOKEN,
host: "preview.contentful.com"
});
export async function getStaticProps({ params, preview = false }) {
const c = preview ? previewClient : client;
const entry = await c.getEntries({
'content_type': 'post',
'fields.slug': params.slug
});
return {
props: {
preview,
post: entry.items[0]?.fields ?? null
},
};
}
export async function getStaticPaths() {
const entries = await client.getEntries();
const posts = entries.items;
const paths = posts.map((post) => ({ params: { slug: post.fields.slug } }));
return {
paths,
fallback: true, // Preview modeではtrueにする
};
}
ここで注意するべき点としてgetStaticPaths
のfallbackをfalseにしてしまうとgetStaticProps
が呼ばれないためtrueにします。
そのためコンポーネント側で記事データが取得できなかった場合にエラーページを表示させるなどのハンドリングが必要になってきます。
以上でNext.js側の処理になります。Vercelにデプロイするとプレビュー表示APIが叩けるようになっているはずです。
.env.localの設定値
ローカル環境で試す場合は.env.localにそれぞれ設定値を記述します。
Contentfulの設定値はSettings>API keysから取得できます。
シークレットトークンはツールなどを利用して自分で文字列を生成します。
NEXT_CONTENTFUL_SPACE_ID=<ContentfulのSpace ID>
NEXT_CONTENTFUL_ACCESS_TOKEN=<ContentfulのContent Delivery APIアクセストークン>
NEXT_CONTENTFUL_PREVIEW_ACCESS_TOKEN=<ContentfulのContent Preview APIアクセストークン>
NEXT_PREVIEW_SCRET_TOKEN=<自分で用意したプレビュー用シークレットトークン>
Vercel側の設定
Vercelでは特にやることはなく.env.localで設定した環境変数を登録するだけです。
プロジェクト画面のSettings>Environment Variablesから登録できます。
Contentful側の設定
こちらもNext.jsで用意したPreview表示APIを登録するだけです。
Settings>Content previewから登録できます。
設定するURLは
http://<自分のサイト>/api/preview?secret=<生成したシークレットトークン>&slug={entry.fields.slug}
といった形になります。
これでプレビューを表示する準備ができました。
Contentfulで記事を作成するためのEditorを開きOpen previewボタンを押してみるとDRAFT状態の記事プレビューが確認できるはずです。
まとめ
Next.jsのPreview modeを使うとわざわざビルドを待たずに下書きの記事を公開前に確認できました。
Contentfulの場合はプレビューデータアクセス用のAPIが用意されてるので便利です。
この機能によってSSGでブログシステムを運用するときの欠点をうまくカバーでき、かつ高パフォーマンスを維持できるのでNext.jsの素晴らしい機能の1つだと思います。