Khác với bộ nhớ đệm trình duyệt thông thường vốn chạy thụ động theo header, service worker cho bạn viết code quyết định từng request được phục vụ thế nào. Đây là nền tảng của PWA (Progressive Web App — ứng dụng web tiến bộ chạy được offline) và là công cụ mạnh để cải thiện các chỉ số tốc độ ở lượt truy cập lặp lại.
Service worker là gì trong bức tranh cache
Service worker là một file JavaScript chạy trên một luồng riêng, tách khỏi trang web. Nó không truy cập trực tiếp DOM (Document Object Model — cây cấu trúc trang), mà ngồi giữa trang và mạng như một proxy (máy chủ trung gian) phía trình duyệt. Mỗi khi trang gửi request (yêu cầu tải tài nguyên), service worker bắt được sự kiện fetch và tự quyết định: trả về từ cache, đi ra mạng, hay kết hợp cả hai.
Bộ nhớ đệm này được quản lý qua Cache API — một kho key-value (cặp khoá–giá trị) lưu thẳng cặp Request/Response. Theo tài liệu web.dev, điểm khác biệt cốt lõi so với HTTP cache là: HTTP cache chỉ xử lý hết hạn theo thời gian (TTL — Time To Live, thời gian sống của tài nguyên), còn service worker cache cho bạn lập trình logic tuỳ ý. Đây là sự chuyển từ thụ động (trình duyệt tự lo, bạn chỉ chỉnh được header) sang chủ động (bạn viết code điều phối).
Vòng đời và Cache API cơ bản
Một service worker đi qua ba pha: install (cài đặt — thường dùng để nạp trước tài nguyên cốt lõi), activate (kích hoạt — dọn cache phiên bản cũ), và fetch (chặn request lúc trang chạy). Đoạn dưới minh hoạ nạp trước “app shell” (khung giao diện) lúc cài:
const CACHE = 'app-v1';
const ASSETS = ['/', '/styles.css', '/app.js', '/logo.svg'];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE).then((cache) => cache.addAll(ASSETS))
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
)
);
});Lưu ý self.skipWaiting() và clients.claim() nếu bạn muốn phiên bản mới giành quyền điều khiển ngay, thay vì chờ người dùng đóng hết tab cũ.

Ba chiến lược cache nên nắm
Chọn chiến lược theo bản chất tài nguyên: thứ ít đổi thì ưu tiên tốc độ, thứ hay đổi thì ưu tiên độ tươi.
| Chiến lược | Cách chạy | Hợp với loại tài nguyên |
|---|---|---|
| Cache-first | Có trong cache trả ngay, không có mới ra mạng | Logo, font, CSS/JS đã gắn hash (mã băm) vào tên file |
| Network-first | Thử mạng trước, hỏng mới dùng cache | API trả dữ liệu cần tươi (giá, tồn kho) |
| Stale-while-revalidate | Trả cache tức thì rồi âm thầm tải bản mới cho lần sau | Bảng tin, danh sách sản phẩm, ảnh thumbnail |
Stale-while-revalidate cân bằng tốt giữa tức thì và tươi: người dùng không bao giờ phải chờ mạng khi cache trúng, mà nội dung vẫn được làm mới ngầm cho lượt sau. Đây là mẫu được web.dev khuyến nghị cho tài nguyên có vòng đời ngắn.
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open(CACHE).then(async (cache) => {
const cached = await cache.match(event.request);
const network = fetch(event.request).then((res) => {
cache.put(event.request, res.clone());
return res;
});
return cached || network; // trả cache ngay, vẫn cập nhật ngầm
})
);
});Cái bẫy lớn nhất: cache cũ kẹt
Sức mạnh chủ động cũng là con dao hai lưỡi. Nếu viết cache-first cho tài nguyên không có cơ chế đổi tên theo phiên bản, người dùng có thể kẹt mãi bản cũ dù bạn đã deploy bản mới — vì service worker cứ trả file từ cache mà không bao giờ ra mạng. Vài cách phòng:
- Đặt tên cache theo phiên bản (ví dụ
app-v1→app-v2) và xoá cache cũ trong sự kiệnactivatenhư đoạn trên. - Gắn hash vào tên file build (kiểu
app.3f9a.js) để mỗi lần đổi nội dung là một URL mới, an toàn cho cache-first. - Đừng cache HTML kiểu cache-first nếu HTML hay đổi — ưu tiên network-first hoặc stale-while-revalidate cho trang.
- Canh TTL hai tầng: theo web.dev, nên để TTL của service worker dài hơn TTL của HTTP cache, tránh việc HTTP cache chặn mất request mà service worker định gửi đi.
Khi bí, mở DevTools → Application → Service Workers, tick “Update on reload” và “Bypass for network” trong lúc phát triển để khỏi tự lừa mình bằng bản cache cũ.

Liên hệ tới tốc độ và Core Web Vitals
Service worker cache không trực tiếp “ăn điểm” lab (đo phòng thí nghiệm), nhưng ở lượt truy cập lặp lại nó cắt gần như toàn bộ thời gian chờ mạng — giúp LCP (Largest Contentful Paint — thời điểm phần tử lớn nhất hiện ra) tới nhanh hơn nhiều. Theo web.dev, ngưỡng “Good” hiện hành đo ở phân vị 75 (p75) là: LCP ≤ 2,5 giây, INP (Interaction to Next Paint — độ trễ tới khung hình kế sau tương tác) ≤ 200ms, CLS (Cumulative Layout Shift — tổng dịch chuyển bố cục) ≤ 0,1. Lưu ý: vì service worker chạy luồng riêng nên nó không trực tiếp giảm INP — INP phụ thuộc vào luồng chính (main thread).
Web22 vận hành web22.dev trên LiteSpeed cộng Cloudflare làm CDN (mạng phân phối nội dung), nơi phần lớn việc cache trang đã do tầng máy chủ và CDN lo. Trong thực chiến, service worker thường đáng dùng khi bạn cần trải nghiệm app-like hoặc chạy offline, chứ với web nội dung thông thường thì cache trang ở tầng server/CDN cho hiệu quả/rủi ro tốt hơn nhiều. Nếu chưa nắm cơ chế đệm phía trình duyệt nền tảng, đọc trước bài cấu hình cache header trình duyệt để hiểu lớp HTTP cache mà service worker sẽ ngồi đè lên.
Câu hỏi thường gặp
Service worker cache có thay được HTTP cache không?
Không nên xem là thay thế. Hai lớp bổ sung nhau: HTTP cache lo phần đơn giản theo TTL, service worker lo phần logic phức tạp. Nên giữ cả hai và canh TTL để chúng không xung đột.
Có cần PWA mới dùng được service worker không?
Không. Service worker chạy được trên mọi site HTTPS hiện đại. PWA chỉ là một ứng dụng tiêu biểu của nó; bạn vẫn có thể dùng riêng cache mà không cần file manifest hay nút “cài app”.
Người dùng bị kẹt bản cũ thì xử lý sao gấp?
Tăng tên phiên bản cache, đảm bảo activate xoá cache cũ, và cân nhắc skipWaiting() để bản mới giành quyền ngay. Hướng dẫn người dùng tải lại trang một lần là cache cũ sẽ được dọn.
Nếu bạn muốn rà soát toàn bộ tầng cache và các đòn bẩy tốc độ cho website của mình, tham khảo dịch vụ tối ưu hiệu năng web của Web22.
