Chuyện gì thực sự xảy ra ở trình duyệt sau khi nhấn F5?

Chuyện gì thực sự xảy ra ở trình duyệt sau khi nhấn F5?

Trong lập trình frontend, "làm mới trang" trông có vẻ là một thao tác đơn giản, nhưng ẩn sau đó là một luồng phối hợp phức tạp giữa giao tiếp mạng (network communication), chiến lược cache (caching strategy) và engine render (rendering engine). Đặc biệt là khi nhấn F5, nó không phải là "tải lại hoàn toàn", cũng không phải là "dùng trực tiếp cache ở local", mà là một cơ chế xác thực thông minh nằm giữa hai trạng thái này. Để thực sự làm chủ các kỹ năng tối ưu hóa hiệu năng và debug frontend, bạn bắt buộc phải hiểu sâu về quá trình này.

Bài viết này sẽ phân tích có hệ thống toàn bộ vòng đời của việc nhấn F5 (refresh thông thường), đồng thời so sánh với Ctrl+F5 (hard reload) và thao tác nhấn Enter trên thanh địa chỉ, nhằm bóc tách hành vi thực sự của trình duyệt qua các giai đoạn: cache, network, phân tích cú pháp (parsing) và render.

I. Bản chất khác biệt giữa 3 cách điều hướng (navigation)

Đầu tiên phải làm rõ: "Refresh" ≠ "Tải lại" (Reload). Trình duyệt áp dụng các chiến lược cache hoàn toàn khác nhau đối với từng thao tác của người dùng.

Thao tác của người dùng Loại hành vi Chiến lược cache Đặc điểm network request Ví dụ minh họa
F5 / Nút Refresh Refresh thông thường (Reload) Bỏ qua strong cache, kích hoạt negotiated cache (cache thương lượng) Request gửi kèm header If-Modified-Since hoặc If-None-Match "Tôi có bản nháp cũ, vui lòng xác nhận xem nó còn hiệu lực không."
Ctrl+F5 / Shift+F5 Hard Reload Bỏ qua hoàn toàn mọi loại cache Request header chứa Cache-Control: no-cache, Pragma: no-cache và không gửi header thương lượng "Bỏ qua mọi thứ tôi đang có, vui lòng cho xin bản mới nhất!"
Nhấn Enter ở thanh địa chỉ / Mở tab mới Điều hướng bình thường (Navigation) Ưu tiên dùng strong cache Nếu strong cache chưa hết hạn, sẽ không có network request nào được gửi "Tôi có sách xịn rồi, cứ thế lấy ra đọc thôi."

Lưu ý: Một số trình duyệt (như Chrome) khi mở Developer Tools (F12) sẽ mặc định chuyển hành vi của F5 thành "Disable cache". Đây là hành vi đặc thù trong chế độ debug, không thuộc tiêu chuẩn chung.

II. Khái niệm cốt lõi: Strong Cache vs Negotiated Cache

1. Strong Cache

  • Định nghĩa: Trình duyệt không cần giao tiếp với server, trả về trực tiếp tài nguyên từ cache ở local (memory cache hoặc disk cache).
  • Header điều khiển:
    • Cache-Control: max-age=3600 (HTTP/1.1, độ ưu tiên cao)
    • Expires: Wed, 26 Nov 2025 12:00:00 GMT (HTTP/1.0, dễ bị ảnh hưởng bởi sai lệch thời gian trên máy client)
  • Đặc điểm: Không có network request, thời gian phản hồi cực nhanh, nhưng có thể khiến người dùng nhìn thấy nội dung đã cũ.

2. Negotiated Cache

  • Định nghĩa: Trình duyệt gửi một "request xác thực" lên server để kiểm tra xem cache có còn hợp lệ hay không.
  • Hai cặp header kinh điển:
    1. Last-Modified / If-Modified-Since
      • Dựa trên thời gian chỉnh sửa file cuối cùng (độ chính xác ở mức giây, có thể bị đánh giá sai).
    2. ETag / If-None-Match
      • Dựa trên fingerprint (dấu vân tay) duy nhất của nội dung tài nguyên (ví dụ: mã hash). Độ chính xác cao hơn, ưu tiên cao hơn.
  • Server phản hồi:
    • Tài nguyên chưa thay đổi → Trả về 304 Not Modified (body rỗng, giúp tiết kiệm băng thông).
    • Tài nguyên đã thay đổi → Trả về 200 OK + nội dung mới + header update cache.

Chiến lược tạo ETag: Các cách implement phổ biến bao gồm mã hóa MD5 nội dung file, inode+mtime (mặc định của Nginx), hoặc custom theo business logic. ETag mang tính nhất quán mạnh giúp tránh được vấn đề vô hiệu hóa cache (cache invalidation) do "độ chính xác của timestamp không đủ".

