jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Web Performance · Part 5 — Optimizing LCP

Break LCP into TTFB, resource load delay, resource load duration, and element render delay; then fix discovery, priority, bytes, rendering, or delivery at the actual bottleneck.

LCP (≤ 2.5s) đo khi phần tử nội dung lớn nhất trong viewport được render. Để tối ưu, ta phải chia LCP thành các thành phần thời gian và tấn công cái lớn nhất. Phần này theo đúng khung phân tích của Google.


1. LCP gồm bốn thành phần thời gian

LCP = TTFB + Resource load delay + Resource load duration + Element render delay
       │        │                    │                    │
       │        │                    │                    └─ resource ready → element rendered
       │        │                    └─ time to load the LCP element (image)
       │        └─ time from TTFB until LCP element STARTS loading
       └─ navigation starts → first byte of HTML arrives

Profiling LCP nghĩa là tìm thành phần nào chiếm nhiều nhất, rồi sửa đúng cái đó. Hai thành phần có chữ “delay” nên tiến gần 0; TTFB và thời gian tải resource thì không thể bằng 0 nhưng vẫn cần kiểm soát.


2. Giảm TTFB (server response)

TTFB là thời gian từ lúc bắt đầu navigation tới khi byte đầu của HTML tới trình duyệt. Nó gồm redirect, thiết lập kết nối, độ trễ mạng và thời gian xử lý server — không chỉ riêng backend. TTFB cao kéo mọi thứ sau nó trễ theo. Cách giảm:

  • Bỏ redirect chain, dùng lại kết nối và đặt origin/CDN gần người dùng.
  • CDN/edge cache: phục vụ HTML cacheable từ edge gần người dùng (Phần 11).
  • Cache phía server: cache HTML render (SSG/ISR), cache truy vấn DB.
  • Giảm việc trên đường tới HTML: tránh truy vấn chậm/đồng bộ trước khi trả HTML.
  • Streaming SSR: gửi phần đầu HTML ngay trong khi phần sau còn render.

Với trang tĩnh hoặc SSG, TTFB thường rất thấp vì HTML đã dựng sẵn — đây là lợi thế hiệu năng lớn của static-first (như chính blog này).


3. Loại bỏ resource load delay (phát hiện phần tử LCP sớm)

Đây là lỗi phổ biến và dễ sửa nhất: tài nguyên LCP (thường là ảnh hero) bị phát hiện muộn vì nằm trong CSS background, được chèn bằng JS, hoặc không có src/srcset trong HTML ban đầu.

<!-- CSS background can be an LCP candidate, but is discovered after CSS. -->
<div class="hero" style="background-image: url(/hero.avif)"></div>

<!-- Content image: expose it directly to the preload scanner. -->
<img src="/hero.avif" alt="..." fetchpriority="high" width="1200" height="600" />
<!-- If the LCP resource must stay hidden in CSS/JS, preload it explicitly. -->
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />

Với ảnh mang nội dung, ưu tiên <img>src/srcset ngay trong HTML và thử fetchpriority="high". Nếu ảnh thực sự mang tính trang trí và phải ở CSS background, giữ đúng semantics nhưng preload URL đó. fetchpriority chỉ là hint; luôn kiểm tra cột Priority và waterfall trong DevTools.


4. Giảm resource load duration (ảnh tải nhanh hơn)

Khi đã bắt đầu tải sớm, giảm thời gian tải phần tử LCP:

  • Định dạng hiện đại: AVIF/WebP thường nhỏ hơn JPEG/PNG ở chất lượng tương đương, nhưng phải đo trên từng asset (Phần 9).
  • Kích thước đúng: đừng tải ảnh 4000px để hiển thị 1200px; dùng srcset/sizes (Phần 9).
  • Nén hợp lý: so sánh nhiều mức chất lượng trên chính asset; không có một con số 75–80 đúng cho mọi codec và loại ảnh.
  • Đừng lazy-load ảnh LCP: loading="lazy" cho ảnh hero là phản tác dụng — nó trì hoãn chính phần tử LCP.
<!-- ❌ do NOT lazy-load the LCP image -->
<img src="/hero.avif" loading="lazy" alt="..." width="1200" height="600" />

<!-- ✅ LCP image loads immediately (eager by default) + high priority -->
<img src="/hero.avif" fetchpriority="high" alt="..." width="1200" height="600" />

5. Giảm render delay (chặn vẽ)

Đôi khi ảnh đã tải xong nhưng vẫn chưa vẽ vì main thread bận hoặc nội dung chờ thứ khác:

  • JS chặn: bundle lớn chạy đồng bộ giữ main thread — defer/code-split (Phần 4, 8).
  • Font chặn text LCP: nếu phần tử LCP là text, font tải chậm có thể trì hoãn nó — chọn font-display, fallback và preload có chọn lọc theo Phần 10.
  • Hydration nặng (SSR framework): hydration đồng bộ lớn có thể chặn — cân nhắc islands/selective hydration hoặc streaming phù hợp (Phần 15).
  • CSS chặn: inline critical CSS để render sớm (Phần 4).

6. Checklist tối ưu LCP

Theo thứ tự “rẻ và hiệu quả nhất trước”:

  1. Tài nguyên LCP có được phát hiện trong HTML ban đầu (<img src/srcset>) hoặc preload nếu nằm trong CSS/JS?
  2. Có khai báo width/height (cũng giúp CLS — Phần 7)?
  3. fetchpriority="high" + không loading="lazy" cho ảnh LCP?
  4. Nếu tài nguyên bị phát hiện muộn: đã preload? Nếu ở origin khác: đã cân nhắc preconnect?
  5. Ảnh dùng AVIF/WebP, kích thước đúng, nén hợp lý?
  6. TTFB thấp (CDN, cache, SSG)?
  7. Nếu LCP là text: chiến lược font-display/fallback/preload đã được đo?
  8. Bundle JS defer/split để không chặn render?

Đo lại sau mỗi thay đổi (Phần 3). Đừng tự động dùng cả <img> + fetchpriority + preload: preload có thể thừa khi ảnh đã được phát hiện đủ sớm. Waterfall và LCP subparts mới quyết định đòn nào cần.


Tóm tắt

  • LCP = TTFB + resource load delay + resource load duration + element render delay; tìm thành phần lớn nhất rồi sửa.
  • Giảm TTFB bằng CDN/cache/SSG (static-first có lợi thế lớn).
  • Loại load delay: để preload scanner thấy resource sớm; dùng fetchpriority="high" và preload có chọn lọc.
  • Giảm load duration: codec/kích thước/nén phù hợp, không lazy-load ảnh LCP.
  • Giảm element render delay: defer/split JS, critical CSS có kiểm soát và chiến lược font phù hợp.

Phần tiếp theo: Tối ưu INP — diệt long task, chia nhỏ việc, đẩy tính toán sang web worker.