跳至內容

草稿模式

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

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

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

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

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

首先,建立 **API 路由**。它可以是任何名稱 - 例如 `pages/api/draft.ts`

在此 API 路由中,您需要在回應物件上呼叫 `setDraftMode`。

export default function handler(req, res) {
  // ...
  res.setDraftMode({ enable: true })
  // ...
}

這將設定一個 **cookie** 來啟用草稿模式。後續包含此 cookie 的請求將觸發 **草稿模式**,從而更改靜態生成頁面的行為(稍後會詳細說明)。

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

pages/api/draft.ts
// simple example for testing it manually from your browser.
export default function handler(req, res) {
  res.setDraftMode({ enable: true })
  res.end('Draft mode is enabled')
}

如果您開啟瀏覽器的開發者工具並訪問 `/api/draft`,您會注意到一個名為 `__prerender_bypass` 的 cookie 的 `Set-Cookie` 回應標頭。

從您的無頭 CMS 安全地存取它

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

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

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

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

終端機
https://<your-site>/api/draft?secret=<token>&slug=<path>
  • `<your-site>` 應該是您的部署網域。
  • `<token>` 應該替換為您產生的秘密權杖。
  • `<path>` 應該是您要檢視的頁面的路徑。如果您要檢視 `/posts/foo`,則應該使用 `&slug=/posts/foo`。

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

**最後**,在草稿 API 路由中

  • 檢查秘密是否相符以及 `slug` 參數是否存在(如果不存在,請求應該失敗)。
  • 呼叫 `res.setDraftMode`。
  • 接著將瀏覽器重新導向至 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 draft mode from being enabled
  if (!post) {
    return res.status(401).json({ message: 'Invalid slug' })
  }
 
  // Enable Draft Mode by setting the cookie
  res.setDraftMode({ enable: true })
 
  // 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 查看的路徑。

步驟二:更新 getStaticProps

下一步是更新 getStaticProps 以支援草稿模式。

如果您請求一個具有 getStaticProps 且已設定 cookie(透過 res.setDraftMode)的頁面,則 getStaticProps 將會在**請求時**被呼叫(而不是在建置時)。

此外,它會以一個 context 物件被呼叫,其中 context.draftMode 將會是 true

export async function getStaticProps(context) {
  if (context.draftMode) {
    // dynamic data
  }
}

我們在草稿 API 路徑中使用了 res.setDraftMode,因此 context.draftMode 將會是 true

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

擷取草稿資料

您可以更新 getStaticProps 以根據 context.draftMode 擷取不同的資料。

例如,您的無頭 CMS 可能會有不同的 API 端點用於草稿文章。如果是這樣,您可以修改 API 端點 URL,如下所示

export async function getStaticProps(context) {
  const url = context.draftMode
    ? 'https://draft.example.com'
    : 'https://production.example.com'
  const res = await fetch(url)
  // ...
}

這樣就完成了!如果您從無頭 CMS 或手動存取草稿 API 路徑(包含 secretslug),您現在應該可以看到草稿內容。而且,如果您在未發佈的情況下更新草稿,您應該能夠查看草稿。

將此設定為您無頭 CMS 上的草稿 URL 或手動存取,您應該就能看到草稿。

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

更多細節

然後,傳送請求到 /api/disable-draft 以叫用 API 路徑。如果使用 next/link 呼叫此路徑,您必須傳遞 prefetch={false} 以防止在預先擷取時意外刪除 cookie。

可與 getServerSideProps 搭配使用

草稿模式可與 getServerSideProps 搭配使用,並以 draftMode 鍵值在 context 物件中提供。

注意事項:使用草稿模式時,不應設定 Cache-Control 標頭,因為它無法被繞過。建議改用 ISR (增量靜態再生)

可與 API 路由搭配使用

API 路由將可在請求物件上存取 draftMode。例如:

export default function myApiRoute(req, res) {
  if (req.draftMode) {
    // get draft data
  }
}

每次 next build 皆為唯一

每次執行 next build 時,都會產生新的繞過 Cookie 值。

這確保了繞過 Cookie 無法被猜測。

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