URL 編碼 bug 通常出現在參數裡包含 &=?#、空格、中文、Emoji,或者另一個完整 URL 的時候。JavaScript 裡最常見的錯誤,是應該用 encodeURIComponent() 時用了 encodeURI()

簡單規則:

  • 編碼完整 URL:用 encodeURI()
  • 編碼參數值或路徑片段:用 encodeURIComponent()

為什麼差別很重要

encodeURI() 會保留完整 URL 中有結構意義的字元,例如:

: / ? # & =

這適合你已經有一個完整 URL,只想編碼空格或非 ASCII 字元的情況。

encodeURIComponent() 會編碼更多字元,因為它處理的是 URL 的某一個組成部分,比如 query 參數值。

如果參數值裡有 &=#,卻用了 encodeURI(),query string 就可能被拆壞。

Query 參數範例

假設你要傳:

const search = 'a=b & c=d'

錯誤:

const url = '/search?q=' + encodeURI(search)

裡面的 & 可能被當成新的 query 參數分隔符。

正確:

const url = '/search?q=' + encodeURIComponent(search)

這樣整個值都會留在 q 參數裡。

URL 裡再巢狀 URL

登入跳轉、OAuth 回呼、分享連結都容易遇到這個問題。

const next = 'https://example.com/a?x=1&y=2'
const loginUrl = '/login?next=' + encodeURIComponent(next)

如果用 encodeURI(next),裡面的 &y=2 可能變成外層 /login 的參數,而不是留在 next 裡。

路徑片段範例

路徑片段也應該用 encodeURIComponent()

const username = 'Ada Lovelace/notes'
const url = '/users/' + encodeURIComponent(username)

這裡的 / 是使用者名稱的一部分,不是路徑分隔符,所以必須編碼。

如果用 encodeURI(),斜線會保留,路由結構就變了。

優先使用 URL 和 URLSearchParams

建構 query string 時,現代 JavaScript 有更安全的 API:

const url = new URL('https://example.com/search')
url.searchParams.set('q', 'a=b & c=d')
url.searchParams.set('lang', 'zh-TW')

console.log(url.toString())

URLSearchParams 會幫你處理編碼和分隔符,也能減少重複編碼的風險。

重複編碼

重複編碼就是把已經編碼過的值又編碼一次。

hello world
hello%20world
hello%2520world

%25% 的編碼形式。如果你看到 %2520,通常說明原本的 %20 又被編碼了一遍。

編碼或解碼前,先判斷你正在處理哪一層:

  • 使用者原始輸入
  • query 參數值
  • 完整 URL
  • JSON payload 裡的 URL
  • 另一個 URL 裡的 redirect 參數

空格:%20 還是 +

URL 裡空格常見寫法是 %20。表單編碼的 query string 中,空格也可能表示為 +

除非你明確知道目標格式,否則不要手動替換空格。JavaScript 裡產生 query string 時,通常用 URLSearchParams 更穩。

排錯清單

URL 參數出錯時:

  1. 先判斷你編碼的是完整 URL,還是某個組件。
  2. query 參數值用 encodeURIComponent()
  3. 建構 query string 優先用 URLSearchParams
  4. 檢查 %252F%2520 這類重複編碼。
  5. 一次只解一層。
  6. 特別注意巢狀 redirect URL。
  7. 用包含 &=?#、空格、中文、日文、Emoji 的值測試。

相關工具

URL 編碼解碼工具 檢查單一值,用 URL 參數解析工具 查看完整 query string 如何被拆分。如果參數裡包含 JSON,先解 URL 這一層,再用 JSON 格式化工具 校驗。