🚀

【Next.js和訳】Advanced Features/Preview Mode

9 min read

この記事について

株式会社 UnReact はプロジェクトの一環としてNext.js ドキュメントの和訳を行っています。

この記事は、Advanced Features/Preview Modeの記事を和訳したものです。

記事内で使用する画像は、公式ドキュメント内の画像を引用して使用させていただいております。

Peview Mode

このドキュメントは、Next.jsのバージョン9.3以降を対象としています。古いバージョンのNext.jsをお使いの方は、以前のドキュメントをご参照ください。

PagesドキュメントData Fetchingドキュメントでは、getStaticPropsgetStaticPathsを使用して、構築時にページを事前にレンダリングする方法(Static Generation)について説明しました。

静的生成は、ページがヘッドレスCMSからデータを取得する場合に便利です。しかし、ヘッドレスCMS上でDraftを書いていて、そのDraftをすぐにページ上でプレビューしたい場合には理想的ではありません。Next.jsはこれらのページをビルド時ではなくリクエスト時にレンダリングし、公開されたコンテンツではなくドラフトのコンテンツを取得したいと思います。Next.jsがStatic Generationをバイパスするのは、このような特定のケースに限られます。

Next.jsには、この問題を解決するプレビューモードという機能があります。以下に、その使い方を説明します。

Step 1.preview API route の作成とアクセス

Next.jsのAPIルートに慣れていない方は、まずAPI Routes documentationに目を通してください。

まず、プレビュー用のAPIルートを作成します。名前は自由で、例えばpages/api/preview.js(TypeScriptを使っている場合は.ts)のようにします。

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

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

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

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

// ブラウザから手動でテストするための簡単な例です。
// これがpages/api/preview.jsにある場合は
// ブラウザから/api/previewを開いてください。
export default function handler(req, res) {
  res.setPreviewData({})
  res.end('Preview mode enabled')
}

ブラウザの開発者ツールを使うと、このリクエストで__prerender_bypass__next_preview_dataのcookieが設定されることに気づくでしょう。

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

実際には、ヘッドレスCMSからこのAPIルートを安全に呼び出したいと思います。具体的な手順は、使用しているヘッドレスCMSによって異なりますが、ここでは一般的な手順をご紹介します。

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

First, お好みのトークンジェネレータを使って秘密のトークン文字列を作成してください。この秘密は、Next.jsアプリとヘッドレスCMSだけが知っているものです。この秘密は、あなたのCMSにアクセスしていない人がプレビューURLにアクセスすることを防ぎます。

Second, お使いのヘッドレスCMSがカスタムプレビュー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}

Finally, プレビューAPIルート:

  • シークレットが一致していることと、slugパラメータが存在することを確認します(存在しない場合、リクエストは失敗します)。
  • res.setPreviewDataを呼び出します。
  • その後、ブラウザを slug で指定したパスにリダイレクトします。(以下の例では307 redirectを使用しています)。
export default async (req, res) => {
  // シークレットと次のパラメータを確認する
  // このシークレットは、このAPIルートとCMSにしか知られてはいけません。
  if (req.query.secret !== 'MY_SECRET_TOKEN' || !req.query.slug) {
    return res.status(401).json({ message: 'Invalid token' })
  }
  // 提供された `slug` が存在するかどうかをチェックするために、ヘッドレス CMS をフェッチする。
  // getPostBySlugは、ヘッドレスCMSに必要なフェッチロジックを実装します。
  const post = await getPostBySlug(req.query.slug)
  // slug が存在しない場合、プレビューモードが有効にならないようにする
  if (!post) {
    return res.status(401).json({ message: 'Invalid slug' })
  }
  // クッキーを設定してプレビューモードを有効にする
  res.setPreviewData({})
  // 取得した記事のパスにリダイレクトする
  // req.query.slugへのリダイレクトは、オープンリダイレクトの脆弱性につながる可能性があるので行いません。  
  res.redirect(post.slug)
}

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

Step 2. getStaticProps の更新

次のステップでは、プレビューモードをサポートするために getStaticProps を更新します。

プレビューモードのクッキーが設定された getStaticProps を持つページを (res.setPreviewData 経由で) リクエストすると、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も使用可能になります。

プレビューデータの取得

getStaticProps を更新して、context.previewcontext.previewData に基づいて異なるデータをフェッチすることができます。

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

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

これで完了です。ヘッドレスCMSからプレビューAPIルート(secretslugを含む)にアクセスするか、手動でアクセスすると、プレビューコンテンツを見ることができるようになるはずです。また、公開せずにドラフトを更新すると、ドラフトをプレビューできるようになるはずです

# ヘッドレスCMSのプレビューURLとして設定するか、手動でアクセスしてください。
# そうすれば、プレビューを見ることができます。
https://<your-site>/api/preview?secret=<token>&slug=<path>

詳細

プレビューモードのクッキーを消去

デフォルトでは、プレビューモードのCookieに有効期限が設定されていないため、ブラウザを閉じた時点でプレビューモードが終了してしまいます。

プレビュークッキーを手動で消去するには、clearPreviewDataを呼び出すAPIルートを作成して、このAPIルートにアクセスします。

export default function handler(req, res) {
// プレビューモードのクッキーをクリアします。
  // この関数は引数をとりません。  
  res.clearPreviewData()
  // ,,,  
}

プレビューモードの継続時間の指定

setPreviewDataはオプションで2番目のパラメータを取ります。これはオプションオブジェクトでなければなりません。次のようなキーを受け取ります。

  • maxAge : プレビューセッションの継続時間を秒単位で指定します。
setPreviewData(data, {
  maxAge: 60 * 60,  // プレビューモードのクッキーは、1時間で期限切れになります。
})

previewData のサイズ制限

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

getServerSidePropsで動作

プレビューモードは、getServerSidePropsでも動作します。また、previewpreviewData を含む context オブジェクトでも利用可能になります。

API ルートとの連携

API ルートは、リクエストオブジェクトの下にある previewpreviewData にアクセスできます。例えば、以下のようになります

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

Unique per next build

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

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

もっと詳しく

以下のページも参考になります。

https://nextjs.org/docs/basic-features/data-fetching

https://nextjs.org/docs/api-routes/introduction

https://nextjs.org/docs/api-reference/next.config.js/environment-variables

Discussion

ログインするとコメントできます