跳至內容

預覽模式

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

範例

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

當您的頁面從無頭 CMS 擷取資料時,靜態生成很有用。然而,當您在無頭 CMS 上撰寫草稿並希望立即在頁面上預覽草稿時,它並不理想。您希望 Next.js 在請求時而不是建置時渲染這些頁面,並擷取草稿內容而不是已發佈的內容。您希望 Next.js 僅針對這種特定情況繞過靜態生成。

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

步驟 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 會在瀏覽器上設定一些**Cookie**,以開啟預覽模式。任何包含這些 Cookie 的 Next.js 請求都將被視為**預覽模式**,並且靜態生成頁面的行為將會改變(稍後會詳細說明)。

您可以透過建立如下 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 Cookie 將會在此請求上設定。

從您的無頭 CMS 安全地存取

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

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

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

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

終端機
https://<your-site>/api/preview?secret=<token>&slug=<path>
  • <your-site> 應該是您的部署網域。
  • <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)
}

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

步驟 2:更新 getStaticProps

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

如果您請求的頁面具有 getStaticProps 且設定了預覽模式 Cookie(透過 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
})

清除預覽模式 Cookie
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 路由將可以透過 request 物件存取 previewpreviewData。例如:

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

每次執行 next build 後皆不同

用於繞過驗證的 Cookie 值和用於加密 previewData 的私鑰都會在 next build 完成後更改。這確保了繞過驗證的 Cookie 無法被猜測。

注意事項:要在本機透過 HTTP 測試預覽模式,您的瀏覽器需要允許第三方 Cookie 和本地儲存空間的存取。