在 app/items裡新增 route.js,打算讓同一條路徑既顯示商品頁,也回傳JSON。建置卻提示 /items/page與 /items/route指向相同路徑。這不是把函式名稱改一下就能修好的錯誤;兩個檔案正在爭用同一個URL。
App Router的 page提供頁面UI,route提供Route Handler。兩者不能放在同一個路由區段,讓同一條路徑各自處理請求。若網站需要商品頁與商品資料API,給它們各自的路徑,再讓呼叫端明確使用對應入口。
資料夾相同,就先看URL是否相同
問題結構通常長這樣。layout是共用外殼,不是這次衝突的主角;衝突來自items底下兩個具有回應責任的檔案。
app/
layout.js
items/
page.js
route.js
page.js可以匯出頁面元件;route.js則匯出GET、POST等處理函式。即使函式寫得完全不同,這兩個檔案還是落在同一個路由區段。不要把「不同檔名」當成「不同網址」,也不要期待框架讀內容後自行猜使用者想要頁面還是JSON。
同樣地,把page改成TypeScript、route改成JavaScript不會分開路徑。副檔名決定程式語言處理,不是幫URL新增區段。衝突位置若在更深層資料夾,沿資料夾名稱算出實際路徑,會比反覆改副檔名更有用。
把API移到明確的資料路徑
一種常見結構是讓 /items保留頁面,將JSON放到 /api/items。這裡的api只是你選的路徑名稱;Route Handler並不是只能放在api資料夾,選這個名字是讓團隊容易辨認入口用途。
app/
layout.js
items/
page.js
api/
items/
route.js
頁面範例只顯示文字,API回合成ID,兩者不接業務資料。把這個最小結構跑通,才能再接回真正的商品清單。
// app/items/page.js
export default function ItemsPage() {
return <main>Items demo page</main>
}
// app/api/items/route.js
export function GET() {
return Response.json({ items: [11, 13] })
}
修改路徑後,呼叫端也要改成 /api/items,不要後端移好了,前端仍向 /items發fetch。那樣可能收到HTML頁面,再在 response.json()報解析錯誤,讓你誤以為API只是回應格式壞掉。
真建置錯誤,與修正後兩種HTTP回應
本文案例使用Next.js16.3.8與React19.3.0。先在items同時放page與route,執行 next build --webpack,建置結束碼為1,錯誤明確指出兩個頁面解析到相同路徑,並列出 /items/page與 /items/route。
之後保留page,把route移到api/items,再啟動測試專案。請求 /items回200,內容類型是HTML,畫面標記為Items demo page;請求 /api/items也回200,但內容類型是JSON,資料為 {"items":[11,13]}。這份修正比較使用實際HTTP回應,不只是檔案結構看起來不同。
curl -i http://127.0.0.1:3000/items
curl -i http://127.0.0.1:3000/api/items
測試時別只看兩個狀態碼都200。確認Content-Type與內容,才能知道拿到頁面還是資料。本例在修正後以開發伺服器核兩個入口,不把它稱為完整部署驗收;自己的專案最後仍應執行適合的建置檢查,確認其他路由沒有一起出問題。
只有POST的route也不能隨便與page共用
「page處理GET、route只匯出POST,所以應該不衝突」看起來合理,但仍不符合這個檔案規則。官方說明page與route會取得該路由的處理責任,不能在同一個區段分別放兩者,再用匯出的HTTP方法自行拆配。
如果頁面表單需要提交資料,可以選合適的Server Action,或將POST處理放在另一條Route Handler路徑。先選互動方式,再依那個方式安排檔案與呼叫端,不能為了少一個資料夾讓兩個入口共用相同URL。
Route Handler只會依實際匯出的HTTP方法處理請求。某個方法沒有被支援時,與路由本身不存在是不同情況;查API回應時,也要核對前端究竟發GET還是POST。這個檢查不會取消page/route同區段的限制,只是修正路徑後,下一步仍要把請求方法對上。
括號路由群組不會替URL分流
用 (site)或 (api)這類路由群組整理資料夾,括號名稱不會出現在URL中。若在兩個群組裡各放items相關入口,實際都解析為 /items,仍可能造成同路徑衝突。路由群組適合組織與layout安排,不應被當成外部網址的前綴。
想要 /api/items,需要真正的api區段,不是 (api)。這個小差別在檔案樹裡很容易被忽略,卻會直接改變路徑。檢查建置錯誤列出的解析後路徑,往往比只看資料夾名稱更能指出哪裡共用。
同時也別為了這個簡單問題引入攔截路由或平行路由等不同功能。那些功能有各自的UI需求與規則,不是把一般JSONAPI與page放同區段的替代方法。本文的任務是讓兩個入口有清楚且不衝突的URL,先用最直接的結構處理。
移路徑時,查引用比刪檔更重要
如果route是剛新增、尚未被外部使用,通常可以按你選定的API路徑調整。如果它已被客戶端或其他網站使用,則需要保留契約與遷移安排,不能直接搬完就讓既有請求失敗。先找出fetch、表單action、測試與文件中的引用,確認誰在呼叫。
搜尋時以實際網址與檔案路徑一起查。頁面連結可能只該指向 /items,取資料的fetch才應指向 /api/items;不要全域把每個items字串都替換成api/items。否則原本正確的導覽也可能被改成打開JSON。
如果測試框架或代理把請求改寫到別處,還要看最終匹配的是哪個路由。這類專案設定應按已有規則核對,不必為了解決單一衝突就新增全站rewrite。路徑已分開,再檢查每個呼叫端,通常能維持最小且容易審查的變更。
API回應與頁面內容各自驗證
資料API應確認輸入、權限、回應形狀與失敗狀態;頁面則確認資料如何呈現與使用者如何操作。本文的兩個合成ID不代表你的商品已經完成權限檢查,也不代表真頁已經接好這份資料。路由衝突解決後,還要回到各入口原本的功能要求。
若Server Component可直接使用你自己的資料函式,不一定需要為了讓頁面顯示清單,就再向自己建立的HTTPAPI發一次請求。API是否有必要存在,取決於其他呼叫端與架構;不能因為本文用了兩條路徑,就把每個伺服器頁都改成自呼叫API。
修正完成後,保留原衝突建置訊息、改後檔案結構與兩個HTTP內容類型的比較。往後再加新路由時,可以直接回看page與route的責任分配,而不是看到建置失敗就刪掉不熟悉的檔案,留下沒有替代入口的功能缺口。
測試資料也應保持入口可辨認:頁面放一個明確文字標記,API回一個小型固定JSON。等這兩個入口核對完成,再恢復完整畫面與真資料。若一開始就接大量內容,HTML解析錯誤、授權失敗與路徑衝突容易同時出現,反而看不清修正了哪一項。最小入口確認後的業務整合仍要測,不把合成回應冒作商品服務已完成。

評論0