- Cấu trúc URL không chỉ là một địa chỉ đơn thuần mà còn hoạt động như phương tiện lưu trữ và khôi phục trạng thái ứng dụng
- Đưa ra ví dụ như trang tải xuống PrismJS, nơi chỉ với một URL có thể tái hiện hoàn toàn cấu hình theme, ngôn ngữ và plugin
- Mỗi thành phần như path, query parameter, fragment biểu diễn nhiều loại trạng thái khác nhau như điều hướng phân cấp, lọc và điều hướng phía client
- Bộ lọc tìm kiếm, phân trang, chế độ hiển thị, khoảng ngày phù hợp để đưa vào URL, còn thông tin nhạy cảm hoặc trạng thái UI tạm thời thì không phù hợp
- Một URL được thiết kế tốt sẽ tăng khả năng chia sẻ, tính dễ dự đoán và hiệu quả cache, từ đó củng cố độ tin cậy và trải nghiệm người dùng của ứng dụng web
Tiềm năng của URL
- URL không chỉ là địa chỉ tài nguyên mà còn đóng vai trò như giao diện người dùng (UI) và container trạng thái
- Tự động bảo toàn trạng thái trong chia sẻ, bookmark, lịch sử trình duyệt và deep link
- Hoạt động như cơ chế quản lý trạng thái mặc định của web từ năm 1991
- Mỗi thành phần của URL biểu diễn một loại trạng thái khác nhau
- Path: điều hướng tài nguyên theo cấu trúc phân cấp (
/users/123/posts)
- Query: bộ lọc, tùy chọn, thiết lập (
?theme=dark&lang=en)
- Fragment: vị trí trong tài liệu hoặc định tuyến SPA (
#features, #/dashboard)
- Tính năng Text Fragments cho phép liên kết trực tiếp tới một đoạn văn bản cụ thể trong trang
Các mẫu query parameter
- Dùng delimiter để đưa nhiều giá trị vào cùng một key (
?tags=frontend,react,hooks)
- Tuần tự hóa dữ liệu lồng nhau bằng JSON hoặc Base64 (
?config=eyJyaWNrIjoicm9sbCJ9==)
- Boolean flag được biểu diễn bằng sự hiện diện của key (
?mobile)
- Bracket notation biểu diễn nhiều giá trị dưới dạng
tags[]=frontend&tags[]=react
- Được tự động nhận diện trong
qs của Node hay middleware Express, nhưng chưa được chuẩn hóa
- Điều cốt lõi là duy trì tính nhất quán
Các ví dụ biểu diễn trạng thái qua URL
- PrismJS: lưu toàn bộ cấu hình theme, ngôn ngữ và plugin trong URL hash
- GitHub: tô sáng một phạm vi dòng code cụ thể bằng
#L108-L136
- Google Maps: đưa tọa độ, mức zoom và loại bản đồ vào URL
- Figma: chia sẻ ngữ cảnh làm việc như vị trí canvas, mức zoom và phần tử được chọn qua URL
- Các trang thương mại điện tử: đưa bộ lọc, sắp xếp và khoảng giá vào URL để khôi phục trạng thái tìm kiếm
Các mẫu kỹ thuật frontend
- Các trạng thái phù hợp để đưa vào URL
- Từ khóa tìm kiếm, bộ lọc, trang/sắp xếp, chế độ hiển thị, khoảng ngày, tab đang mở, cấu hình UI, feature flag
- Các trạng thái không phù hợp với URL
- Thông tin nhạy cảm như mật khẩu hay token, trạng thái UI tạm thời, dữ liệu nhập chưa lưu, dữ liệu dung lượng lớn, trạng thái thay đổi với tần suất cao
- Tiêu chí đánh giá: khi người khác nhấp vào cùng một URL, họ có nên thấy cùng một trạng thái hay không
Cách triển khai trong JavaScript
- Có thể đọc/ghi query parameter bằng API
URLSearchParams
pushState thêm mục lịch sử mới, replaceState cập nhật mục hiện tại
- Khôi phục UI khi người dùng bấm quay lại trình duyệt bằng sự kiện
popstate
Cách triển khai trong React
- Có thể quản lý trạng thái URL gọn gàng bằng hook
useSearchParams của React Router
- Tự động đồng bộ URL và UI khi đọc/cập nhật parameter
Best practice trong quản lý trạng thái bằng URL
- Không đưa giá trị mặc định vào URL (chỉ giữ
?theme=dark, còn giá trị mặc định xử lý trong code)
- Dùng debouncing để tránh cập nhật URL quá nhiều khi đang nhập (
lodash.debounce)
- pushState vs replaceState
pushState: dùng cho trạng thái có thể quay lại như thay đổi bộ lọc hay chuyển trang
replaceState: dùng cho các chỉnh sửa chi tiết như nhập từ khóa tìm kiếm
Nhìn URL như một hợp đồng (Contract)
- Một URL được thiết kế tốt đóng vai trò như hợp đồng tường minh giữa ứng dụng và người dùng
- Làm rõ ranh giới giữa trạng thái công khai/riêng tư, client/server, chia sẻ/phiên làm việc
- URL dễ đọc giúp diễn đạt ý định và có thể được cả con người lẫn máy móc hiểu được
- Dạng
example.com/products/laptop?color=silver&sort=price truyền đạt ý nghĩa rõ ràng hơn
- Cải thiện hiệu quả cache
- Cùng một URL được xem là cùng một tài nguyên nên tăng tỷ lệ cache hit
- Có thể điều khiển các biến thể cache bằng query parameter
- Quản lý phiên bản và thử nghiệm
- Có thể phân biệt phiên bản API hay A/B test bằng
?v=2, ?beta=true, ?experiment=new-ui
Anti-pattern cần tránh
- Chỉ giữ trạng thái trong bộ nhớ của SPA, khiến mất trạng thái khi refresh
- Đưa thông tin nhạy cảm vào URL (
?password=secret123)
- Tên parameter mơ hồ (
?foo=true&bar=2 thay vì ?mobile=true&page=2)
- Mã hóa JSON phức tạp bằng Base64 làm URL dài quá mức
- Vượt quá giới hạn độ dài URL (bị ràng buộc bởi trình duyệt, server và CDN)
- Làm vô hiệu nút Back (có thể xảy ra khi lạm dụng
replaceState)
Kết luận
- Một URL tốt không chỉ trỏ tới nội dung mà còn thể hiện cuộc đối thoại giữa người dùng và ứng dụng
- URL là phương tiện quản lý trạng thái lâu đời và thanh lịch nhất, chứa đựng ý định, ngữ cảnh và khả năng chia sẻ
- Dù có nhiều công cụ quản lý trạng thái phức tạp như Redux, MobX, Zustand hay Recoil,
đừng quên chức năng nền tảng là URL mới chính là sức mạnh thực sự của web
- Một ứng dụng làm mất trạng thái khi refresh đang bỏ lỡ bản chất cốt lõi của web
2 bình luận
Ý kiến Hacker News
Khi review code, tôi cố lưu càng nhiều trạng thái (state) vào URL càng tốt
Việc sau khi làm mới trang lại bị đưa đến một vị trí hoàn toàn khác, hoặc URL đã chia sẻ lại hiển thị một màn hình không liên quan, là điều khá xúc phạm từ góc nhìn người dùng
Cách làm này khiến tốc độ phát triển chậm lại, nhưng giúp nâng cao nhận thức về UX trong nhóm và làm rõ view đang chứa bao nhiêu trạng thái
Cũng có lo ngại rằng URL sẽ trở thành một dạng API công khai và tạo ra ràng buộc, nhưng tôi nghĩ phần lớn URL chỉ được dùng trong ngắn hạn nên không phải vấn đề quá lớn
Nếu cần thì có thể xử lý bằng mã migration từ URL cũ sang URL mới khi tải trang
Tôi nghĩ dùng query parameter thay vì path sẽ tốt hơn một chút
Từ góc nhìn người dùng, từ “quay lại” gắn liền với nút của trình duyệt nên rất dễ gây nhầm lẫn
Việc trạng thái bị đặt lại khi làm mới trang còn ít khó chịu hơn. Vì người dùng vốn có nhận thức “làm mới = bắt đầu lại từ đầu”
Nếu mọi thứ đều xử lý bằng JS thì các tính năng mặc định như vậy dễ bị hỏng một cách tinh vi
Nhưng dù đã làm việc với hơn 30 UX designer, tôi chưa từng nhận được hướng dẫn nào liên quan đến URL
Đặc biệt trên mobile, rất khó đưa trang về trạng thái ban đầu nên làm mới là cách giải quyết nhanh nhất
Với infinite scroll hay UI bộ lọc phức tạp, càng nhiều trạng thái nằm trong URL thì việc reset lại càng phiền phức
Nếu UX đã gây khó chịu sẵn mà còn bắt người dùng phải dọn dẹp cả URL nữa thì đó là gấp đôi căng thẳng cho họ
Tôi cảm thấy ngay cả những người có hiểu biết số cao cũng hiểu chưa tốt về URL và DNS
Họ nên có khả năng giảm rủi ro phishing, hiểu ý nghĩa của các tham số URL (
?t=_,utm_), và loại bỏ thông tin cá nhân trước khi chia sẻHọ cũng nên biết rằng biểu tượng ổ khóa HTTPS không có nghĩa là “đáng tin cậy”
Nếu dùng URL làm state container thì cấu trúc bên trong sẽ bị lộ ra, và cần quản lý phiên bản
Cũng có thể phát sinh vấn đề về tương thích giữa các trình duyệt hoặc trong luồng xác thực
Dù vậy tôi vẫn cố gắng đưa càng nhiều trạng thái vào URL càng tốt, giống như tham số dòng lệnh
Tuy nhiên đây là một trade-off có chủ đích, chứ không phải vì thiếu hiểu biết hay thiếu kinh nghiệm
Tôi muốn giới thiệu Rison, một thư viện cũ nhưng vẫn hữu ích
Nó cho phép lưu JSON vào URL một cách gọn gàng, và cũng được dùng trong Kibana của Elastic
Ví dụ: http://example.com/service?query=q:'*',start:10,count:10
Khi hệ thống phát triển thì cấu trúc trạng thái cũng thay đổi, nên đưa trạng thái vào URL sẽ tạo ra ràng buộc cho quá trình tiến hóa
Vì URL về bản chất là một chuỗi mang tính lâu dài
Thay vào đó, tôi nghĩ nên xem URL như một dạng protocol, rồi mã hóa/giải mã trạng thái trong đó
Với những trang đơn giản thì cũng có thể đưa toàn bộ trạng thái vào URL
Nhưng với những thứ như feed thì còn tùy vào kỳ vọng của người dùng, chẳng hạn “khi làm mới có nên quay về trạng thái mới nhất không?”
Giới hạn độ dài URL khác nhau tùy theo cấu hình của trình duyệt, server, CDN và công cụ tìm kiếm, nhưng thường là dưới 2000 ký tự
Điều đáng suy nghĩ là trong giới hạn đó có thể chứa được bao nhiêu trạng thái, hay có cần cách tiếp cận khác hay không
- . _ ~), nên mật độ thông tin thực ra khá caodraw.io có thể lưu toàn bộ trạng thái vào URL để chia sẻ
Dữ liệu sơ đồ được mã hóa bằng Base64, nên chỉ với một liên kết là có thể khôi phục hoàn toàn
Tuy nhiên tôi không chắc điều này có đúng với định nghĩa “state container” hay không
Tôi dùng hash routing (#/dashboard) cho các ứng dụng self-host
Vì không cần viết lại URL phía server (.htaccess v.v.), nên dù không hoàn hảo, nó vẫn giúp giảm bớt ràng buộc của môi trường triển khai
Microsoft Teams bản mới xử lý mọi màn hình bằng một URL duy nhất nên không thể bookmark
Không thể mở trực tiếp một team hay channel cụ thể nên rất bất tiện
HATEOAS không được chú ý nhiều vì cái tên không hay, nhưng rốt cuộc nó vẫn là khái niệm nền tảng của web
Nhưng trong môi trường mà bạn kiểm soát cả server lẫn client thì nó chỉ tạo thêm độ phức tạp
Đặc biệt nếu client vẫn phải biết cấu trúc endpoint, thì đó chỉ là làm cho URL trở nên mờ đục hơn mà thôi
Tôi hay dùng tính năng tab ngủ, nhưng với các web app cố định URL và hoạt động như một khối thì khi vào chế độ ngủ, thông tin sẽ bị mất.
Thế nhưng mấy trang web kiểu đó lại cái nào cũng nặng, nên cũng không thể không cho chúng ngủ được.