URL encode tiếng Việt | Space, +, %20 và bẫy double-encode khi copy từ trình duyệt
Query tiếng Việt phải percent-encode đúng một lần. Space → ưu tiên %20; + chỉ đáng tin trong form-urlencoded. Path segment và query value encode riêng (encodeURIComponent); không encode cả URL. Copy từ thanh địa chỉ Chrome thường đã encode — encode thêm = double-encode (%25). Kiểm nhanh: URL Encode/Decode.
API search q=Hà Nội trên staging trả 0 kết quả. Log gateway hiện q=H%25C3%25A0%2520N%25E1%25BB%2599i. Đó không phải bug Elasticsearch — pipeline đã encode hai lần. Freelancer copy URL từ thanh địa chỉ (đã %C3%A0) rồi encodeURIComponent lại trước khi gắn vào fetch.
Bài này dành dev / QA Việt Nam xây search, filter, deeplink có tiếng Việt, khoảng trắng, dấu + — và cần biết path khác query thế nào.
Bài viết này giúp bạn
- Hiểu percent-encoding với chữ Việt (UTF-8 →
%XX) - Phân biệt space /
+/%20 - Biết khi encode path vs query
- Nhận và sửa double-encode
- Dùng URL Encode Kawa để soi chuỗi trước khi blame backend
Percent-encoding là gì (ngắn, đủ dùng)
URL “an toàn” dùng tập ký tự hạn chế. Chữ có dấu tiếng Việt nằm ngoài tập đó → chuyển UTF-8 bytes rồi viết % + hex:
| Chuỗi | UTF-8 (hex) | Trong URL |
|---|---|---|
á | C3 A1 | %C3%A1 |
ệ | E1 BB 87 | %E1%BB%87 |
| space | 20 | %20 (hoặc + trong form) |
Ví dụ:
Hà Nội → H%C3%A0%20N%E1%BB%99i
Thử encode/decode nhanh trên công cụ URL khi log chỉ toàn %E1%BB....
Space, +, %20 — đừng đoán
| Dạng | Ngữ cảnh | Lưu ý |
|---|---|---|
| %20 | RFC 3986 percent-encoding — path và query | Lựa chọn an toàn nhất khi tự xây URL |
| + | application/x-www-form-urlencoded (form HTML cổ) | Nhiều server query hiểu + = space; path thì + thường là dấu cộng thật |
| space thô | URL bar đôi khi hiển thị đẹp | Copy/share dễ gãy; HTTP wire gần như luôn encode |
Thực tế VN:
- Search
áo sơ mi→ query nên làq=%C3%A1o%20s%C6%A1%20mi - SKU có dấu
+(ví dụtee+black) trong path: encode+thành%2Bnếu muốn literal cộng — kẻo một số parser query hiểu nhầm
Khi decode: %20 và (trong form context) + đều có thể thành space. Khi encode lại, đừng biến space thành + rồi encode %2B — dễ loạn.
Path vs query — encode khác nhau
https://shop.vn/danh-muc/đồ-nam?q=áo khoác&sort=price
\_______path________/ \_____query______/
Path segments
- Encode từng segment sau khi split
/ - Giữ
/phân tách — khôngencodeURIComponentcả path - Non-ASCII, space,
?,#trong tên category → phải encode
const segment = encodeURIComponent('đồ nam'); // %C4%91%E1%BB%93%20nam
const path = `/danh-muc/${segment}`;
Query values
- Encode từng value (và key nếu key có ký tự lạ)
- Giữ
?&=làm cú pháp
const q = encodeURIComponent('áo khoác');
const url = `https://shop.vn/search?q=${q}&sort=price`;
Sai phổ biến
// SAI — encode cả URL → phá : / ?
encodeURIComponent('https://shop.vn/search?q=áo');
// SAI — encodeURI giữ ? & nhưng không encode đủ value có & trong text
encodeURI('https://shop.vn/search?q=a&b'); // &b thành param mới!
| Hàm | Giữ nguyên | Dùng cho |
|---|---|---|
| encodeURI | : / ? # [ ] @ ! $ & ' ( ) * + , ; = | URL gần đủ; ít khi đủ an toàn cho value tự do |
| encodeURIComponent | Ít hơn — encode cả & = ? / | Từng query value / path segment |
Double-encode — bẫy copy từ trình duyệt
Chuỗi lành
Hà Nội → H%C3%A0%20N%E1%BB%99i
Encode lần 2
% → %25:
H%25C3%25A0%2520N%25E1%25BB%2599i
Backend decode một lần ra H%C3%A0%20N%E1%BB%99i (vẫn “mã”) — search không khớp “Hà Nội”.
Vì sao hay xảy ra ở VN workflow
- QA copy URL từ Chrome (thanh địa chỉ đã hiện dạng encode hoặc mix)
- Paste vào sheet / Zalo / ticket
- Dev
encodeURIComponent(copied)trước khi gọi API - Hoặc Nginx / API gateway encode thêm khi proxy
Cách phát hiện
- Decode một lần trên URL Decode
- Nếu vẫn thấy
%C3/%E1dày đặc → có thể cần decode thêm hoặc pipeline đang double - Nếu ra chữ Việt rõ ràng → dừng; đừng encode lại trước khi gửi nếu bạn đang cầm sẵn dạng wire
Quy tắc copy
| Nguồn | Coi như | Việc làm |
|---|---|---|
| Input user / DB plaintext | Chưa encode | encodeURIComponent một lần khi gắn URL |
| Thanh địa chỉ / access log | Đã encode | Decode để đọc; gửi lại nguyên hoặc decode→encode sạch một lần |
| Document khách paste “đẹp” | Không chắc | Decode thử; nếu fail hoặc vẫn % thì soi pipeline |
Case study
1. Filter tỉnh “Hồ Chí Minh”
Frontend:
params.set('city', 'Hồ Chí Minh'); // URLSearchParams encode giúp
URLSearchParams thường encode đúng một lần. Nguy hiểm khi bạn vừa params.set vừa encode tay value trước đó → double.
2. Deeplink app myapp://product/áo
Custom scheme vẫn nên encode segment. Một số WebView decode khác browser — test bằng cách decode lại trên tool trước khi ship.
3. Sign HMAC trên query
Ký trên chuỗi đã encode đúng chuẩn mà server mong đợi. Nếu client ký plaintext nhưng server verify trên bản encode (hoặc ngược lại), chữ ký lệch — trông như “auth bug” nhưng là encode mismatch.
Checklist trước khi blame backend search
- Value tiếng Việt chỉ encode một lần
- Không
encodeURIComponentcả URL - Space dùng
%20(trừ khi API bắt form+và đã document) -
+literal trong SKU →%2Bnếu cần - Copy từ browser → kiểm tra double (
%25) - So raw trên URL Encode/Decode với kỳ vọng plaintext
Tóm tắt
- Tiếng Việt = UTF-8 percent-encoding.
- Query value / path segment encode riêng; giữ cấu trúc URL.
%20> đoán+, trừ form-urlencoded rõ ràng.- Copy từ browser thường đã encode — encode thêm là double.
Khi log chỉ toàn %, đừng đọc bằng mắt: decode một lần trên tool local, rồi quyết định bước tiếp.