Cache-Control là một tiêu đề HTTP xác định các quy tắc lưu trữ đệm tài nguyên trên phía máy khách, máy chủ proxy và CDN bằng một tập hợp các chỉ thị. Không giống như tiêu đề Expires cũ, Cache-Control hỗ trợ hàng chục kết hợp: max-age đặt thời gian sống tính bằng giây, private và public kiểm soát khả năng sử dụng bộ nhớ đệm, no-cache và no-store — xác minh bắt buộc. Theo Google Web Dev (2025), cấu hình Cache-Control đúng cách có thể giảm thời gian tải trang 50-80% cho các lần truy cập lặp lại. Điều này làm cho tiêu đề này trở nên quan trọng đối với hiệu suất của ứng dụng web và di động.
Ý chính
Cache-Control là một tiêu đề HTTP, được chuẩn hóa trong HTTP/1.1 (RFC 7234), cho phép máy chủ chỉ định cách thức và thời gian máy khách, proxy và CDN có thể lưu trữ đệm phản hồi. Không giống Expires (HTTP/1.0), Cache-Control sử dụng các chỉ thị — các lệnh văn bản kết hợp bằng dấu phẩy: Cache-Control: public, max-age=3600, must-revalidate. Tiêu đề này cung cấp khả năng kiểm soát chi tiết đối với từng mắt xích trong chuỗi bộ nhớ đệm.
Bộ nhớ đệm là một trong những cơ chế cơ bản của hiệu suất ứng dụng web và di động. Nếu không có nó, mọi yêu cầu của người dùng sẽ đi trực tiếp đến máy chủ, gây ra tải quá mức và độ trễ. Cache-Control xác định ba cấp độ bộ nhớ đệm: trình duyệt/ứng dụng (bộ nhớ đệm riêng), máy chủ proxy (bộ nhớ đệm dùng chung), và CDN (bộ nhớ đệm phân tán). Mỗi cấp độ diễn giải các chỉ thị khác nhau.
Cấu hình Cache-Control không đúng là một trong những nguyên nhân phổ biến nhất gây ra sự cố hiệu suất. Bộ nhớ đệm quá tích cực khiến người dùng thấy dữ liệu cũ. Bộ nhớ đệm quá yếu dẫn đến các yêu cầu quá mức đến máy chủ và tải chậm. Theo Akamai (2025), tối ưu hóa Cache-Control cho nội dung tĩnh giảm tải máy chủ 70-90% và cải thiện thời gian tải 40-60% cho người dùng di động.
Cache-Control xuất hiện trong HTTP/1.1 (RFC 2616, 1999) để thay thế Expires. Expires có một vấn đề cơ bản: nó sử dụng ngày tuyệt đối phụ thuộc vào múi giờ của máy chủ và máy khách. Cache-Control đã giải quyết vấn đề này bằng cách chuyển sang thời gian tương đối (max-age tính bằng giây kể từ thời điểm nhận được phản hồi). Sau đó, trong RFC 7234 (2014), các chỉ thị mới đã được thêm vào: immutable cho tài sản tĩnh, stale-while-revalidate và stale-if-error cho xác minh trễ.
Cache-Control bao gồm hơn 10 chỉ thị được chia thành ba nhóm: chỉ thị yêu cầu (máy khách → máy chủ), chỉ thị phản hồi (máy chủ → máy khách) và phần mở rộng. Trong thực tế, phát triển di động sử dụng 6-7 chỉ thị phản hồi chính bao phủ 95% các kịch bản bộ nhớ đệm. Hãy xem xét từng chỉ thị với các ví dụ và khuyến nghị.
| Chỉ thị | Ý nghĩa | Ví dụ |
|---|---|---|
| max-age | Thời gian sống tính bằng giây kể từ phản hồi | max-age=3600 — 1 giờ |
| s-maxage | max-age cho bộ nhớ đệm dùng chung (proxy, CDN) | s-maxage=86400 — 1 ngày cho CDN |
| public | Cho phép mọi người lưu trữ đệm (bao gồm proxy) | public, max-age=3600 |
| private | Chỉ cho phép bộ nhớ đệm trên trình duyệt/ứng dụng | private, max-age=600 |
| no-cache | Không sử dụng nếu không xác minh (cần 304) | no-cache |
| no-store | Cấm hoàn toàn lưu trữ đệm | no-store |
| must-revalidate | Sau max-age, phải xác minh lại với nguồn gốc | max-age=3600, must-revalidate |
| immutable | Tài nguyên sẽ không thay đổi (cho tài sản tĩnh có phiên bản) | max-age=31536000, immutable |
max-age là chỉ thị quan trọng nhất. Nó cấm máy khách gửi yêu cầu đến máy chủ trong thời gian đã chỉ định. Đối với tài sản tĩnh (CSS, JS, hình ảnh), max-age thường được đặt từ 1 ngày đến 1 năm. Đối với phản hồi API — từ 0 giây (dữ liệu luôn mới) đến 5-10 phút (dữ liệu tham khảo). s-maxage cho phép đặt thời gian sống khác nhau cho CDN và trình duyệt: CDN lưu trữ bản sao trong 1 ngày, trình duyệt trong 1 giờ.
Hai chỉ thị này thường bị nhầm lẫn. no-cache không cấm bộ nhớ đệm — nó yêu cầu xác minh bản sao đã lưu trữ đệm ở mỗi lần sử dụng thông qua yêu cầu có điều kiện (If-Modified-Since hoặc If-None-Match). Nếu máy chủ phản hồi 304 — máy khách sử dụng bộ nhớ đệm. Nếu 200 — nó cập nhật. no-store, mặt khác, hoàn toàn cấm lưu phản hồi trong bất kỳ bộ nhớ đệm nào, bao gồm cả đĩa và bộ nhớ. Chỉ sử dụng no-store cho dữ liệu nhạy cảm — token, dữ liệu thanh toán, tài liệu cá nhân.
Tiêu đề Expires (HTTP/1.0) cũng chỉ định thời gian sống của tài nguyên nhưng sử dụng ngày tuyệt đối: Expires: Thu, 03 Jul 2026 12:00:00 GMT. Cache-Control max-age sử dụng thời gian tương đối kể từ thời điểm phản hồi. Sự khác biệt này rất quan trọng đối với các hệ thống phân tán: nếu máy chủ và máy khách ở các múi giờ khác nhau, Expires có thể bị hiểu sai. Cache-Control không có vấn đề này — 3600 giây luôn là 3600 giây.
Khi cả hai tiêu đề đều có mặt, Cache-Control được ưu tiên hơn Expires. Điều này được xác định trong RFC 7234: “Nếu phản hồi bao gồm trường Cache-Control với chỉ thị max-age, người nhận PHẢI bỏ qua trường Expires.” Trong thực tế, khuyên không nên trả về Expires cho các máy khách hiện đại, vì Cache-Control bao phủ tất cả các kịch bản của Expires. Tuy nhiên, để tương thích ngược với các proxy và trình duyệt cũ, cả hai tiêu đề có thể được trả về.
Expires tồn tại chủ yếu cho nội dung tĩnh trên Nginx và Apache — các máy chủ này tự động thêm cả hai tiêu đề. Nếu dự án của bạn gặp Expires mà không có Cache-Control, hãy thay thế nó bằng Cache-Control với max-age: độ chính xác kiểm soát bộ nhớ đệm được cải thiện và sự phụ thuộc vào múi giờ được loại bỏ. Để di chuyển, chỉ cần cấu hình máy chủ để thêm Cache-Control thay vì Expires.
# Nginx: Cache-Control cho tệp tĩnh
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable, max-age=2592000";
}
# Các chính sách khác nhau cho các loại nội dung khác nhau
location /api/config {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}
location /api/static-data {
expires 5m;
add_header Cache-Control "public, max-age=300";
}
Trong cấu hình Nginx, các tệp tĩnh (CSS, JS, hình ảnh) được đặt Cache-Control trong 30 ngày với thuộc tính immutable — thuộc tính này thông báo cho trình duyệt rằng tài nguyên không bao giờ thay đổi ở URL này (quản lý phiên bản thông qua hash trong tên tệp). Các điểm cuối API sử dụng no-cache cho dữ liệu động và public với max-age ngắn cho dữ liệu tham khảo — danh sách thường xuyên được yêu cầu và hiếm khi thay đổi.
Trong các ứng dụng di động, Cache-Control đóng một vai trò đặc biệt do các hạn chế của mạng di động: độ trễ cao, kết nối không ổn định, giới hạn lưu lượng. Bộ nhớ đệm thích hợp cho phép hiển thị dữ liệu cho người dùng ngay lập tức, ngay cả khi ngoại tuyến, và cập nhật chúng trong nền. OkHttp trên Android và URLSession trên iOS có hệ thống bộ nhớ đệm tích hợp tôn trọng Cache-Control.
OkHttp sử dụng CacheInterceptor, đọc Cache-Control từ phản hồi và tự động quản lý bộ nhớ đệm. Nếu máy chủ trả về Cache-Control: max-age=3600, OkHttp sẽ không gửi yêu cầu đến máy chủ trong một giờ. Sau khi max-age hết hạn, OkHttp gửi một yêu cầu có điều kiện với If-Modified-Since và If-None-Match. Cấu hình bộ nhớ đệm trong OkHttp: OkHttpClient.Builder().cache(Cache(directory, maxSize)).
fun createCachedClient(cacheDir: File): OkHttpClient {
return OkHttpClient.Builder()
.cache(Cache(cacheDir, 10L * 1024 * 1024))
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.header("Cache-Control",
"public, max-age=300")
.removeHeader("Pragma")
.build()
}
.build()
}
Mã tạo một OkHttpClient với bộ nhớ đệm 10 MB và ghi đè Cache-Control thông qua NetworkInterceptor. Nếu máy chủ không trả về Cache-Control hoặc sử dụng Expires, bộ chặn sẽ thêm public, max-age=300 (5 phút). Bộ chặn loại bỏ tiêu đề Pragma cũ (HTTP/1.0) để tương thích. Bộ nhớ đệm trên iOS hoạt động tương tự thông qua URLCache.shared với các cài đặt memoryCapacity và diskCapacity.
Chỉ thị stale-while-revalidate cho phép hiển thị bộ nhớ đệm cũ cho người dùng trong khi ứng dụng tải dữ liệu mới trong nền. Điều này mang lại hiệu ứng phản hồi tức thì: người dùng thấy nội dung ngay lập tức và sau một giây, nó được cập nhật lên phiên bản hiện tại. Được OkHttp hỗ trợ từ phiên bản 3.10 và URLCache trên iOS 14+. Ví dụ: Cache-Control: max-age=3600, stale-while-revalidate=300 — 1 giờ bộ nhớ đệm mới, sau đó 5 phút hiển thị dữ liệu cũ với làm mới nền.
Các loại tài nguyên khác nhau yêu cầu các chiến lược bộ nhớ đệm khác nhau. Hãy xem các cấu hình tối ưu cho các kịch bản điển hình trong phát triển di động. Đối với nội dung tĩnh có hash trong tên tệp (bundle.abc123.js), bạn có thể đặt max-age lên đến 1 năm với immutable. Đối với danh sách API hiếm khi được cập nhật (thư mục, danh mục) — max-age từ 5 phút đến 1 giờ với stale-while-revalidate.
| Loại tài nguyên | Cache-Control | Giải thích |
|---|---|---|
| Tài sản tĩnh có phiên bản | public, max-age=31536000, immutable | 1 năm, tệp không thay đổi (hash trong URL) |
| Tài sản tĩnh không có phiên bản | public, max-age=86400, must-revalidate | 1 ngày với xác minh lại bắt buộc sau đó |
| API: dữ liệu tham khảo | public, max-age=600, stale-while-revalidate=60 | 10 phút bộ nhớ đệm + 1 phút cũ |
| API: dữ liệu người dùng | private, max-age=60 | 1 phút, chỉ cho một người dùng cụ thể |
| API: dữ liệu nhạy cảm | no-store | Cấm hoàn toàn bộ nhớ đệm |
| Trang HTML | no-cache, must-revalidate | Xác minh ở mỗi yêu cầu, 304 nếu không thay đổi |
Điều quan trọng là phải nhớ về bảo mật: đối với các phản hồi chứa dữ liệu cá nhân của người dùng, luôn đặt private. Nếu không có chỉ thị này, một proxy công cộng (ví dụ: doanh nghiệp) có thể lưu trữ đệm phản hồi và chuyển nó cho người dùng khác. Đối với token xác thực và thông tin thanh toán, hãy sử dụng no-store — ngay cả bộ nhớ đệm riêng cũng không nên lưu dữ liệu này trên đĩa.
Để xác minh tính đúng đắn của Cache-Control, hãy sử dụng tiêu đề Age (bộ nhớ đệm đã được lưu trữ bao nhiêu giây) và X-Cache (hit/miss trên CDN). Trong trình duyệt — tab Network, cột Size hiển thị “from disk cache” hoặc “304 Not Modified”. Nếu một tài nguyên đáng lẽ phải được lưu trữ đệm nhưng lại tải mỗi lần, hãy kiểm tra xem máy chủ có thêm Cache-Control: no-cache hoặc Pragma: no-cache cùng với các chỉ thị của bạn không.
Câu hỏi thường gặp
max-age áp dụng cho tất cả bộ nhớ đệm (bao gồm trình duyệt), s-maxage chỉ áp dụng cho bộ nhớ đệm dùng chung (proxy, CDN). Nếu s-maxage được chỉ định, CDN sẽ bỏ qua max-age và sử dụng s-maxage. Điều này cho phép đặt thời gian sống khác nhau cho trình duyệt và CDN.
Không, sau khi gửi phản hồi với max-age, máy khách sẽ không gửi yêu cầu cho đến khi hết thời gian. Để vô hiệu hóa bộ nhớ đệm ngay lập tức, bạn cần thay đổi URL của tài nguyên (thêm phiên bản/hash) và gửi thông báo push hoặc tin nhắn WebSocket để đặt lại bắt buộc.
Chỉ thị immutable (RFC 8246) thông báo cho trình duyệt rằng tài nguyên sẽ không bao giờ thay đổi ở URL này. Trình duyệt thậm chí không cố gửi yêu cầu có điều kiện khi làm mới trang — nó sử dụng bộ nhớ đệm cho đến khi max-age hết hạn. Chỉ hoạt động với các tệp có phiên bản.
Googlebot tính đến Cache-Control: bộ nhớ đệm dài giúp tăng tốc độ thu thập dữ liệu lặp lại. noindex với bộ nhớ đệm nhanh được chấp nhận. no-store có thể làm chậm quá trình lập chỉ mục vì Googlebot sẽ tải trang từ đầu mỗi lần. max-age quá ngắn làm tăng tải máy chủ trong quá trình thu thập dữ liệu.
Thông qua helmet hoặc middleware: res.set('Cache-Control', 'public, max-age=3600'). Đối với tệp tĩnh, hãy sử dụng express.static với tham số maxAge: express.static('public', {maxAge: '1y'}). Đối với các tuyến đường động — riêng lẻ trong từng trình xử lý.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm