iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
佛心分享-IT 人職涯歷練

從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始系列 第 26 篇

Day 26|/:id 怎麼把別人的網址吃掉了?Static Route 和 Dynamic Route 到底差在哪?

  • 分享至 

  • xImage
  •  

前幾篇開始從:

Debug
↓
Component 選擇
↓
Trade-off

慢慢進入比較接近真實專案的工程判斷。

今天換一個第一次看到時,我覺得非常容易滿頭問號的問題:

網址明明寫對了,為什麼卻跑進另一個頁面?

例如我們原本想進:

/users/create

結果 Router 卻把:

create

當成:

id

於是原本想開:

Create Page

卻跑去:

Detail Page

甚至開始打:

GET /users/create

然後 Backend 回:

400
404

第一眼很容易以為:

API 壞了?

但真正的 Root Cause 可能根本還沒走到 API。

而是在:

Route Matching。


先看兩種 Route

假設我們有:

/users/create

這是一個固定網址。

可以稱作:

Static Route

例如:

/users/create

因為:

create

就是固定的文字。


另一條:

/users/:id

這裡:

:id

代表:

這一段可以接不同的值。

例如:

/users/123
/users/456
/users/999

Router 會把:

123
456
999

當成:

id

這種叫:

Dynamic Route

:id 其實不知道什麼叫「ID」

這裡是很重要的一點。

人看到:

:id

會自然理解:

這裡應該放資料庫 ID。

例如:

123

但 Router 不一定知道:

id 必須是數字。

對它來說:

/users/123

可能符合。

那:

/users/create

其實在「網址形狀」上也可能符合:

/users/:id

因為:

:id = "create"

完全可以成立。


可以把 Route 想成一個 Pattern

例如:

/users/:id

其實是在說:

/users/
後面再接一段東西

所以:

/users/123

符合。

/users/abc

也可能符合。

甚至:

/users/create

也有可能符合。

因為對 Dynamic Segment 來說:

create

只是一段字串。


問題就出現在兩條 Route 同時可能符合

假設我們有:

/users/create

以及:

/users/:id

那網址:

/users/create

對第一條來說:

完全符合

對第二條來說:

:id = "create"

也可能符合。

這時候 Router 就要決定:

到底要選哪一條?


不同 Router 的 Matching 規則可能不同

這裡不能直接記:

「Router 永遠由上往下,先寫的先吃。」

因為不同版本、不同 Routing Library,規則可能不同。

有些 Router 會:

依照宣告順序

有些會:

對 Route 做 ranking

也就是比較:

Static Route
Dynamic Route
Wildcard

哪一條更具體。

所以真正該做的不是死背:

一定要放前面

而是:

知道 Static 和 Dynamic Route 可能發生衝突,然後確認目前專案使用的 Router 怎麼 Match。


但專案裡看到 Route 順序,仍然要特別注意

例如某些 Route 設定可能長得像:

[
  {
    path: "/users/:id",
    component: UserDetail,
  },
  {
    path: "/users/create",
    component: UserCreate,
  },
]

如果目前的 Routing 機制會優先命中前面的:

/users/:id

那:

/users/create

就可能先被當成:

id = create

這時就會出問題。


更安全的思考方式:越具體的 Route 越值得先確認

例如:

/users/create
/users/edit
/users/import

這些都是:

Static Route

而:

/users/:id

比較廣。

可以把它想成:

Static Route
→ 我就是指定這個名字

Dynamic Route
→ 只要這裡有一段值,我都有可能接

所以在某些 Router 設計裡,

會希望較具體的:

/users/create

不要被較廣泛的:

/users/:id

搶走。


如果真的被吃掉,後面會發生什麼?

假設使用者開:

/users/create

但 Router Match 到:

/users/:id

那 Detail Page 可能收到:

id = "create";

接著:

Detail Page
↓
取得 id
↓
UserService.getById(id)

最後變成:

GET /users/create

Frontend 可能完全不知道:

我其實根本進錯頁面了。

它只是很正常地:

拿 route param
↓
打 Detail API

所以整條錯誤流程是:

Route Match 錯
↓
進錯 Component
↓
取得錯誤 param
↓
打錯 API
↓
Backend Error

