Next.js 列表切換後,網址已經是 ?q=shop,搜尋標題卻還寫 blog;按一下瀏覽器重新整理,標題又正常了。這種差異不一定是快取,也不一定是 Link 壞掉。先找出畫面讀的是目前網址,還是元件第一次建立時留下的值。
這裡用 App Router 的搜尋條件作例子。站內 Link 的基本選擇可先讀Next.js 站內連結用 Link 還是 a;本文接著處理「連結已正確切換,但畫面狀態沒跟上」的問題。
先決定哪個值應該跟著網址走
搜尋結果、分類與頁碼通常需要能直接開啟、分享,也要在重新整理後保持同一條件。若你的列表把 q 定義為搜尋條件,就讓結果與顯示的已套用條件讀同一個網址參數。別讓網址是一份資料,元件又保留另一份互不更新的副本。
輸入框裡尚未送出的文字則不同。訪客正在把「部落格」改成「部落格模板」,不代表結果已經套用新條件。可以用本地 state 保存草稿,按送出後才更新網址。這樣畫面能分清「正在編輯」與「已套用」,也不會因每打一個字就換頁而打斷操作。
先在紙上列出三項:網址裡的 q、結果實際使用的 q、輸入框草稿。若前兩項不一致,就查讀取流程;若只有草稿不同,先確認它是否本來就應保留。不要為了讓所有數字一致,直接清掉訪客還沒送出的內容。
最容易漏看的,是 useState 的初始值
下面兩行看起來都在讀 q,生命週期卻不同:
const q = useSearchParams().get('q') ?? '';
const [initialQ] = useState(q);
q 來自這次渲染取得的網址參數;initialQ 是 state 第一次初始化時的值。React 不會因下一次傳給 useState 的初始參數改了,就自動覆寫既有 state。若元件仍保留著,切換到 shop 時,q 可以是 shop,initialQ 卻仍是 blog。
這不表示元件完全沒有重新渲染。它可能已經讀到新 q,只是畫面仍選擇顯示 initialQ。排查時把兩個值暫時並排顯示,通常比單看「畫面沒動」更容易定位。若目前 q 已更新,就先查用了哪個變數,不必急著清除整站快取。
同一個共用 layout,三種操作的差異
以下以 Next.js 16 的 App Router 搜尋示例說明。首頁與模板列表共用 layout,裡面的 Client Component 同時顯示目前 q、初始化快照及草稿。由 /?q=blog 填入「部落格模板」,再點原分頁的 Link 前往 /products?q=shop。

這個例子點 router.refresh(),初始化快照仍是 blog,草稿仍在。再按瀏覽器重新整理,同一個 shop 網址重新建立元件,快照才變成 shop,草稿回到空白。重新整理看似「修好」標題,其實只是重新初始化,沒有解決下一次 Link 切換仍可能讀舊快照的原因。
這裡的保留條件是共用 layout 中的同一個元件。若你換了元件的 key、跨到另一個根 layout,或本來把狀態放在會被替換的位置,結果可能不同。請先看實際元件邊界,別把示例推成所有頁面都一定保留 state。
修正顯示值,別再複製一份網址 state
如果標題只是要顯示目前搜尋條件,直接由 useSearchParams 取值即可。這段應放在 Client Component,檔案開頭要有 'use client':
'use client';
import { useSearchParams } from 'next/navigation';
export default function AppliedSearch() {
const params = useSearchParams();
const q = params.get('q') ?? '';
return <p>已套用搜尋:{q || '全部'}</p>;
}
共用的 Server layout 不接收 searchParams;不能把它當每次換頁都重新取得查詢參數的入口。需要跟著網址更新的小區塊,可由 layout 嵌入上面的 Client Component;伺服器端結果查詢則放在 page,從 page 的 searchParams 取得條件。
在這裡採用的 Next.js 16,page 的 searchParams 是 Promise,伺服器端使用前要按版本寫法讀取;不要直接照搬舊版同步物件的範例。還要處理重複參數、空值與允許的分類/頁碼範圍,網址有值不代表能直接拿去查資料。
若路由採靜態渲染,使用 useSearchParams 的 Client Component 應放在合適的 Suspense 邊界內。開發模式能打開,不表示正式 build 的要求都已滿足。上面只是讀值元件,整合時要同時核對頁面的渲染方式與外層邊界。
把讀取位置標清楚後,再沿著結果元件往下查。例如標題直接讀 q,資料查詢卻仍收 initialQ,兩邊還是會分裂。讓查詢參數先經同一套合法值處理,再把處理後的值交給查詢和標題;不要在不同元件各自選一個預設值,造成空網址時一邊顯示全部、一邊查 blog。
若結果是在客戶端非同步取得,也要核對請求使用的參數與最後顯示的回應。網址變成 shop,而畫面顯示較早 blog 請求的結果,是另一個讀取/回應時序問題。先保留兩次請求的條件與回應順序給維護者,不能只因標題更新了,就認定查詢也正確;也不要把未查過的請求問題都歸給 layout。
草稿要保留,還是跟著條件重設?
不要一看到 state 舊值,就把所有 state 改成網址值。網址是已套用條件的依據;草稿可以有自己的生命週期。若產品希望換分類後仍能繼續編輯,保留草稿是合理行為。若希望切換特定搜尋條件時重建表單,才考慮在那個小元件上使用對應 key,並確認重設會失去哪些輸入。
也別先替整個 layout 加 key。那會擴大重建範圍,其他表單、焦點或暫存狀態也可能被重設。只要顯示已套用 q,不需要另外建 state 再用 Effect 同步;讓同一份參數直接產生標題,少一份副本就少一個不同步的位置。
router.refresh 不是瀏覽器重新整理
router.refresh() 會向伺服器重新請求並合併更新後的 Server Component 內容,同時保留不受影響的客戶端 React state。它不等於重新建立整個 document,也不能當作「把所有欄位清空」的按鈕。
資料內容確實舊了,還要另查伺服器端取資料與快取失效方式;router.refresh 不會因此一併讓所有伺服器快取失效。先分清舊的是本地快照、目前參數,還是伺服器查詢結果,再決定修哪一段。
用同一組條件確認修正
修好後,先由 blog 點到 shop,確認網址、已套用標題及結果使用同一個 q。再返回上一頁、直接貼上 shop 網址,以及在新分頁打開;已套用條件應能由網址重建。草稿是否保留,則按先前決定的規則另外判斷。
新分頁不會接續原分頁的未提交 state;若需求是跨重新整理保存草稿,還需要明確的保存方案與資料範圍。不要把共用 layout 的暫存誤當永久保存。完成這些比較後,再處理真正的伺服器資料問題,才不會用反覆重整掩蓋狀態讀取錯位。
參考資料
Next.js:useSearchParams 與渲染邊界
Next.js:layout 的查詢參數限制
Next.js:page 的 searchParams
Next.js:router.refresh 與客戶端狀態
示例測試版本:Next.js 16.3.8/React 19.3.0。

評論0