如何在 Next.js 中使用預覽模式 (Preview Mode) 預覽內容

注意:此功能已被 草稿模式 (Draft Mode) 取代。

範例

頁面文件資料獲取文件 中,我們討論了如何使用 getStaticPropsgetStaticPaths 在建置時預渲染頁面(靜態生成 (Static Generation))。

當您的頁面從無頭 CMS (headless CMS) 獲取資料時,靜態生成非常有用。但是,當您在無頭 CMS 上撰寫草稿並希望立即在頁面上預覽草稿時,這並不理想。您會希望 Next.js 在請求時而非建置時渲染這些頁面,並獲取草稿內容而非已發布的內容。您會希望 Next.js 僅針對此特定情況繞過靜態生成。

Next.js 有一個稱為預覽模式 (Preview Mode) 的功能可以解決這個問題。以下是使用它的說明。

步驟 1:建立並存取預覽 API 路由

如果您不熟悉 Next.js API 路由,請先查看 API 路由文件

首先,建立一個預覽 API 路由。它可以有任何名稱,例如 pages/api/preview.js(如果使用 TypeScript 則為 .ts)。

在此 API 路由中,您需要在回應物件上呼叫 setPreviewDatasetPreviewData 的參數應該是一個物件,這可以由 getStaticProps 使用(稍後會詳細說明)。現在,我們將使用 {}

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

res.setPreviewData 會在瀏覽器上設定一些 cookies,從而開啟預覽模式。任何包含這些 cookies 的 Next.js 請求都會被視為預覽模式,靜態生成頁面的行為將會改變(稍後會詳細說明)。

您可以透過建立如下所示的 API 路由並從瀏覽器手動存取來進行測試:

pages/api/preview.js
// 用於從瀏覽器手動測試的簡單範例。
export default function handler(req, res) {
  res.setPreviewData({})
  res.end('預覽模式已啟用')
}

如果您開啟瀏覽器的開發者工具並訪問 /api/preview,您會注意到在此請求上會設定 __prerender_bypass__next_preview_data cookies。

從無頭 CMS 安全存取

實際上,您會希望從無頭 CMS 安全地呼叫此 API 路由。具體步驟會根據您使用的無頭 CMS 而有所不同,但以下是一些您可以採取的常見步驟。

這些步驟假設您使用的無頭 CMS 支援設定自訂預覽 URL。如果不支援,您仍然可以使用此方法來保護您的預覽 URL,但需要手動建構並存取預覽 URL。

首先,您應該使用您選擇的令牌生成器建立一個秘密令牌字串。此秘密僅由您的 Next.js 應用程式和無頭 CMS 知道。此秘密可以防止沒有存取您 CMS 權限的人存取預覽 URL。

其次,如果您的無頭 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 中包含變數,以便根據 CMS 的資料動態設定 <path>,例如:&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' })
  }

  // 透過設定 cookies 啟用預覽模式
  res.setPreviewData({})

  // 重新導向到從獲取的文章路徑
  // 我們不重新導向到 req.query.slug,因為這可能導致開放重新導向漏洞
  res.redirect(post.slug)
}

如果成功,瀏覽器將被重新導向到您要預覽的路徑,並設定預覽模式 cookies。

步驟 2:更新 getStaticProps

下一步是更新 getStaticProps 以支援預覽模式。

如果您請求一個具有 getStaticProps 的頁面,並且設定了預覽模式 cookies(透過 res.setPreviewData),則 getStaticProps 將在請求時(而非建置時)被呼叫。

此外,它將被呼叫並帶有一個 context 物件,其中:

  • context.preview 將為 true
  • context.previewData 將與用於 setPreviewData 的參數相同。
export async function getStaticProps(context) {
  // 如果您使用預覽模式 cookies 請求此頁面:
  //
  // - context.preview 將為 true
  // - context.previewData 將與
  //   用於 `setPreviewData` 的參數相同。
}

我們在預覽 API 路由中使用了 res.setPreviewData({}),因此 context.previewData 將為 {}。如果需要,您可以使用它來從預覽 API 路由傳遞會話資訊到 getStaticProps

如果您還使用 getStaticPaths,則 context.params 也將可用。

獲取預覽資料

您可以根據 context.preview 和/或 context.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://<your-site>/api/preview?secret=<token>&slug=<path>

更多詳細資訊

須知:在渲染期間,next/router 會公開一個 isPreview 標誌,請參閱 router 物件文件 以獲取更多資訊。

指定預覽模式持續時間

setPreviewData 接受一個可選的第二個參數,該參數應為一個選項物件。它接受以下鍵:

  • maxAge:指定預覽會話持續的秒數。
  • path:指定應應用 cookie 的路徑。預設為 /,為所有路徑啟用預覽模式。
setPreviewData(data, {
  maxAge: 60 * 60, // 預覽模式 cookies 在 1 小時後過期
  path: '/about', // 預覽模式 cookies 應用於具有 /about 的路徑
})

清除預覽模式 cookies

預設情況下,預覽模式 cookies 沒有設定過期日期,因此預覽會話會在瀏覽器關閉時結束。

要手動清除預覽模式 cookies,請建立一個呼叫 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 呼叫此路由,則必須傳遞 prefetch={false} 以防止在連結預取期間呼叫 clearPreviewData

如果在 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 中可用。但是,由於資料將儲存在 cookie 中,因此有大小限制。目前,預覽資料限制為 2KB。

getServerSideProps 一起使用

預覽模式也適用於 getServerSideProps。它將在包含 previewpreviewDatacontext 物件上可用。

須知:使用預覽模式時不應設定 Cache-Control 標頭,因為它無法被繞過。相反,我們建議使用 增量靜態再生 (ISR)

與 API 路由一起使用

API 路由將在請求物件下存取 previewpreviewData。例如:

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

每個 next build 唯一

next build 完成時,繞過 cookie 值和用於加密 previewData 的私鑰都會改變。這確保了繞過 cookie 無法被猜測。

須知:要在 HTTP 上本地測試預覽模式,您的瀏覽器需要允許第三方 cookies 和本地儲存存取。