商品網址打錯,頁面顯示「找不到商品」,瀏覽器也沒有報錯,看起來似乎已處理完。可是用開發者工具查看文件請求,狀態仍是200。原因可能很單純:程式只回傳了一段找不到的文字,對Next.js來說,這仍是一張成功產生的頁面。畫面內容與HTTP狀態是兩件事,需要在資料查詢的分支一起決定。
在App Router中,可以呼叫notFound()終止目前路由區段的渲染,交由對應的not-found.js呈現找不到的畫面。不過,不要把它記成「任何情況都會回404」:官方文件區分非串流與串流回應;前者可回404,已開始串流的回應可能仍是200。先分清這個條件,才不會因為一個狀態碼就誤判程式有沒有執行。
先在查詢結果的分支判斷
假設網址是/items/[slug]。以下示範只接受exists這個測試項目,其他值都視為不存在,沒有連接資料庫,也沒有登入權限判斷。
import { notFound } from 'next/navigation';
export default async function ItemPage({ params }) {
const { slug } = await params;
if (slug !== 'exists') {
notFound();
}
return <main>Demo exists</main>;
}
這裡先等待路由參數,再判斷項目是否存在。實際專案通常會把條件換成資料查詢:取得商品後,如果結果是空值,便呼叫notFound();有資料才繼續回傳頁面。不要在查詢之前憑網址長相猜測是否存在,也不要先使用商品欄位,等程式因空值出錯才改成找不到。
notFound()會拋出框架辨識的特殊錯誤並停止該區段,因此不需要寫成return notFound()才能生效。也要小心範圍過大的try...catch:若把框架的控制流程一起捕捉,再回傳普通文字,原本要交給找不到畫面的分支就可能被自己截住。資料服務的失敗和資料不存在最好各自處理。
用not-found.js準備讀者下一步
在同一個路由區段放置以下檔案,即可提供這個案例的找不到畫面。
export default function MissingItem() {
return <main>Demo missing item</main>;
}
檔案名稱是not-found.js,不是一般元件取了相似名字便會自動生效。所在路由區段也有意義:商品區段可以提供商品列表連結,其他區段可有各自的說明。要確認專案的資料夾結構,避免只看根目錄檔案就假設所有子路由都使用同一份內容。
對訪客而言,找不到頁應說清楚目前連結沒有對應項目,並提供可用的返回方式。不要把資料庫錯誤堆疊、查詢語句或內部紀錄直接放進頁面。若資料來源暫時故障,也不適合一律寫成商品不存在,否則訪客與維護者都會收到錯誤訊息。錯誤狀態應依實際原因選擇,不能只為了消除畫面例外而把所有失敗收成404。
這個非串流案例實際回了什麼
本地範例使用Next.js 16.3.8,在沒有loading或Suspense邊界的路由中,直接完成上述項目判斷,再發出回應。以HTTP請求分別讀取兩個網址,/items/exists回200;/items/missing回404。不存在的回應包含找不到的內容,也出現noindex指示;存在的回應沒有這個指示。
這個結果證明的是這份範例在回應尚未送出前判斷不存在的行為。它不表示換成遠端資料庫、其他版號、加入載入畫面或經過不同代理後,所有細節都完全一樣。移植時至少要用自己站點的存在與不存在網址各測一次,尤其是實際資料查詢比這個即時分支慢很多的頁面。
查看原始HTML還有一個容易誤會的地方:App Router回應可能攜帶元件資料和備用畫面的定義。這個案例的存在網址中,也能搜尋到找不到畫面的文字,但狀態是200,正常項目文字同樣存在。不能只用「原始碼裡有某個字串」就判定訪客正在看到找不到頁;畫面、回應狀態與資料載荷需要分開核對。
加入串流後為何可能仍是200
官方not-found.js文件說明,串流回應與非串流回應的狀態不同。伺服器一旦已送出回應標頭,後來才發現資料不存在,就不能像尚未開始的回應那樣重新選擇狀態碼。因此,串流情況下找不到的畫面可能出現,HTTP仍維持200。這不是靠在找不到元件裡改一行文字就能修正的事。
若你的實作回200,先查是否有載入邊界、先送出的版面內容或較晚才完成的資料查詢,再看notFound()是否真的執行。不要立即刪除所有載入畫面,或到全站中介層把含某個字串的回應都改404。這些做法可能破壞正常頁面,也會把呈現文字當成路由存在性的依據。
本篇沒有以延遲資料刻意重建串流200案例;這個差異依據官方文件列出。本站若需要特定路由的非串流404,應把資料判斷時間和回應流程當作該路由的設計條件,另外實測。訪客看到清楚的找不到內容仍然必要,但它不能代替傳輸層的檢查。
查驗時先看文件請求
開發者工具的Network中,先找到這次直接載入網址的文件請求,查看狀態與回應,再核對頁面呈現。頁面內切換路由時可能發出框架資料請求;圖片、字型或其他資源的200也不代表商品頁是200。測試前應確認自己正在讀哪一個請求,避免混用快取畫面或上一次導覽結果。
可以用一個確定存在的slug和一個確定不存在的slug比較,同時記下測試環境是否啟用串流。若站點有CDN或反向代理,再核對公開網址的回應,不能只憑本地開發伺服器推定正式結果。伺服器紀錄也應能對回這次路由和時間,幫助判斷狀態是在應用程式還是其他環節產生。
notFound()的文件另說明它會加入搜尋引擎用的noindex標記。本例只核對回應中出現這個標記,沒有測試搜尋引擎爬取、收錄或移除結果。若修正已存在的錯誤網址,仍要檢查實際公開回應與後續索引狀態;一份本地HTML不能代表Google已經完成處理。
別把權限失敗混成資料不存在
商品不存在和使用者無權查看,是兩個不同的問題。有些專案會依自己的安全與產品政策,用找不到回應隱藏資源是否存在;這需要明確的權限流程,並不是呼叫notFound()便自動完成。這份示範只判斷測試項目,沒有展示或驗證任何存取控制。
實作時保留清楚的分支:路由參數有效後查資料,資料存在後再依需求處理可見範圍,最後產生頁面。若查詢本身故障,讓故障進入適合的錯誤處理。這樣維護者才看得出某次回應是正常的不存在,還是服務出了問題;訪客也能得到與實際情況相符的下一步。

評論0