Menu

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

注意: この機能はドラフトモードに置き換えられています。

ページドキュメントデータフェッチングドキュメントでは、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として指定します。これはプレビュー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: '無効なトークンです' })
  }
 
  // 提供された`slug`が存在するかどうかを確認するためにヘッドレスCMSをフェッチ
  // getPostBySlugはヘッドレスCMSへの必要なフェッチロジックを実装する関数
  const post = await getPostBySlug(req.query.slug)
 
  // slugが存在しない場合、プレビューモードが有効にならないようにする
  if (!post) {
    return res.status(401).json({ message: '無効なスラグです' })
  }
 
  // クッキーを設定してプレビューモードを有効にする
  res.setPreviewData({})
 
  // フェッチしたポストのパスにリダイレクト
  // オープンリダイレクト脆弱性につながる可能性があるため、req.query.slugにリダイレクトしない
  res.redirect(post.slug)
}

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

ステップ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も利用できます。

プレビューデータをフェッチする

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として設定するか、手動でアクセスすると、プレビューを確認できるはずです。

Terminal
https://<your-site>/api/preview?secret=<token>&slug=<path>

詳細情報

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

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

setPreviewDataはオプションの2番目のパラメータとしてオプションオブジェクトを受け取ります。以下のキーを受け付けます:

  • 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ルートを呼び出すために/api/clear-preview-mode-cookiesにリクエストを送信します。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でも動作します。previewpreviewDataを含むcontextオブジェクトでも利用できます。

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