Next.jsでプレビューモードを使ってコンテンツをプレビューする方法

: この機能はドラフトモードに置き換えられました。

使用例

Pagesドキュメントデータフェッチングドキュメントでは、getStaticPropsgetStaticPathsを使ってビルド時にページを事前レンダリングする(静的生成)方法について説明しました。

静的生成はヘッドレスCMSからデータを取得するページに便利です。しかし、ヘッドレスCMSで下書きを書いていて、その下書きをすぐにページ上でプレビューしたい場合には理想的ではありません。このような場合、Next.jsにビルド時ではなくリクエスト時にページをレンダリングさせ、公開済みのコンテンツではなく下書きのコンテンツを取得させたいでしょう。Next.jsにはこの特定のケースに対して静的生成をバイパスするプレビューモードという機能があります。以下にその使用方法を説明します。

ステップ1: プレビューAPIルートの作成とアクセス

Next.jsのAPIルートに慣れていない場合は、まずAPIルートドキュメントを確認してください。

まず、プレビューAPIルートを作成します。任意の名前を付けることができます(例: pages/api/preview.js、TypeScriptを使用している場合は.ts)。

このAPIルートでは、レスポンスオブジェクトに対してsetPreviewDataを呼び出す必要があります。setPreviewDataの引数はオブジェクトで、これは後でgetStaticPropsで使用できます(詳細は後述)。今のところ、{}を使用します。

export default function handler(req, res) {
  // ...
  res.setPreviewData({})
  // ...
}

res.setPreviewDataはブラウザにいくつかのクッキーを設定し、プレビューモードを有効にします。これらのクッキーを含むNext.jsへのリクエストはすべてプレビューモードと見なされ、静的に生成されたページの動作が変更されます(詳細は後述)。

以下のようなAPIルートを作成し、ブラウザから手動でアクセスすることで、これをテストできます:

pages/api/preview.js
// ブラウザから手動でテストするための簡単な例
export default function handler(req, res) {
  res.setPreviewData({})
  res.end('プレビューモードが有効になりました')
}

ブラウザの開発者ツールを開いて/api/previewにアクセスすると、このリクエストに対して__prerender_bypass__next_preview_dataクッキーが設定されていることがわかります。

ヘッドレスCMSから安全にアクセスする

実際には、このAPIルートにヘッドレスCMSから安全にアクセスしたいでしょう。具体的な手順は使用するヘッドレスCMSによって異なりますが、以下に一般的な手順を示します。

これらの手順は、使用するヘッドレスCMSがカスタムプレビューURLの設定をサポートしていることを前提としています。サポートしていない場合でも、この方法を使用してプレビューURLを保護できますが、プレビューURLを手動で構築してアクセスする必要があります。

まず、任意のトークンジェネレーターを使用して秘密トークン文字列を作成します。この秘密はNext.jsアプリとヘッドレスCMSのみが知っているものにします。これにより、CMSにアクセスできない人がプレビューURLにアクセスするのを防ぎます。

次に、ヘッドレスCMSがカスタムプレビューURLの設定をサポートしている場合、以下のURLをプレビューURLとして指定します。プレビューAPIルートがpages/api/preview.jsにあると仮定します。

ターミナル
https://<あなたのサイト>/api/preview?secret=<トークン>&slug=<パス>
  • <あなたのサイト>はデプロイドメインに置き換えてください。
  • <トークン>は生成した秘密トークンに置き換えてください。
  • <パス>はプレビューしたいページのパスです。/posts/fooをプレビューしたい場合は、&slug=/posts/fooを使用します。

ヘッドレスCMSによっては、プレビューURLに変数を含めることができ、<パス>をCMSのデータに基づいて動的に設定できます(例: &slug=/posts/{entry.fields.slug})。

最後に、プレビューAPIルートで:

  • 秘密トークンが一致し、slugパラメータが存在することを確認します(存在しない場合、リクエストは失敗します)。
  • res.setPreviewDataを呼び出します。
  • その後、ブラウザをslugで指定されたパスにリダイレクトします(以下の例では307リダイレクトを使用しています)。
export default async (req, res) => {
  // 秘密トークンと次のパラメータを確認
  // この秘密はこのAPIルートとCMSのみが知っている必要があります
  if (req.query.secret !== 'MY_SECRET_TOKEN' || !req.query.slug) {
    return res.status(401).json({ message: '無効なトークンです' })
  }

  // 提供された`slug`が存在するかどうかを確認するためにヘッドレスCMSをフェッチ
  // getPostBySlugはヘッドレスCMSへの必要なフェッチロジックを実装します
  const post = await getPostBySlug(req.query.slug)

  // slugが存在しない場合、プレビューモードを有効にしない
  if (!post) {
    return res.status(401).json({ message: '無効なslugです' })
  }

  // クッキーを設定してプレビューモードを有効化
  res.setPreviewData({})

  // フェッチした投稿のパスにリダイレクト
  // req.query.slugにリダイレクトしないでください。オープンリダイレクトの脆弱性につながる可能性があります
  res.redirect(post.slug)
}