所以 API Error 不一定是 API 問題

這跟前面幾篇 Debug 的核心很像。

表面看到:

GET /users/create
→ 400

很容易直接查:

Backend Controller
Service
API

但其實應該先問:

為什麼 Frontend 會把 create 當成 ID?

往前追:

API Parameter
↑
Route Param
↑
Route Matching

Root Cause 才出現。


我現在遇到奇怪的 API 參數,會往前追來源

例如 Network 看到:

GET /users/create

如果這支 API 本來期待:

/users/{id}

我不會只想:

Backend 怎麼沒有擋 create?

而會回 Frontend 看:

id 從哪裡來?

可能看到:

const { id } = params;

再繼續問:

params 從哪來?

答案:

Route

最後才找到:

/users/:id

這就是「往上游追資料」

我們前面其實一直在練這件事。

例如:

畫面錯
↓
找 State
↓
找 Response
↓
找 API

今天則是:

API Param 錯
↓
找 params
↓
找 Router
↓
找 Route Matching

不要停在錯誤發生的地方。

而是:

往前找到這個錯誤值最早從哪裡進來。


Dynamic Route 不一定只有 id

例如:

/orders/:orderId
/customers/:customerId/edit
/reports/:type

這些都是 Dynamic Segment。

例如:

/reports/daily

可能得到:

type = "daily";

所以:

:

後面的名字其實只是:

我們幫這段網址取的變數名稱。


可以把它想成函式參數

例如:

function getUser(id) {}

這裡:

id

只是一個變數名稱。

Route:

/users/:id

也是類似概念。

網址:

/users/123

就像:

id = "123";

網址:

/users/create

如果 Match 到同一條,

就變成:

id = "create";

Router 不會因為變數叫:

id

就自動理解:

只能放 Number。


如果 ID 確實只能是數字呢?

這時可以有不同策略。

例如:

Frontend Route 設計避免衝突

或者:

進入頁面後驗證 param

Backend 當然也應該驗證自己的輸入。

但重點是:

不要因為 Backend 最後會驗證,就讓 Frontend Route 設計保持模糊。

每一層還是應該做好自己的責任。


路由命名本身也可以降低衝突

例如:

/users/create
/users/:id

已經很常見。

也可以把操作放得更明確:

/users/new
/users/:id
/users/:id/edit

真正使用哪一種,

還是看專案規範。

但重點是:

設計 Route 時,要想到哪些 path segment 可能互相撞到。


我現在看 Route,不會只看「網址長怎樣」

我會開始看:

這是 Static 還是 Dynamic?

Dynamic Segment 能吃到哪些值?

前面有沒有可能撞到的 Static Route?

這個 Router 的 matching 規則是什麼?

Route Param 最後會流去哪裡?

因為 Route 不只是:

點哪個網址去哪個頁面。

它其實也是資料流的入口。


Route Param 也是資料

例如:

/users/123

裡面的:

123

最後可能變成:

params.id

再變成:

UserService.getById(params.id)

所以完整資料流:

URL
↓
Router
↓
Route Params
↓
Component
↓
Service
↓
API

這跟我們之前的:

Input
↓
State
↓
Request

其實很像。

只是資料來源從:

使用者輸入

換成了:

URL。

今天真正想記的是

看到:

/users/:id

不要把:

:id

理解成:

Router 已經知道這是一個真正的資料庫 ID。

更準確的理解是:

這一段網址是一個動態值,而我們把它命名成 id。

所以:

/users/create

在某些 Route Matching 情境下,

也可能被理解成:

id = "create";

Debug 時如果看到:

API 莫名收到奇怪的 ID

除了查 API,

也要往前看:

這個 ID 是不是從 URL / Route Param 來的?

最後再提醒自己:

錯誤出現在 API,不代表 Root Cause 在 API。

它可能早在 Router 決定「這個 URL 應該進哪個頁面」時,就已經發生了。

下一篇可以繼續進另一個很容易「看起來有做,實際上只做一半」的主題:

權限不是把按鈕藏起來就好:Frontend Permission 和 Backend Authorization 到底差在哪?


上一篇
Day 25|套件明明可以硬改,為什麼最後反而選擇換一種元件?
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始 共 26 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言