如何在 Next.js 中使用草稿模式預覽內容

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

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

Next.js 的草稿模式 (Draft Mode) 功能可解決此問題。以下是使用說明。

步驟 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
// 簡單範例,供從瀏覽器手動測試
export default function handler(req, res) {
  res.setDraftMode({ enable: true })
  res.end('草稿模式已啟用')
}

若開啟瀏覽器開發者工具並訪問 /api/draft,您會注意到一個 Set-Cookie 回應標頭,其中包含名為 __prerender_bypass 的 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 可能允許在草稿網址中包含變數,使 <path> 能根據 CMS 資料動態設定,例如:&slug=/posts/{entry.fields.slug}

最後,在草稿 API 路由中:

  • 檢查密鑰是否匹配及 slug 參數是否存在(若否,請求應失敗)
  • 呼叫 res.setDraftMode
  • 然後將瀏覽器重新導向至 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' })
  }

  // 透過設定 cookie 啟用草稿模式
  res.setDraftMode({ enable: true })

  // 重新導向至獲取的 post 路徑
  // 不直接導向 req.query.slug 以避免開放重新導向漏洞
  res.redirect(post.slug)
}

若成功,瀏覽器將被重新導向至您想預覽的路徑,並帶有草稿模式 cookie。

步驟 2:更新 getStaticProps

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

若您請求的頁面具有 getStaticProps 且設定了 cookie(透過 res.setDraftMode),則 getStaticProps 將在請求時(而非建置時)被呼叫。

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

export async function getStaticProps(context) {
  if (context.draftMode) {
    // 動態資料
  }
}

我們已在草稿 API 路由中使用 res.setDraftMode,因此 context.draftMode 將為 true

若您同時使用 getStaticPaths,則 context.params 也將可用。

獲取草稿資料

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

例如,您的無頭 CMS 可能有專用的草稿文章 API 端點。若是如此,您可以如下修改 API 端點網址:

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 上的草稿網址或手動存取,您應能看到草稿。

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

更多細節

預設情況下,草稿模式工作階段會在瀏覽器關閉時結束。

若要手動清除草稿模式 cookie,請建立一個呼叫 setDraftMode({ enable: false }) 的 API 路由:

pages/api/disable-draft.ts
export default function handler(req, res) {
  res.setDraftMode({ enable: false })
}

然後發送請求至 /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) {
    // 獲取草稿資料
  }
}

每次 next build 生成唯一值

每次執行 next build 時都會生成新的繞過 cookie 值。

這確保繞過 cookie 無法被猜測。

須知:若要在 HTTP 環境下本地測試草稿模式,您的瀏覽器需允許第三方 cookie 和本地儲存存取。

On this page