Table of Contents
前言
有一個專案是用 Next.js Pages router 開發的
之前遇到連結後點進去,有時頁面會載入失敗,Network 裡看到 _next/data/...json 是 {}
追查了一陣子,最後發現是 Next.js Prefetch 的問題
問題現象
在正式環境中,觀察到以下現象:
- Hover 連結後點擊,頁面載入失敗,即使設定
prefetch={false} - Network 看到
/_next/data/...json內容不是預期的網頁資料,而是 {}
像是「prefetch 快取到壞掉的 JSON」,真正點擊時拿到錯誤資料,頁面就炸了
背景知識:Next.js Prefetch 是什麼?
當使用者在頁面上看到連結時,Next.js 會在背景預先抓取目標頁面的 JSON 資料
使用者真正點擊時,頁面可以瞬間渲染,大幅提升使用者體驗
Prefetch 的觸發時機
- 預取會在
Link元件進入使用者視窗時發生,初始載入或透過滾動
Next.js 會在背景中預先載入連結路線(由 href 標示)以及相關資料,以提升客戶端導航的效能
預取僅在生產環境中啟用
// 預設會 prefetch<Link href="/videos/123">看影片</Link>
// 關閉 prefetch<Link href="/videos/123" prefetch={false}>看影片</Link>prefetch 屬性的值:
- true(預設值):完整路徑及其資料將會預取
- false:理論上不預取,但實測 hover 仍可能觸發
如果想完全移除 hover 時的預取,可以用 a 標籤,或逐步採用 App Router,因為可以在 hover 時關閉預取
Prefetch 專用的 Header
當 Next.js 發起 prefetch 請求時,會帶上兩個關鍵 header:
purpose: prefetch— 告訴伺服器這是預取請求x-middleware-prefetch— Next.js 內部用來標記這是 prefetch 的 header
伺服器可以根據這些 header 來決定是否要跑完整的 SSR 流程,或是返回簡化的預覽資料
問題根因分析
從官方 issue 內容來看,最可能原因是 Next.js 某些版本在「Middleware + /_next/data」流程有 bug,導致部分 data request 回傳空的 {},並帶上 x-middleware-next: 1 / x-middleware-skip
這通常會被下列情況放大:
- 預取與正式導頁共用同一個
/_next/data/*URL,預取回應可能被標記為 skip 或空內容 - 反向代理或 CDN(例如 Nginx cache)把「預取或 skip 的空 JSON」快取起來,真正導頁就拿到 {}
- Middleware 有 set-cookie 或自訂 header,觸發了 Next 內部「跳過或重用 data response」的最佳化路徑
- middleware 中有 fetch 或 rewrites,可能讓資料流在某些情況下被提早中止(更少見,但也會出現空 body
解法:讓 Prefetch 繞過特殊處理
在 Middleware 偵測到 prefetch 請求時,主動刪除 x-middleware-prefetch header,讓它不要走 Next.js 的特殊 prefetch 路徑,而是走正常的 SSR 流程
function handlePrefetch(request: NextRequest): Headers { const reqHeaders = new Headers(request.headers); const purpose = reqHeaders.get("purpose"); const isPrefetch = purpose && purpose.match(/prefetch/i);
if (isPrefetch) { // 刪除這個 header,讓 prefetch 走正常流程 reqHeaders.delete("x-middleware-prefetch"); }
return reqHeaders;}這個做法的好處是:
- Prefetch 請求會得到完整的 SSR 結果
- 不會拿到錯誤的快取 JSON
- 正式環境的穩定性大幅提升
代價與效能取捨
任何技術決策都有代價,這個修法也不例外
優點
- 穩定性提升:不再出現 hover 後頁面壞掉的問題
- 使用者體驗改善:點擊後頁面一定能正確載入
- Debug 更容易:不再需要處理奇怪的 prefetch 快取問題
代價
- Prefetch 效率降低:因為走正常流程,prefetch 成本變高
- 伺服器負擔增加:每次 prefetch 都會觸發完整 SSR
- 首點延遲可能增加:如果完全關閉 prefetch,首次點擊會稍微慢一點
進階方案:如果還是想保留 Prefetch 效能
如果你的網站對效能要求更高,或 prefetch 問題讓你很苦惱,可以考慮以下方案
方案一:覆寫並關閉 Link 的 Prefetch
直接關閉 prefetch:
import { useRouter } from "next/router";import { useEffect } from "react";
const useConditionalPrefetch = () => { const router = useRouter();
useEffect(() => { // 直接把 prefetch 變成空操作,等同關閉 router.prefetch = () => Promise.resolve(); }, [router]);};然後在 _app.tsx 中使用
如何驗證問題是否解決
如果你也遇到類似問題,可以用以下方式驗證
1. 打開 DevTools Network
- Hover 一個連結
- 觀察
/_next/data/...json的內容
2. 使用 Production Build 測試
npm run buildnpm start# 然後用正式環境的行為測試總結
這次的踩坑經驗 穩定性優先於效能 寧可稍微慢一點,也不要使用者看到錯誤