Cấu trúc URL | Scheme, host, path, query — đọc đúng trước khi encode

(Cập nhật: 19 tháng 7, 2026 ) URL scheme host path query percent-encoding tiếng Việt API
Kết luận

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 encodeURI vs encodeURIComponent vs URLSearchParams
  • 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ầnVí dụVai trò
schemehttpsGiao thức; quyết định cổng mặc định (443) và bảo mật
hostapi.example.vnTên miền hoặc IP mà DNS/TCP nối tới
port8443Chỉ ghi khi khác mặc định; local hay gặp :5173, :8080
path/v1/searchTài nguyên / route trên server
queryq=...&page=2Tham số; bắt đầu bằng ?, nối bằng &
fragmentresultsNeo phía client (#...); không gửi lên server HTTP
Fragment không đến backend

#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:5173http://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 https gọi API http → 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: localhost127.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áchKế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

Chọn hàm encode nào?
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)

  1. Tách bằng mắt hoặc new URL(str) — scheme/host/path/query rõ chưa?
  2. Query có tiếng Việt / khoảng trắng / & trong value không?
  3. Encode một lần — có %25 lạ không?
  4. Fragment (#) có đang chứa data mà bạn tưởng server nhận không?
  5. Local: localhost vs 127.0.0.1, port có khớp CORS không?
  6. 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

  1. Mở URL Encode
  2. Dán chỉ phần value cần kiểm (ví dụ cà phê) hoặc cả chuỗi %XX cần đọc lại
  3. Encode → lấy %XX; Decode → đọc tiếng Việt từ log
  4. 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