← 回到 Blog
Next.js約 3 分鐘閱讀

Next.js 快取重新驗證排查紀錄:把部署後頁面 freshness 留成證據

分類Next.jsSEO網站經營
標籤#Next.js#Vercel#Cache#Revalidation#SEO#部署驗證
Next.js 快取重新驗證排查 cover,顯示 route map、CDN cache、sitemap、curl status 與 OG 預覽證據

Next.js 內容站最容易讓人誤判的問題之一,是「部署成功」不等於「讀者看到的頁面已經更新」。GitHub commit 已經上去、Vercel build 也成功,但自訂網域可能仍然吃到舊的 HTML;文章頁可能已經 200,Blog 列表或 sitemap 卻還沒有新 slug;Open Graph 圖片也可能因快取而延遲更新。

我會把這類問題視為 freshness 事件,而不是只按一次重新部署。這篇整理一份適合內容站的排查紀錄:先確認程式與內容是否已在 repo,再分開檢查 route、列表、SEO 檔案、metadata 與 cache header,最後記錄是否需要重新驗證或等待邊緣快取刷新。

先分清楚:內容沒發布,還是部署快取沒刷新

排查第一步不是清 cache,而是確認新內容是否真的進入版本控制。若 Markdown 文章還停在 untracked 或 staged,production 當然不會更新。

git status --short

git log -1 --oneline

git ls-remote origin main

我會把觀察結果寫成這樣:

Publication evidence
- local file exists: content/blog/new-article.md
- frontmatter draft: false
- git status: clean after commit
- origin/main commit: matches local commit
- build source: same commit as origin/main

如果這一段還沒成立,就不要急著討論 CDN 或 ISR。先把內容 commit、push,並確認遠端分支指向正確 commit。

用 route 組合驗證 freshness

內容站不只是一個文章頁。一次有效的 freshness 檢查至少要包含首頁、Blog 列表、root-level 文章頁、分類頁、robots 與 sitemap。

for path in \
  / \
  /blog \
  /new-article-slug \
  /category/next-js \
  /robots.txt \
  /sitemap.xml

do
  curl -L -I "https://www.ucamc.com${path}"
done

狀態碼只是第一層。真正要看的還包括:

  • 新文章頁是否 200。
  • /blog 是否含有新文章標題或 slug。
  • /sitemap.xml 是否含有 root-level canonical URL。
  • /robots.txt 的 Sitemap 是否指向 production domain。
  • 分類頁是否能從新文章分類進入。

在 UCAMC 這類 root-level 文章架構中,文章 canonical URL 是 /{slug}/blog 只是列表頁。新文章不需要、也不應該依賴 /blog/{slug} 這種路徑。

把 cache header 與 HTML snippet 一起看

有些時候 curl -I 看到 200,但 HTML 仍然是舊內容;也可能 status 新了,列表還沒刷新。這時我會同時保存 header 與 body snippet,而不是只留一行「已上線」。

curl -L -I 'https://www.ucamc.com/blog'

curl -L -sS 'https://www.ucamc.com/blog' | grep 'new-article-slug'

curl -L -sS 'https://www.ucamc.com/sitemap.xml' | grep 'new-article-slug'

紀錄時可以拆成:

Freshness evidence
- article route: 200, contains article h1
- blog listing: 200, contains slug
- sitemap: 200, contains production canonical
- robots: 200, Sitemap points to https://www.ucamc.com/sitemap.xml
- cache observation: first probe stale, later probe fresh

如果 cache header 顯示 HIT,但頁面內容仍舊,就把它當成快取 freshness 問題,而不是立刻改程式碼。

檢查 metadata 與 OG 圖片,不只檢查頁面文字

文章頁更新後,SEO 與社群分享也要一起驗證。尤其是 cover image 或 og:image 變更時,build 成功不代表圖片 URL 可讀。

curl -L -I 'https://www.ucamc.com/images/blog/article-cover.png'

curl -L -sS 'https://www.ucamc.com/new-article-slug' | grep 'og:image'

curl -L -sS 'https://www.ucamc.com/new-article-slug' | grep 'canonical'

我會確認三件事:

  1. 圖片回應是 200,content-type 是 image。
  2. canonical 指向 production root-level URL。
  3. og:image 指向與文章主題相符的 cover,而不是舊文章或泛用圖。

這樣可以避免「頁面看得到文字,但社群分享圖仍舊」的半套上線。

重新驗證前先保留觀察紀錄

若確認 origin/main 已正確、local production 也正常,但 production domain 還是舊內容,才進入重新驗證或 redeploy 判斷。不要在沒有證據時亂改 slug、刪 .next 或重寫 routing。

Revalidation decision record
- local production route: OK
- origin/main: updated
- Vercel preview domain: fresh / stale
- custom domain: fresh / stale
- sitemap: fresh / stale
- action: wait / redeploy / purge cache / inspect deployment hook
- rollback needed: no source rollback, deployment-layer issue only

這份紀錄的重點,是把「原始內容品質」和「部署快取層」分開。內容站長期經營時,這種分層紀錄比一次性的口頭判斷可靠很多。

Legacy redirect 也要用正確範圍驗證

舊 WordPress 文章常會有 ID-prefixed URL,例如 123-old-slug。這種 legacy URL 應該 301 到 root-level slug。但新文章不需要測 /blog/new-slug,因為那不是 UCAMC 的 canonical 路徑。

curl -I 'https://www.ucamc.com/123-new-article-slug'

驗證結果應該寫成:

Legacy redirect evidence
- requested path: /123-new-article-slug
- status: 301
- location: /new-article-slug
- note: /blog/{slug} is not part of new article policy

結語:freshness 是一條證據鏈

Next.js 內容站的部署問題,很少只靠一個 status code 就能判斷。比較可靠的做法,是把 repo、build、route、列表、sitemap、robots、metadata、圖片與 cache header 串成一條證據鏈。

當這條證據鏈完整時,團隊就能清楚知道:問題是文章還沒發布、build 沒吃到新 commit、Vercel preview 已更新但 custom domain 慢半拍,還是 OG 圖片或 sitemap 被快取。這也讓 UCAMC 這類長期內容站,在每日維護中可以更穩定地累積文章,而不是每次部署都重新猜一次。