プレビューモード

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

ページドキュメントデータフェッチングドキュメントでは、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として以下を指定します。これはプレビューAPIルートがpages/api/preview.jsにあることを前提としています。

Terminal
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: '無効なトークンです' })
  }

  // ヘッドレスCMSに問い合わせて、指定された`slug`が存在するか確認
  // 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として設定するか、手動でアクセスすると、プレビューを確認できます。

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