Devlog/2026-03-28/5 phút
v1.1

Cache-Control: Hiểu đúng để tối ưu hiệu năng hệ thống.

Cache-Control là HTTP header quyết định response được lưu và dùng lại thế nào. Đặt đúng thì nhanh hơn đáng kể, đặt sai thì sinh ra hàng loạt bug khó chịu.

#http#cache#performance#web
fig.01 — cache-layers.svgorigin200 · 412msCDNpublic · HITbrowsermax-age=3600 · freshL13600giâyCache-Control: public, max-age=0, must-revalidate

Cache là một trong những cách hiệu quả nhất để tăng hiệu năng cho web. Nhưng dùng sai thì nó gây ra rất nhiều bug khó chịu: dữ liệu cũ, UI không cập nhật, hoặc người dùng thấy nội dung khác nhau.

§1Cache-Control là gì?

Cache-Control là HTTP header chỉ định cách một response được lưu cache và khi nào được phép dùng lại. Server gửi header này cho client (browser, CDN, proxy).

Ví dụ:

http
Cache-Control: public, max-age=0, must-revalidate

§2Các directive quan trọng

2.1 public vs private

  • public cho phép mọi cache lưu lại, gồm browser, CDN và proxy.
  • private chỉ cho cache ở browser của user, không cache ở CDN.

Quy tắc chung: static assets dùng public, data cá nhân dùng private.

2.2 max-age

http
Cache-Control: max-age=3600

Response được coi là fresh trong 3600 giây (1 giờ). Trong thời gian này không cần gọi server, cache được dùng trực tiếp. Đây là cách tối ưu performance mạnh nhất.

2.3 no-cache

http
Cache-Control: no-cache

Cái tên dễ làm hiểu nhầm thành "không cache". Thực ra response vẫn được cache, chỉ là phải kiểm tra với server trước khi dùng.

2.4 no-store

http
Cache-Control: no-store

Không lưu cache ở bất kỳ đâu. Dùng cho thông tin nhạy cảm như banking, auth.

2.5 must-revalidate

http
Cache-Control: must-revalidate

Khi cache hết hạn, bắt buộc phải hỏi lại server, không được dùng cache cũ.

2.6 s-maxage và stale-while-revalidate

Khi có CDN đứng giữa, hai directive này rất hay dùng:

http
Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=30
  • s-maxage là thời gian fresh riêng cho cache dùng chung (CDN, proxy), ghi đè max-age ở đó.
  • stale-while-revalidate cho phép trả bản cũ thêm 30 giây sau khi hết hạn, trong lúc cache lấy bản mới ở background.

Với ví dụ trên, browser giữ 60 giây, CDN giữ 10 phút, và người dùng không phải chờ khi CDN làm mới.

§3Hiểu sâu qua ví dụ thực tế

http
Cache-Control: public, max-age=0, must-revalidate

Ý nghĩa:

  • public: cache được lưu ở mọi nơi
  • max-age=0: hết hạn ngay lập tức
  • must-revalidate: phải check server trước khi dùng

Gộp lại: response có thể cache, nhưng mỗi lần dùng phải hỏi server xem có bản mới không.

§4Cơ chế revalidation

Khi cache cần kiểm tra lại, nó dùng ETag hoặc Last-Modified.

ETag

Server trả:

http
ETag: "abc123"

Client gửi lại:

http
If-None-Match: "abc123"

Nếu không thay đổi:

http
HTTP 304 Not Modified

Server không cần gửi lại body, nên tiết kiệm được bandwidth.

Last-Modified

Tương tự:

http
Last-Modified: Thu, 26 Mar 2026 10:00:00 GMT

Client:

http
If-Modified-Since: ...

§5Các chiến lược cache phổ biến

5.1 Static assets (JS, CSS, images)

http
Cache-Control: public, max-age=31536000, immutable

Cache 1 năm và không bao giờ revalidate, dùng kèm filename hash (app.abc123.js). Đây là cấu hình cho performance tốt nhất.

5.2 HTML (dynamic content)

http
Cache-Control: public, max-age=0, must-revalidate

