HTTP Caching: Tại Sao Browser Của Bạn Thông Minh Hơn Bạn Nghĩ
Browser vừa tải xong trang này. Nếu bạn refresh ngay bây giờ, hầu hết các file sẽ không được download lại — chúng được serve từ disk hoặc memory trong vài millisecond. Đó là HTTP caching. Hiểu cách nó quyết định lưu gì, giữ bao lâu, và khi nào hỏi lại server — là một trong những kỹ năng highest-leverage trong web performance.
Post này cover hai cơ chế chính: Cache-Control và ETag/Last-Modified, khi nào dùng cái nào, và những mistake hay gặp nhất.
Hai Cơ Chế, Một Mục Tiêu
HTTP caching hoạt động theo hai hướng khác nhau:
| Cơ chế | Câu hỏi nó trả lời | Có round trip không? |
|---|---|---|
| Cache-Control | "Tôi có cần hỏi server không?" | Không — serve thẳng từ cache |
| ETag / Last-Modified | "Nội dung có thay đổi kể từ lần cuối fetch?" | Có — nhưng response body có thể rỗng (304) |
Phân biệt được hai cái này là chìa khóa để dùng caching đúng.
Cache-Control: Lever Chính
Cache-Control là response header nói cho browser (và CDN nếu có) biết cách xử lý bản cache hiện tại.
Các directive quan trọng nhất:
Cache-Control: max-age=31536000, immutable
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: max-age=0, must-revalidate
immutable nói với browser "URL này sẽ không bao giờ thay đổi nội dung." Kết hợp với content hash trong tên file (main.a3f92b.js), bạn có thể cache vô hạn mà không lo stale asset.Ý nghĩa thực tế của từng directive:
max-age=N
Cache response này trong N giây. Trong khoảng thời gian đó, browser serve thẳng mà không hỏi server. Với static asset có hash trong tên file, set 31536000 (1 năm) là an toàn.
no-cache
Dù tên là vậy, directive này không tắt caching. Nó có nghĩa là "luôn revalidate trước khi serve." Browser vẫn cache response nhưng check với server mỗi request. Đây là lựa chọn đúng cho HTML page.
no-store
Cái này mới thực sự tắt caching. Không lưu gì cả. Dùng cho sensitive data (user dashboard, banking data) — không phải default cho mọi route.
must-revalidate
Khi max-age hết hạn, browser phải liên hệ server trước khi serve bản cũ. Tránh serve nội dung lỗi thời ngay cả khi network chập chờn.
ETag: Conditional Request
Khi max-age hết hạn (hoặc bạn set no-cache), browser gửi conditional request để check xem có gì thay đổi không:
sequenceDiagram
Browser->>Server: GET /api/data<br/>If-None-Match: "abc123"
alt Nội dung không đổi
Server-->>Browser: 304 Not Modified<br/>(không có body)
else Nội dung đã thay đổi
Server-->>Browser: 200 OK<br/>ETag: "xyz789"<br/>(full response body)
end
ETag là hash của nội dung response. Nếu khớp với cái browser gửi lên, server trả về 304 Not Modified — chỉ có headers, không có body. Browser dùng lại bản cache đã có.
Last-Modified hoạt động tương tự nhưng dùng timestamp thay vì hash. ETag đáng tin hơn vì timestamp chỉ có độ chính xác 1 giây và có thể khác nhau giữa các server trong một cluster.Chiến Lược Đúng Cho Từng Loại Asset
Không phải asset nào cũng cần cùng một cache policy. Đây là decision matrix:
| Loại asset | Cache-Control nên dùng | Lý do |
|---|---|---|
| JS/CSS có hash trong tên file | max-age=31536000, immutable | Hash đổi = URL mới = safe mãi mãi |
| HTML page | no-cache | Luôn fresh, validate bằng ETag |
| API response (public) | max-age=60, stale-while-revalidate=300 | Serve nhanh, refresh ngầm |
| API response (private) | no-store | Không cache — bao giờ hết |
| Font tự host | max-age=31536000, immutable | Font hiếm khi thay đổi |
| Ảnh từ CMS | max-age=86400 + ETag | Freshness theo ngày + conditional |
Pattern stale-while-revalidate
Một trong những directive hữu ích nhất nhưng hay bị hiểu sai:
Cache-Control: max-age=60, stale-while-revalidate=300
Cụ thể nó làm gì:
- 60 giây đầu: serve thẳng từ cache, không có network
- Từ 60 đến 360 giây: serve bản cũ ngay lập tức, đồng thời fetch bản mới ở background
- Sau 360 giây: bắt buộc phải revalidate trước khi serve
Kết quả là UI không bao giờ block vì cache miss trong vòng 6 phút, nhưng vẫn đủ fresh.
stale-while-revalidate không phù hợp cho data mà stale = sai (ví dụ: số lượng tồn kho, auth state). Dùng cho nội dung mà việc trễ 5 phút là chấp nhận được.Những Lỗi Hay Gặp
Cache-Control: no-cache
Cache-Control: max-age=31536000, immutable
Cache-Control: no-store
Cache-Control: max-age=60, stale-while-revalidate=300
Set no-cache trên file JS có hash là lỗi phổ biến nhất. File đó đã có cache-busting built-in trong tên file rồi — thêm no-cache chỉ tạo thêm round trip mỗi lần load trang mà không có lợi gì.
Key Takeaways
Cache-Control: max-age + immutablelà lựa chọn đúng cho hashed static asset. Set một lần, không cần nghĩ lại.no-cachecó nghĩa là "luôn revalidate", không phải "không cache". Dùng cho HTML.no-storechỉ dành cho sensitive data — không phải default cho mọi API route.ETag/304 Not Modifiedgiảm một nửa bandwidth cho resource hay thay đổi.stale-while-revalidatelà pattern background-refresh — tốt cho public API data.