Người dùng nhấn nút quay lại thường xuyên hơn hầu hết mọi điều khiển khác trong trình duyệt. Họ mong đợi màn hình trước đó xuất hiện ngay lập tức, chính xác tại vị trí họ đã để lại. Các trình duyệt hiện đại đáp ứng kỳ vọng đó bằng bộ nhớ đệm back/forward, hay bfcache. Thay vì hủy một trang khi bạn điều hướng đi, trình duyệt sẽ đóng băng nó trong bộ nhớ. Khi bạn quay lại, nó sẽ khôi phục một bản snapshot. Trình duyệt bỏ qua việc phân tích HTML, thực thi lại JavaScript và tính toán lại bố cục. Kết quả mang lại cảm giác tức thì vì trang web chưa bao giờ thực sự "chết".

bfcache thực sự làm gì

Việc tải một trang thông thường rất tốn kém. Trình duyệt phải lấy tài nguyên, token hóa HTML, xây dựng DOM, chạy các script, giải quyết các style, thực hiện layout, vẽ pixel và tổng hợp các lớp. bfcache né tránh hầu hết những việc đó bằng cách giữ cho trang ở trạng thái đóng băng trong RAM. Đây không phải là bộ nhớ đệm đĩa (disk cache). Trang đã được kết xuất, bao gồm cả JavaScript heap, vị trí cuộn và trạng thái biểu mẫu, sẽ nằm trong bộ nhớ trong khi người dùng đọc trang tiếp theo. Khi người dùng nhấn quay lại, trình duyệt sẽ làm tan chảy bản snapshot và kích hoạt sự kiện pageshow. Trang web tiếp tục hoạt động mà không cần chạm vào mạng hoặc thực hiện lại layout từ đầu. Đối với người dùng trên các thiết bị chậm hoặc kết nối chập chờn, sự khác biệt giữa việc khôi phục từ bfcache và tải mới có thể lên tới hàng trăm mili giây hoặc hơn.

Điều gì làm gián đoạn bfcache

Một nhà phát triển gần đây đã thực hiện một thử nghiệm sạch để tìm hiểu chính xác điều gì ngăn cản bfcache. Họ đã xây dựng sáu trang đơn giản, mỗi trang kiểm tra một tác nhân gây lỗi nghi vấn, sau đó điều hướng đi và nhấn quay lại. Kết quả thu được rất rõ ràng.

Một trang cơ bản không có các header hay script bất thường đã được khôi phục thành công. Một trang có trình lắng nghe beforeunload cũng được khôi phục mà không gặp vấn đề gì. Đáng ngạc nhiên là một trang được phục vụ với Cache-Control: no-store cũng đi vào bfcache, trái ngược với các hướng dẫn trước đây. Ngay cả một bài báo blog trực tiếp, thứ có vẻ quá năng động để có thể đóng băng, cũng được khôi phục thành công.

Hai trang đã thất bại. Một trang có trình lắng nghe sự kiện unload không thể được khôi phục. Một trang có kết nối WebSocket đang mở cũng bị chặn. Hai thất bại này chỉ ra những cái bẫy thường xuyên bắt gặp các trang web thực tế đang vận hành mỗi ngày.

Cái bẫy sự kiện unload

Sự kiện unload từ lâu đã là tín hiệu mặc định để thực hiện các bước dọn dẹp vào phút chót. Các nhà phát triển sử dụng nó để gửi các beacon phân tích, dừng các bộ đếm thời gian hoặc xóa trạng thái tạm thời. Vấn đề là bfcache được xây dựng trên ý tưởng rằng trang web có thể hoạt động trở lại. Nếu trình duyệt thấy một trình lắng nghe unload, nó giả định rằng trang web muốn bị hủy hoàn toàn và từ chối đóng băng nó. Việc hàm được gắn vào có trống hay không không quan trọng. Chỉ riêng sự hiện diện của trình lắng nghe này cũng đủ để ngăn chặn việc lưu bộ nhớ đệm trên mọi trình duyệt hiện đại.

Sự thay thế là pagehide. Sự kiện này kích hoạt cả khi trang đang được đóng băng để đưa vào bfcache và khi nó thực sự bị hủy bỏ. Nếu bạn cần phân biệt giữa hai trường hợp này, thuộc tính event.persisted sẽ là true khi trang đang chuẩn bị vào bfcache. Tuy nhiên, đối với hầu hết các tác vụ dọn dẹp, pagehide đều có thể bao quát cả hai hướng. Hãy chuyển mọi logic dọn dẹp từ unload sang pagehide. Sau đó, hãy loại bỏ hoàn toàn mọi trình lắng nghe unload, bao gồm cả những trình lắng nghe nằm ẩn trong các đoạn mã phân tích của bên thứ ba hoặc các plugin cũ.

Cái bẫy kết nối đang hoạt động

Một kết nối mạng hoặc lưu trữ đang mở báo hiệu rằng trang của bạn vẫn đang thực hiện các tác vụ thực tế. Trình duyệt sẽ kiểm kê các tài nguyên đang hoạt động tại thời điểm điều hướng. Nếu nó tìm thấy một WebSocket đang mở, một kết nối WebRTC peer đang hoạt động, hoặc một kết nối IndexedDB còn sót lại, nó sẽ hủy bỏ việc đóng băng và hủy trang theo cách thông thường. Bản snapshot không thể được tin cậy khi các byte dữ liệu vẫn có thể đang được truyền đi.

Bạn nên đóng các tài nguyên này bên trong trình lắng nghe pagehide. Hãy gọi phương thức close của WebSocket. Đóng các kết nối WebRTC peer. Hủy hoặc hoàn tất bất kỳ giao dịch IndexedDB nào còn đang chờ xử lý. Nếu ứng dụng của bạn cần các kênh đó khi người dùng quay lại, hãy mở lại chúng bên trong pageshow. Mô hình "đóng khi pagehide, khôi phục khi pageshow" này giúp trang đủ điều kiện để điều hướng quay lại tức thì mà không làm mất chức năng.

Sự bất ngờ từ no-store

Trong nhiều năm, quan niệm thông thường cho rằng Cache-Control: no-store sẽ ngăn cản bfcache. Chrome đã thay đổi hành vi đó vào năm 2025. Một trang được phục vụ với no-store giờ đây có thể đi vào bfcache. Trình duyệt sẽ chỉ loại bỏ bản snapshot đã đóng băng sau đó nếu trạng thái xác thực hoặc cookie thay đổi theo cách làm mất hiệu lực trạng thái đã lưu. Nếu bạn đang sử dụng no-store như