跳到主要內容

預覽模式

注意:此功能已被草稿模式取代。

範例

Pages 文件資料抓取文件中,我們討論了如何在建置時預先渲染頁面(靜態產生),使用 getStaticPropsgetStaticPaths

當您的頁面從 Headless CMS 抓取資料時,靜態產生非常有用。但是,當您在 Headless CMS 上撰寫草稿並想要預覽頁面上的草稿時,這並非理想的做法。您會希望 Next.js 在請求時而非建置時渲染這些頁面,並抓取草稿內容而非已發布的內容。您會希望 Next.js 僅針對此特定情況繞過靜態產生。

Next.js 有一個稱為預覽模式的功能,可以解決此問題。以下是如何使用它的說明。

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

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

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

在此 API 路由中,您需要在 response 物件上呼叫 setPreviewDatasetPreviewData 的引數應該是一個物件,getStaticProps 可以使用它(稍後會詳細介紹)。目前,我們將使用 {}

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

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

您可以透過建立如下的 API 路由,並從瀏覽器手動存取來手動測試此功能

pages/api/preview.js
// simple example for testing it manually from your browser.
export default function handler(req, res) {
  res.setPreviewData({})
  res.end('Preview mode enabled')
}

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

從您的 Headless CMS 安全地存取

實際上,您會希望從您的 headless CMS 安全地呼叫此 API 路由。具體的步驟會因您使用的 headless CMS 而異,但以下是一些您可以採取的常見步驟。

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

首先,您應該使用您選擇的 token 產生器建立一個密碼 token 字串。這個密碼只有您的 Next.js 應用程式和您的 headless CMS 知道。這個密碼可以防止沒有權限存取您 CMS 的人存取預覽網址。

其次,如果您的 headless CMS 支援設定自訂預覽網址,請指定以下內容作為預覽網址。這假設您的預覽 API 路由位於 pages/api/preview.js

終端機
https://<your-site>/api/preview?secret=<token>&slug=<path>
  • <your-site> 應該是您的部署網域。
  • <token> 應該替換為您產生的密碼 token。
  • <path> 應該是您想要預覽頁面的路徑。如果您想要預覽 /posts/foo,那麼您應該使用 &slug=/posts/foo

您的 headless CMS 可能允許您在預覽網址中包含變數,以便可以根據 CMS 的資料動態設定 <path>,例如:&slug=/posts/{entry.fields.slug}

最後,在預覽 API 路由中

  • 檢查密碼是否匹配,以及 slug 參數是否存在 (如果不存在,則請求應失敗)。
  • 呼叫 res.setPreviewData
  • 然後將瀏覽器重新導向到 slug 指定的路徑。(以下範例使用 307 重新導向)。
export default async (req, res) => {
  // Check the secret and next parameters
  // This secret should only be known to this API route and the CMS
  if (req.query.secret !== 'MY_SECRET_TOKEN' || !req.query.slug) {
    return res.status(401).json({ message: 'Invalid token' })
  }
 
  // Fetch the headless CMS to check if the provided `slug` exists
  // getPostBySlug would implement the required fetching logic to the headless CMS
  const post = await getPostBySlug(req.query.slug)
 
  // If the slug doesn't exist prevent preview mode from being enabled
  if (!post) {
    return res.status(401).json({ message: 'Invalid slug' })
  }
 
  // Enable Preview Mode by setting the cookies
  res.setPreviewData({})
 
  // Redirect to the path from the fetched post
  // We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
  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) {
  // If you request this page with the preview mode cookies set:
  //
  // - context.preview will be true
  // - context.previewData will be the same as
  //   the argument used for `setPreviewData`.
}

我們在預覽 API 路由中使用了 res.setPreviewData({}),所以 context.previewData 將會是 {}。如有必要,您可以使用它將會話資訊從預覽 API 路由傳遞到 getStaticProps

如果您也使用 getStaticPaths,那麼 context.params 也將會可用。

提取預覽資料

您可以更新 getStaticProps 以根據 context.preview 和/或 context.previewData 提取不同的資料。

例如,您的 headless CMS 可能針對草稿文章有不同的 API 端點。如果是這樣,您可以使用 context.preview 來修改 API 端點網址,如下所示

export async function getStaticProps(context) {
  // If context.preview is true, append "/preview" to the API endpoint
  // to request draft data instead of published data. This will vary
  // based on which headless CMS you're using.
  const res = await fetch(`https://.../${context.preview ? 'preview' : ''}`)
  // ...
}

就是這樣!如果您從您的 headless CMS 或手動存取預覽 API 路由 (帶有 secretslug),您現在應該能夠看到預覽內容。並且如果您在未發布的情況下更新草稿,您應該能夠預覽草稿。

將此設定為您的 headless CMS 上的預覽網址或手動存取,您應該就能夠看到預覽。

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

更多詳細資訊

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

指定預覽模式持續時間

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

  • maxAge:指定預覽會話持續時間的秒數。
  • path:指定 cookie 應用的路徑。預設為 /,為所有路徑啟用預覽模式。
setPreviewData(data, {
  maxAge: 60 * 60, // The preview mode cookies expire in 1 hour
  path: '/about', // The preview mode cookies apply to paths with /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 和本地儲存存取權。