III. Sơ đồ luồng toàn cảnh khi nhấn F5

  • Giai đoạn bắt đầu: Người dùng phát lệnh nhấn F5.
  • Giai đoạn xử lý cache: Bypass strong cache, kích hoạt quá trình network validation (xác thực mạng) của negotiated cache.
  • Server ra quyết định: Logic rẽ nhánh để kiểm tra xem tài nguyên đã thay đổi hay chưa.
  • Lấy tài nguyên: Dựa vào HTTP response 304 hoặc 200 để quyết định dùng lại cache hay tải nội dung mới.
  • Luồng render trang: Toàn bộ rendering pipeline từ bước parse HTML cho đến bước compositing (tổng hợp) cuối cùng.
  • Xử lý vòng lặp: Lặp lại quá trình xác thực cache đối với từng sub-resource.

IV. Giải thích chi tiết toàn bộ vòng đời khi refresh bằng F5

Giả sử trang hiện tại là https://example.com/index.html, người dùng nhấn F5.

Giai đoạn 1: Xác thực cache và lấy tài liệu chính (Main Document - HTML)

1. Kích hoạt lệnh refresh Trình duyệt nhận diện đây là thao tác "Reload", buộc phải bypass strong cache (bỏ qua cache mạnh), cho dù có cài đặt Cache-Control: max-age=86400 thì cũng bị vô hiệu hóa.

2. Khởi tạo request xác thực Gửi một request GET cho tài liệu HTML chính, tự động đính kèm các header thương lượng (negotiated headers) nếu trước đó đã có lưu:

3. Server ra quyết định

  • Nếu tài nguyên chưa thay đổi → Trả về 304 Not Modified, không truyền tải phần body, trình duyệt tái sử dụng (reuse) cache ở local.
  • Nếu tài nguyên đã thay đổi → Trả về 200 OK + file HTML mới + cập nhật lại ETag/Last-Modified.

Chi tiết quan trọng: Cho dù server có trả về 304, trình duyệt vẫn sẽ parse lại HTML! Bởi vì thứ được lưu trong cache chỉ là luồng byte, chứ không phải là cây DOM.

Giai đoạn 2: Parse HTML và Luồng render (Critical Rendering Path)

Cho dù HTML được lấy từ cache hay tải mới về, trình duyệt đều sẽ thực thi trọn vẹn quy trình render:

1. Dựng cây DOM

  • Parse luồng byte HTML từng dòng để tạo ra các đối tượng node.
  • Khi gặp thẻ <script>, mặc định sẽ chặn quá trình parse HTML (trừ khi có thuộc tính async hoặc defer).

2. Dựng cây CSSOM

  • Parse các inline style hoặc file CSS external (thông qua thẻ <link>).
  • CSS là tài nguyên chặn render: Không có CSSOM thì không thể dựng được Render Tree.

3. Tạo Render Tree

  • Gộp DOM và CSSOM lại, loại bỏ các node không hiển thị (ví dụ: display: none).
  • Render Tree sẽ chứa các computed style của từng node hiển thị.

4. Layout / Reflow

  • Tính toán thông tin hình học của từng node (vị trí, kích thước).
  • Điều kiện trigger: Cấu trúc DOM thay đổi, resize cửa sổ, JS đọc các thuộc tính như offset, v.v.

5. Paint / Repaint

  • Chuyển đổi Render Tree thành các pixel trên màn hình (màu sắc, viền, văn bản, v.v.).
  • Layerization : Các trình duyệt hiện đại sẽ promote các element phức tạp (như dùng transform, opacity) thành các lớp độc lập.

6. Compositing

  • Luồng tổng hợp sẽ merge các layer theo thứ tự trục Z, và cuối cùng output ra GPU để hiển thị lên màn hình.
  • Giai đoạn này có thể tạo ra các animation hiệu năng cao (ví dụ: dùng transform sẽ không trigger lại quá trình layout/paint).

Mẹo hiệu năng: Mặc dù thao tác F5 refresh có tái sử dụng một phần cache, nhưng nó vẫn phải chạy qua trọn vẹn rendering pipeline. Do đó, việc giảm độ phức tạp của DOM, tối ưu hóa CSS selector, và sử dụng hợp lý thuộc tính will-change là cực kỳ quan trọng đối với trải nghiệm refresh.

Giai đoạn 3: Xử lý cache của các sub-resource (CSS/JS/IMG)

1. Xử lý đệ quy các tài nguyên phụ thuộc Trong quá trình parse HTML, khi phát hiện các thẻ như <link>, <script>, <img>, v.v., mỗi tài nguyên này đều sẽ trigger một request negotiated cache giống hệt như cách F5 xử lý file gốc:

  • Request header cũng mang theo If-None-Match (nếu trước đó đã có ETag).
  • Server cũng sẽ trả về 304 hoặc 200.

