Cấu trúc URL | Scheme, host, path, query — đọc đúng trước khi encode
URL không phải một chuỗi phẳng — nó là scheme + host (+ port) + path + query (+ fragment). Lỗi API thường nằm ở query encode sai hoặc ghép path/query lẫn nhau. Tiếng Việt trong ?q= phải percent-encode; giá trị query dùng encodeURIComponent, không encode cả URL bằng encodeURI. Khi nghi ngờ, tách từng phần rồi kiểm bằng URL Encode.
Developer Việt hay gặp cảnh này: copy URL từ Figma/Slack, paste vào fetch, backend báo 400 — hoặc tệ hơn, param tiếng Việt thành ???. Nguyên nhân ít khi là “API hỏng”, mà là không tách đúng cấu trúc URL trước khi encode.
Bài này tập trung đọc URL theo scheme / host / path / query, chỉ ra chỗ encode, và checklist tránh double-encode khi làm việc với tiếng Việt.
Bài viết này giúp bạn
- Nhận diện từng thành phần của một URL đầy đủ
- Biết phần nào được encode, phần nào giữ nguyên
- Ghép query tiếng Việt không làm vỡ
&/= - Phân biệt
encodeURIvsencodeURIComponentvsURLSearchParams - Debug nhanh bằng checklist + công cụ encode local
URL đầy đủ trông như thế nào?
Ví dụ (có chủ đích dài hơn thực tế để thấy mọi phần):
https://api.example.vn:8443/v1/search?q=H%C3%A0+N%E1%BB%99i&page=2#results
│ │ │ │ │ │
│ │ │ │ │ └─ fragment
│ │ │ │ └─ query (sau ?)
│ │ │ └─ path
│ │ └─ port (tùy chọn)
│ └─ host
└─ scheme
| Thành phần | Ví dụ | Vai trò |
|---|---|---|
| scheme | https | Giao thức; quyết định cổng mặc định (443) và bảo mật |
| host | api.example.vn | Tên miền hoặc IP mà DNS/TCP nối tới |
| port | 8443 | Chỉ ghi khi khác mặc định; local hay gặp :5173, :8080 |
| path | /v1/search | Tài nguyên / route trên server |
| query | q=...&page=2 | Tham số; bắt đầu bằng ?, nối bằng & |
| fragment | results | Neo phía client (#...); không gửi lên server HTTP |
#results chỉ dùng trên trình duyệt. API Node/Express không nhận fragment. Đừng đặt token hoặc filter quan trọng sau #.
Origin = scheme + host + port
CORS và cookie domain nhìn origin, không nhìn path/query. http://localhost:5173 và http://localhost:8080 là hai origin khác nhau — liên quan trực tiếp đến lỗi CORS khi gọi API local.
Scheme và host: lỗi hay gặp trên local
http vs https
- Dev: thường
http://localhost - Staging/production:
https://... - Mixed content: trang
httpsgọi APIhttp→ trình duyệt chặn
Khi copy URL từ production sang local, đổi scheme + host + port, giữ path/query nếu backend tương thích.
localhost vs 127.0.0.1
Về TCP gần như giống nhau, nhưng với trình duyệt:
- Cookie / SameSite có thể khác ngữ cảnh
- CORS:
localhost≠127.0.0.1(host khác → origin khác)
Team outsourcing hay dính: frontend mở localhost, API docs ghi 127.0.0.1 — CORS đỏ dù “cùng máy”.
Path: route, không phải query
Path mô tả tài nguyên:
/users
/users/42
/v1/orders/export
Quy tắc thực tế:
- Slash đầu
/là tuyệt đối trên host hiện tại - Path segment có khoảng trắng / tiếng Việt → cần encode (
/thư-mục→ percent-encode) - Không nhét filter dài vào path nếu API thiết kế query — dễ đụng giới hạn proxy và log
Ví dụ sai phổ biến khi build string tay:
// SAI: nối query vào path mà quên ?
const url = base + "/search" + "q=" + keyword;
// → /searchq=hello (thiếu ?)
Đúng hơn:
const url = `${base}/search?q=${encodeURIComponent(keyword)}`;
Hoặc dùng URL + searchParams (xem bên dưới).
Query: nơi tiếng Việt dễ vỡ nhất
Query có dạng:
?key1=value1&key2=value2
?mở phần query=tách tên và giá trị&tách các cặp
Nếu giá trị chứa &, =, ?, #, khoảng trắng hoặc tiếng Việt mà không encode, parser sẽ cắt sai.
Ví dụ tiếng Việt
Từ khóa tìm kiếm: cà phê sữa
| Cách | Kết quả |
|---|---|
| Không encode | ?q=cà phê sữa — khoảng trắng / dấu có thể bị cắt hoặc lỗi proxy |
| Encode đúng | ?q=c%C3%A0%20ph%C3%AA%20s%E1%BB%AFa |
| Form style | ?q=c%C3%A0+ph%C3%AA+s%E1%BB%AFa (+ = khoảng trắng trong form encoding) |
Để xem chuỗi %XX đọc được lại tiếng Việt, dán vào URL Encode / Decode rồi bấm Decode — chạy trên trình duyệt, không upload.
encodeURI vs encodeURIComponent
| Hàm / API | Khi nào dùng |
|---|---|
| encodeURI(url) | Encode cả URL nhưng giữ : / ? # & =. Không đủ an toàn cho giá trị query có & |
| encodeURIComponent(value) | Giá trị query / path segment. Encode cả & = ? # → an toàn khi nhúng vào key= |
| URLSearchParams | Tự encode theo chuẩn form; tránh double-encode nếu bạn đã %XX sẵn |
| new URL(path, base) | Ghép scheme/host/path đúng chuẩn; kết hợp .searchParams.set() |
Quy tắc vàng: encode value, không encode cả chuỗi ?a=1&b=2 nếu bạn đang tự nối — encode từng value rồi nối bằng &.
const q = "Hà Nội & xung quanh";
const url = new URL("https://api.example.vn/v1/search");
url.searchParams.set("q", q); // tự encode
url.searchParams.set("page", "2");
console.log(url.toString());
// https://api.example.vn/v1/search?q=H%C3%A0+N%E1%BB%99i+%26+xung+quanh&page=2
Double-encode — triệu chứng %25
à → %C3%A0 (một lần)
→ %25C3%25A0 (hai lần — sai, xuất hiện %25)
Log thấy nhiều %25 liên tiếp: ai đó encode rồi encode lại. Decode một lần chưa ra tiếng Việt → thử decode thêm, hoặc tìm chỗ gọi encodeURIComponent trùng.
Case study ngắn
1) Freelancer: form tìm kiếm tiếng Việt
Khách báo “search không ra gì với từ có dấu”. Frontend gửi:
GET /search?q=Đà Nẵng
Proxy nginx cắt tại khoảng trắng. Sửa: encodeURIComponent hoặc searchParams. Sau khi encode, Network tab hiện %C4%90%C3%A0%20N%E1%BA%B5ng — backend nhận đúng.
2) Outsourcing: ghép webhook URL
Team nối callback vào query của URL đã có sẵn ?env=staging:
// SAI
redirect = base + "?callback=" + myUrl;
// base đã có ? → thành ...?env=staging?callback=...
Đúng: dùng URL rồi searchParams.set("callback", myUrl) — thư viện tự chọn ? hoặc &.
Checklist debug URL (5 phút)
- Tách bằng mắt hoặc
new URL(str)— scheme/host/path/query rõ chưa? - Query có tiếng Việt / khoảng trắng /
&trong value không? - Encode một lần — có
%25lạ không? - Fragment (
#) có đang chứa data mà bạn tưởng server nhận không? - Local:
localhostvs127.0.0.1, port có khớp CORS không? - So sánh chuỗi encode với công cụ URL Encode (không cần đăng ký, chạy local trên trình duyệt)
Cách dùng nhanh trên Kawa
- Mở URL Encode
- Dán chỉ phần value cần kiểm (ví dụ
cà phê) hoặc cả chuỗi%XXcần đọc lại - Encode → lấy
%XX; Decode → đọc tiếng Việt từ log - Copy kết quả vào Postman / code — đối chiếu với
URLSearchParams
Không thay thế việc hiểu cấu trúc URL, nhưng giúp xác nhận encode đúng trước khi đổ lỗi backend.
FAQ mở rộng
Path có dấu tiếng Việt có SEO không?
Nhiều site dùng slug không dấu (ha-noi) cho dễ đọc; nếu dùng Unicode, trình duyệt vẫn encode khi request. Ưu tiên convention team và CDN.
Nên để secret trong query không?
Tránh. Query vào access log, Referer, history. Dùng header hoặc body.
application/x-www-form-urlencoded liên quan gì?
Cùng họ percent-encoding; khoảng trắng thường thành +. Khi decode thủ công, nhớ quy ước này.
Liên kết liên quan
- URL Encode / Decode — mã hóa percent-encoding trên trình duyệt
- Danh sách công cụ
- CORS trên localhost — origin = scheme + host + port