プレビューモード

: この機能は Draft Mode に置き換えられました。

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

静的生成は、ヘッドレスCMSからデータを取得するページに便利です。しかし、ヘッドレスCMSで下書きを書いているときに、その下書きをすぐにプレビューしたい場合には理想的ではありません。このような場合、Next.js にビルド時ではなくリクエスト時にページをレンダリングさせ、公開済みのコンテンツではなく下書きのコンテンツを取得させたいでしょう。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://<your-site>/api/preview?secret=<token>&slug=<path>
  • <your-site> はデプロイドメインに置き換えます。
  • <token> は生成した秘密トークンに置き換えます。
  • <path> はプレビューしたいページのパスです。/posts/foo をプレビューしたい場合は &slug=/posts/foo を使用します。

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

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

  • 秘密が一致し、slug パラメータが存在することを確認します(存在しない場合、リクエストは失敗します)。
  • res.setPreviewData を呼び出します。
  • 次に、ブラウザを slug で指定されたパスにリダイレクトします(以下の例では 307リダイレクト を使用しています)。
export default async (req, res) => {
  // 秘密とnextパラメータを確認
  // この秘密はこの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から(secretslug を使用して)プレビューAPIルートにアクセスするか、手動でアクセスすると、プレビューコンテンツを確認できるはずです。また、下書きを公開せずに更新すると、その下書きをプレビューできるはずです。

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

ターミナル
https://<your-site>/api/preview?secret=<token>&slug=<path>

詳細

豆知識: レンダリング中に next/routerisPreview フラグを公開します。詳細は routerオブジェクトのドキュメント を参照してください。

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

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経由でローカルでプレビューモードをテストするには、ブラウザでサードパーティのクッキーとローカルストレージへのアクセスを許可する必要があります。