jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Web Performance · Part 4 — Critical Rendering Path & Loading

The journey from HTML to pixels: DOM, CSSOM, render tree, layout, paint. Why CSS and JS block rendering, and resource hints (preload, preconnect, prefetch, defer, async) to control load order.

Để tối ưu tốc độ tải, bạn phải hiểu trình duyệt làm gì từ lúc nhận HTML tới lúc vẽ pixel đầu tiên. Hành trình đó gọi là Critical Rendering Path (CRP). Hiểu nó giải thích vì sao một thẻ <script> đặt sai chỗ có thể làm trắng màn hình, và vì sao CSS lại “chặn render”.


1. Từ HTML tới pixel: các bước của CRP

HTML  ──parse──▶  DOM ─┐
                       ├─▶ Render Tree ─▶ Layout ─▶ Paint ─▶ Composite ─▶ pixel
CSS   ──parse──▶ CSSOM ─┘
  1. Trình duyệt parse HTML thành DOM.
  2. Parse CSS thành CSSOM và tính style cho các node cần render.
  3. Từ DOM + style, tạo render tree (node display: none không có box render).
  4. Layout tính vị trí và kích thước các box.
  5. Paint ghi các lệnh vẽ cho text, màu, ảnh và hiệu ứng.
  6. Compositor ghép các layer/tile thành khung hình trên màn hình.

Mục tiêu tối ưu tải: rút ngắn thời gian tới khi render tree sẵn sàng và paint lần đầu xảy ra (FCP/LCP).


2. CSS chặn render

Các stylesheet áp dụng cho media hiện tại và được phát hiện sớm thường chặn render: trình duyệt chờ tải + parse chúng để tránh vẽ nội dung chưa style (FOUC). Không phải mọi file CSS đều chặn mọi viewport; stylesheet có media query không khớp có thể được tải với ưu tiên thấp hơn mà không chặn paint hiện tại.

Hệ quả thực tế:

  • Giảm CSS chặn: chỉ nạp CSS cần cho phần đầu trang (above-the-fold); phần còn lại nạp sau.
  • Inline critical CSS: nhúng thẳng CSS tối thiểu cho above-the-fold vào <head> để paint ngay, không chờ request CSS ngoài.
  • CSS có điều kiện không chặn: dùng media để CSS chỉ chặn khi áp dụng.
<!-- These styles only block rendering when their media query matches. -->
<link rel="stylesheet" href="print.css" media="print" />
<link rel="stylesheet" href="wide.css" media="(min-width: 80rem)" />

Đừng dùng mẹo đổi media bằng onload như một mặc định: nó có thể gây FOUC/CLS và còn phụ thuộc JavaScript. Với CSS thật sự không critical, ưu tiên tách theo route/component hoặc chỉ chèn stylesheet khi tính năng cần nó.


3. JavaScript chặn parse — và async/defer

Mặc định, gặp <script> (không thuộc tính), trình duyệt dừng parse HTML, tải script, chạy nó, rồi mới parse tiếp. Vì script có thể document.write hay sửa DOM. Một script lớn ở <head> = trắng màn hình tới khi nó xong.

Hai thuộc tính giải quyết:

<!-- defer: download in parallel, RUN after HTML parse completes, preserves order. Default choice. -->
<script src="app.js" defer></script>

<!-- async: download in parallel, RUN as soon as loaded (interrupts parse), NO order guarantee. -->
<script src="analytics.js" async></script>
Chặn parse?Thời điểm chạyGiữ thứ tự?Dùng cho
(none)ngay lập tứchiếm khi nên dùng
deferKhôngsau khi DOM xongscript ứng dụng
asyncChỉ khi executengay khi tải xongkhôngscript độc lập (analytics)
type="module"Khôngsau khi parse (mặc định defer)theo module graphES modules

Quy tắc đơn giản: script ứng dụng dùng defer (hoặc type="module", vốn defer sẵn); script bên thứ ba độc lập dùng async. Tránh script đồng bộ trong <head>.


