827 字
4 分鐘

new URL() 為什麼吃掉 API 路徑?基底尾斜線與相對網址排錯

new URL('users', 'https://example.com/api/v1') 的結果是 /api/users,v1 不見了。如果基底代表目錄,保留結尾 /;如果相對值以 / 開頭,它會從網站根目錄解析,基底中的 API 路徑仍會被捨棄。

這是 URL 解析規則,不代表 API 已重新導向或伺服器改了路由。先在發出請求前看解析結果,才能區分網址組錯與後端回應錯。

用斷言看出 new URL() 的五種路徑#

MDN 的相對網址解析說明指出,基底的目前目錄取到最後一個斜線為止。下面程式可存成 .mjs,以 Node.js 執行,不會連線:

import assert from 'node:assert/strict';
const cases = [
['users', 'https://example.com/api/v1', 'https://example.com/api/users'],
['users', 'https://example.com/api/v1/', 'https://example.com/api/v1/users'],
['/users', 'https://example.com/api/v1/', 'https://example.com/users'],
['../users', 'https://example.com/api/v1/', 'https://example.com/api/users'],
['https://other.example/users', 'https://example.com/api/v1/',
'https://other.example/users'],
];
for (const [reference, base, expected] of cases) {
assert.equal(new URL(reference, base).href, expected);
}
console.log('五組 URL 路徑檢查通過');

第一組的 v1 是最後一段,解析 users 時會被取代;第二組最後一段是目錄,才會留下 /v1/。第四組的 ../ 從目前目錄往上一層。第五組直接給完整網址,基底不會把它限制在原網站。

不要直接把所有網址都補上 /:原本指向檔案或完整 endpoint 的 URL 可能就是不該補。要先定義設定值是「API 目錄」還是「完整資源位址」,再寫對應測試。

要保留 API 前綴,就分開組路徑與 query#

以下設定契約是固定的 API 目錄與程式內已知 endpoint。查詢資料用 searchParams 加入,避免將 &、+ 誤當網址結構。範例可獨立執行:

import assert from 'node:assert/strict';
const apiDirectory = new URL('https://example.com/api/v1/');
const endpoint = new URL('users', apiDirectory);
endpoint.searchParams.set('q', 'C++ & 台灣');
endpoint.searchParams.set('page', '1');
assert.equal(endpoint.origin, apiDirectory.origin);
assert.equal(endpoint.pathname, '/api/v1/users');
assert.equal(endpoint.searchParams.get('q'), 'C++ & 台灣');
assert.equal(endpoint.searchParams.get('page'), '1');
console.log(endpoint.href);

重點是檢查最終物件的 origin 和 pathname,而非原始字串看起來有 /api 就算通過。需要保留多個同名 query 時,接著看 URLSearchParams 重複 key 與編碼邊界,不要在這裡再手動做一次解碼。

這份程式只是對固定 endpoint 的回歸斷言,不是任意使用者網址的安全驗證器。若輸入能包含完整網址、//other.example/ 或 ../,就必須另定允許範圍;伺服器代抓網址還涉及重新導向、DNS 與內網目的地,不能只靠這兩個斷言宣稱已防止 SSRF。

Network 面板看到的網址仍不同,接著查哪裡?#

先記錄建立 Request 前的 endpoint.href,再比較 Network 的請求網址與重新導向鏈。若第一個請求正確、後續位置不同,調查 redirect;若一開始就錯,回頭檢查執行環境實際讀到的 base、endpoint 與任何包裝函式。

瀏覽器裡 fetch('users') 會依文件的基底 URL 解析;它不是自動沿用你另存的 API 設定。需要穩定 API 目錄時,明確傳入建立好的 URL。若在 Node 腳本處理的是本機資源檔,則應查 工作目錄與 import.meta.url,不要把網頁 API 路徑與檔案定位混為一談。

本文執行的是 Node.js 的 URL 解析與 query 往返檢查,沒有送出 HTTP 請求、觸發 redirect 或測試實際框架。因此通過斷言只代表這些輸入的解析結果符合契約,後端是否存在該 endpoint 仍要另行驗收。

參考資料:

MDN:Resolving relative references to a URL

WHATWG URL Standard

MDN:HTML base 元素

new URL() 為什麼吃掉 API 路徑?基底尾斜線與相對網址排錯
https://laplusda.com/posts/javascript-new-url-base-trailing-slash/
作者
Zero
發佈於
2026-10-31
許可協議
CC BY-NC-SA 4.0