2. Hành vi đặc biệt của JavaScript

  • Thẻ <script> truyền thống: Chặn quá trình parse HTML cho đến khi JS được tải xuống, parse và thực thi hoàn tất.
  • <script async>: Tải bất đồng bộ, tải xong sẽ thực thi ngay lập tức (điều này có thể làm lộn xộn thứ tự thực thi của các script).
  • <script defer>: Tải bất đồng bộ, nhưng trì hoãn việc thực thi cho đến tận trước khi sự kiện DOMContentLoaded fire (rất được khuyên dùng cho các file JS không quan trọng).
  • Script dạng module (type="module"): Mặc định đã mang sẵn hành vi của defer.

Xu hướng tối ưu hóa hiện đại: Việc áp dụng các kỹ thuật như rel="preload", <link rel="prefetch">, hoặc inline tài nguyên (Critical CSS/JS) có thể giúp cải thiện đáng kể tốc độ cảm nhận của người dùng sau khi nhấn F5 refresh.

V. Tổng kết so sánh: Sự khác biệt bản chất giữa F5 và các thao tác khác

Tiêu chí F5 Refresh (Refresh thông thường) Ctrl+F5 (Hard Reload) Nhấn Enter trên thanh địa chỉ
Strong Cache Bỏ qua Bỏ qua Sử dụng (Nếu còn hiệu lực)
Negotiated Cache Kích hoạt Bỏ qua Kích hoạt nếu strong cache đã hết hạn
Lượng Network Request Trung bình (Chỉ gửi request xác thực / validation) Cao (Fetch lại toàn bộ) Thấp (Có thể không có request nào)
Có parse lại HTML không? Có (Nếu hit cache thì tái sử dụng byte stream)
Ngữ cảnh sử dụng Dev debug, nội dung có thể đã được update Ép buộc lấy tài nguyên mới nhất Duyệt web hàng ngày của user

VI. Best Practices

1. Cấu hình chiến lược cache hợp lý

  • HTML: Cache-Control: no-cache (Ép buộc dùng negotiated cache, đảm bảo luôn lấy nội dung mới nhất).
  • Tài nguyên tĩnh (JS/CSS/IMG): Cache-Control: public, max-age=31536000 kết hợp với cách đặt tên file theo mã hash nội dung (ví dụ: app.a1b2c3.js), giúp đạt được mục tiêu "cache vĩnh viễn + update ngay lập tức".

2. Tận dụng ETag để tăng độ chính xác của cache

  • Tránh việc chỉ phụ thuộc vào Last-Modified, đặc biệt là trong các hệ thống CI/CD có tần suất build liên tục (thời gian sửa file thay đổi nhưng nội dung chưa chắc đã đổi).
  1. Giám sát tỉ lệ response 304
  • Tỉ lệ 304 cao cho thấy chiến lược cache đang hoạt động hiệu quả; nếu xuất hiện quá nhiều response 200, bạn cần kiểm tra xem tài nguyên có đang bị update một cách không cần thiết hay không.

4. Tránh lỗi "Nhấn F5 nhưng không update"

  • Nếu user report lỗi "đã sửa code nhưng giao diện không đổi", khả năng rất cao là file HTML đang bị dính strong cache. Bắt buộc phải đảm bảo file HTML không được set cache dài hạn.

VII. Lời kết

Thao tác nhấn F5 refresh hoàn toàn không hề đơn giản chỉ là "tải lại" — nó là một sự đánh đổi cực kỳ tinh tế mà trình duyệt phải thực hiện để cân bằng giữa trải nghiệm người dùng, hiệu suất mạng và tính nhất quán của dữ liệu.

Việc hiểu rõ cơ chế caching và luồng render (rendering pipeline) nằm bên dưới không chỉ giúp chúng ta viết ra những đoạn code frontend tối ưu hơn, mà còn giúp khoanh vùng chính xác các ca bệnh kinh điển như "tại sao code mới deploy lại không nhận?".

Khi đã nắm vững những nguyên lý cốt lõi này, bạn hoàn toàn có thể bơi lội dễ dàng trong các task tối ưu hiệu năng, thiết kế cache và troubleshoot/debug, thực sự trở thành một kỹ sư frontend "không chỉ biết cách làm, mà còn hiểu thấu đáo tại sao lại làm như vậy"

Ngoài ra nếu muốn đào sâu hơn nữa, học các kiến thức chuyên sâu thực tiễn hơn từ giảng viên là chuyên gia có hơn 10 năm kinh nghiệm frontend và từng là kỹ sư tại Shopee Singapo thì bạn có thể tham khảo khoá học React Nâng Cao bên dưới của Sydexa nha!

React Nâng Cao - Chuyên Sâu với Tối Ưu Hiệu Năng
Khóa học React từ cơ bản đến nâng cao với chuyên gia. Trở thành Senior React Developer với Sydexa.

Read more