4. Resource Hints: kiểm soát thứ tự & thời điểm tải

Trình duyệt tự đoán thứ tự tải, nhưng bạn biết rõ hơn cái gì quan trọng. Resource hints cho phép “nói trước” với trình duyệt.

bắt tay sớm với origin khác

Kết nối tới một domain (DNS + TCP + TLS) tốn thời gian. preconnect làm bước này sớm cho các origin quan trọng (CDN font, API):

<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="dns-prefetch" href="https://cdn.example.com" /> <!-- lighter — DNS only -->

tải sớm tài nguyên quan trọng bị “giấu”

Một số tài nguyên quan trọng bị trình duyệt phát hiện muộn (font khai trong CSS, ảnh hero trong CSS background, module import sâu). preload giúp phát hiện và bắt đầu tải sớm; nó không mặc định biến mọi request thành ưu tiên cao, nên có thể kết hợp fetchpriority khi thật sự cần:

<!-- preload above-the-fold font to avoid waiting -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />
<!-- preload LCP image so it loads immediately, not after CSS/JS -->
<link rel="preload" href="/hero.avif" as="image" fetchpriority="high" />

Cẩn thận: preload quá nhiều thứ phản tác dụng — nó cạnh tranh băng thông với chính tài nguyên đang cần và có thể tải cả tài nguyên cuối cùng không dùng. Chỉ preload tài nguyên critical bị phát hiện muộn. Nếu ảnh LCP đã có src/srcset ngay trong HTML, thường bắt đầu với fetchpriority="high" và chỉ thêm preload khi waterfall cho thấy vẫn phát hiện trễ.

tải trước cho điều hướng tương lai

prefetch tải tài nguyên cho trang có thể tới tiếp theo, với ưu tiên thấp (lúc rảnh):

<!-- user likely goes to /dashboard next → prefetch when idle -->
<link rel="prefetch" href="/dashboard.js" as="script" />

tinh chỉnh ưu tiên (hint, không phải mệnh lệnh)

<img src="/hero.avif" fetchpriority="high" width="1200" height="600" alt="..." />
<img src="/below-fold.avif" loading="lazy" width="800" height="600" alt="..." />

5. Thứ tự tối ưu CRP (tổng hợp)

Gom lại thành quy trình:

  1. Giảm số byte chặn render: inline critical CSS, defer JS, hoãn CSS không quan trọng.
  2. Rút ngắn chuỗi request quan trọng: ít vòng request nối tiếp nhau (request A xong mới biết cần B).
  3. Ưu tiên đúng tài nguyên: preconnect một vài origin quan trọng, preload tài nguyên critical bị phát hiện muộn, fetchpriority để gợi ý thứ tự tương đối.
  4. Hoãn phần không cần ngay: lazy-load ảnh dưới màn hình, code-split JS (Phần 8), prefetch điều hướng tương lai.

Dùng waterfall của WebPageTest (Phần 3) để thấy chuỗi request quan trọng và phát hiện request nào đến muộn.


Tóm tắt

  • CRP: HTML→DOM, CSS→CSSOM, gộp thành render tree → layout → paint → composite. Tối ưu là rút ngắn tới paint đầu.
  • CSS áp dụng cho viewport có thể chặn render → giảm/inline critical CSS có kiểm soát, tách CSS theo route/media/tính năng.
  • JS chặn parse → dùng defer cho script ứng dụng, async cho script độc lập; tránh script đồng bộ trong <head>.
  • Resource hints: preconnect (bắt tay sớm), preload (phát hiện sớm tài nguyên quan trọng bị giấu — đừng lạm dụng), prefetch (điều hướng tương lai), fetchpriority (gợi ý ưu tiên). Phần 14 sẽ đào sâu modulepreload, Early Hints và Speculation Rules.

Phần tiếp theo: Tối ưu LCP — server response, ảnh hero, font, và resource hints áp dụng cụ thể.