Luôn check server, nhưng vẫn tận dụng được 304. Phù hợp với SSR và content thay đổi thường xuyên.

Nếu HTML chứa dữ liệu của từng user (dashboard, trang tài khoản) thì không dùng public: CDN có thể lưu trang của người này và trả cho người khác. Dùng:

http
Cache-Control: private, no-cache

5.3 API responses

Trường hợp real-time

http
Cache-Control: no-store

Trường hợp có thể cache nhẹ

http
Cache-Control: public, max-age=60

Ví dụ list sản phẩm, hoặc data không thay đổi liên tục.

5.4 User-specific data

http
Cache-Control: private, max-age=300

Cache ở browser và không leak qua CDN.

§6Những sai lầm phổ biến

1. Dùng no-cache khi muốn "không cache"

no-cache vẫn cache. Muốn không cache thì phải dùng no-store.

2. Không version static assets

Người dùng bị kẹt với file cũ. Luôn đặt hash vào tên file:

text
app.[hash].js

3. Cache API mà không nghĩ về consistency

User thấy dữ liệu cũ, và bug kiểu này rất khó debug.

4. Không hiểu CDN cache

CDN có thể cache ngay cả khi bạn không để ý, nên cần đặt rõ public hay private.

5. Dùng public cho trang có dữ liệu user

CDN trả dashboard của user này cho user khác. Trang nào phụ thuộc vào người đang đăng nhập thì dùng private.

§7Best Practices (tóm tắt)

Static assets

http
public, max-age=31536000, immutable

HTML

http
public, max-age=0, must-revalidate

API

  • Real-time: no-store
  • Semi-static: max-age=60

Sensitive data

http
no-store

Luôn dùng versioning

Hash filename để tránh cache bug.

§8Ví dụ: site tĩnh trên Cloudflare Pages

Phần trên khá lý thuyết, nên mình thêm một ví dụ cụ thể: một site Next.js export tĩnh deploy lên Cloudflare Pages. Cloudflare Pages đọc header tuỳ chỉnh từ file _headers đặt trong thư mục output (với Next.js là public/_headers, khi build sẽ được copy ra output).

Next.js đặt mọi JS và CSS đã build trong /_next/static/, và tên file có hash. Nội dung đổi thì tên file đổi, nên các file này cache được rất lâu:

text
# public/_headers
/_next/static/*
  Cache-Control: public, max-age=31536000, immutable

HTML thì ngược lại: tên file không đổi (/index.html, /posts/abc/index.html), nên phải luôn kiểm tra lại với server. Mình không thêm rule /* cho HTML vào file này. Cloudflare Pages gộp header của mọi rule khớp với một URL, và nếu hai rule cùng đặt Cache-Control thì hai giá trị bị nối bằng dấu phẩy. Một file trong /_next/static/ khớp cả /* lẫn /_next/static/* sẽ nhận header kiểu public, max-age=0, must-revalidate, public, max-age=31536000, immutable.

Nếu thật sự cần đặt header cho mọi đường dẫn, dùng cú pháp ! để gỡ header của rule rộng hơn trước khi đặt lại:

text
/*
  Cache-Control: public, max-age=0, must-revalidate

/_next/static/*
  ! Cache-Control
  Cache-Control: public, max-age=31536000, immutable

Sau khi deploy, kiểm tra header thực tế bằng curl:

bash
curl -sI https://example.com/ | grep -i cache-control
curl -sI https://example.com/_next/static/chunks/main-abc123.js | grep -i cache-control

Thay example.com bằng domain của bạn và đường dẫn file JS bằng một file thật lấy từ tab Network của trình duyệt.

§9Kết

Cache-Control nhìn thì đơn giản nhưng ảnh hưởng rất lớn đến hệ thống. Đặt đúng thì giảm load cho server và app chạy nhanh hơn, đồng thời tránh được hàng loạt bug khó chịu kể trên.

Trạm Dev · 2026-03-28 · v1.1 · EOF
#http#cache#performance#web
$ ls ./devlog7 / 7 · bài mới nhất