成功すると、ブラウザはプレビューしたいパスにリダイレクトされ、プレビューモードのクッキーが設定されます。

ステップ2: getStaticPropsの更新

次のステップは、プレビューモードをサポートするようにgetStaticPropsを更新することです。

プレビューモードのクッキーが設定された状態(res.setPreviewData経由)でgetStaticPropsを持つページをリクエストすると、getStaticPropsビルド時ではなくリクエスト時に呼び出されます。

さらに、以下のプロパティを持つcontextオブジェクトとともに呼び出されます:

  • context.previewtrueになります。
  • context.previewDatasetPreviewDataに使用した引数と同じになります。
export async function getStaticProps(context) {
  // プレビューモードのクッキーが設定された状態でこのページをリクエストすると:
  //
  // - context.previewはtrueになります
  // - context.previewDataは`setPreviewData`に使用した引数と同じになります
}

プレビューAPIルートでres.setPreviewData({})を使用したので、context.previewData{}になります。必要に応じて、プレビューAPIルートからgetStaticPropsにセッション情報を渡すためにこれを使用できます。

getStaticPathsも使用している場合、context.paramsも利用可能です。

プレビューデータの取得

context.previewcontext.previewDataに基づいて異なるデータを取得するようにgetStaticPropsを更新できます。

例えば、ヘッドレスCMSには下書き投稿用の異なるAPIエンドポイントがあるかもしれません。その場合、以下のようにcontext.previewを使用してAPIエンドポイントURLを変更できます:

export async function getStaticProps(context) {
  // context.previewがtrueの場合、APIエンドポイントに"/preview"を追加して
  // 公開データではなく下書きデータをリクエストします。これは使用する
  // ヘッドレスCMSによって異なります。
  const res = await fetch(`https://.../${context.preview ? 'preview' : ''}`)
  // ...
}

これで完了です!ヘッドレスCMSからまたは手動でプレビューAPIルート(secretslug付き)にアクセスすると、プレビューコンテンツを確認できるはずです。また、公開せずに下書きを更新した場合、その下書きをプレビューできるはずです。

ヘッドレスCMSにこれをプレビューURLとして設定するか、手動でアクセスすると、プレビューを確認できます。

ターミナル
https://<あなたのサイト>/api/preview?secret=<トークン>&slug=<パス>

詳細情報

知っておくと便利: レンダリング中、next/routerisPreviewフラグを公開します。詳細はルーターオブジェクトのドキュメントを参照してください。

プレビューモードの期間を指定する

setPreviewDataはオプションの第二引数を取ることができ、これはオプションオブジェクトである必要があります。以下のキーを受け入れます:

  • maxAge: プレビューセッションの持続時間を秒数で指定します。
  • path: クッキーが適用されるパスを指定します。デフォルトは/で、すべてのパスでプレビューモードが有効になります。
setPreviewData(data, {
  maxAge: 60 * 60, // プレビューモードのクッキーは1時間で期限切れになります
  path: '/about', // プレビューモードのクッキーは/aboutのパスに適用されます
})

プレビューモードのクッキーをクリアする

デフォルトでは、プレビューモードのクッキーに有効期限が設定されていないため、プレビューセッションはブラウザを閉じると終了します。

プレビューモードのクッキーを手動でクリアするには、clearPreviewData()を呼び出すAPIルートを作成します:

pages/api/clear-preview-mode-cookies.js
export default function handler(req, res) {
  res.clearPreviewData({})
}

その後、/api/clear-preview-mode-cookiesにリクエストを送信してAPIルートを呼び出します。このルートをnext/linkを使用して呼び出す場合、リンクのプリフェッチ中にclearPreviewDataが呼び出されないようにprefetch={false}を渡す必要があります。

setPreviewData呼び出しでパスが指定された場合、clearPreviewDataに同じパスを渡す必要があります:

pages/api/clear-preview-mode-cookies.js
export default function handler(req, res) {
  const { path } = req.query

  res.clearPreviewData({ path })
}

previewDataのサイズ制限

setPreviewDataにオブジェクトを渡し、getStaticPropsで利用できます。ただし、データはクッキーに保存されるため、サイズに制限があります。現在、プレビューデータは2KBに制限されています。

getServerSidePropsとの連携

プレビューモードはgetServerSidePropsでも動作します。contextオブジェクトにpreviewpreviewDataが含まれます。

知っておくと便利: プレビューモードを使用する場合、Cache-Controlヘッダーを設定しないでください。バイパスできないためです。代わりにISRを使用することをお勧めします。

APIルートとの連携

APIルートは、リクエストオブジェクトの下でpreviewpreviewDataにアクセスできます。例えば:

export default function myApiRoute(req, res) {
  const isPreview = req.preview
  const previewData = req.previewData
  // ...
}

next buildごとに一意

バイパスクッキーの値とpreviewDataを暗号化するための秘密鍵は、next buildが完了するたびに変更されます。 これにより、バイパスクッキーが推測されるのを防ぎます。

知っておくと便利: HTTP経由でローカルでプレビューモードをテストするには、ブラウザでサードパーティのクッキーとローカルストレージへのアクセスを許可する必要があります。