Tối ưu LCP 2026: fetchpriority, Preload, Early Hints và Speculation Rules
Hướng dẫn 2026 giảm LCP xuống dưới 2.5s với fetchpriority, preload, Early Hints HTTP 103 và Speculation Rules API. Kèm code Next.js 15, lệnh DevTools và số đo thực tế trên Chrome 136.
Tối ưu LCP (Largest Contentful Paint) trong năm 2026 nghĩa là kéo thời gian render phần tử lớn nhất trong viewport xuống dưới 2.5 giây bằng cách kết hợp fetchpriority="high", <link rel="preload">, Early Hints (HTTP 103), Speculation Rules API và một CDN gần người dùng. Trên hồ sơ trace mà tôi đo hằng ngày, bốn đòn bẩy này thường rút LCP từ 3.8s xuống dưới 2.0s mà không phải viết lại framework. Hướng dẫn này đi qua từng giai đoạn của LCP (TTFB, resource load delay, resource load time và element render delay) kèm code chạy được, lệnh DevTools và những con số tôi đã đo trên Chrome 136.
Ngưỡng LCP "Good" của Google năm 2026 vẫn là ≤ 2.5s ở phân vị 75 trên thiết bị di động và máy tính.
LCP gồm 4 phần con: TTFB, resource load delay, resource load time và element render delay. Biết phần nào lớn nhất sẽ quyết định cách sửa.
fetchpriority="high" trên ảnh hero giảm LCP trung bình 10% theo dữ liệu CrUX của Chrome team.
Early Hints (HTTP 103) cho phép trình duyệt preload tài nguyên trước khi server gửi response chính, tiết kiệm 100-400ms TTFB.
Speculation Rules API với prerender đưa LCP của trang điều hướng nội bộ xuống gần 0ms.
Đừng preload font hoặc ảnh không phải là LCP, bạn sẽ làm chậm chính LCP element vì cạnh tranh băng thông.
LCP là gì và ngưỡng năm 2026
Largest Contentful Paint là thời điểm trình duyệt vẽ xong phần tử nội dung lớn nhất nhìn thấy được trong viewport ban đầu. Thường là ảnh hero, video poster, background image, hoặc một khối text dài. Tài liệu chính thức của web.dev về LCP chia ngưỡng thành ba mức: Good ≤ 2.5s, Needs Improvement 2.5-4.0s, Poor > 4.0s. Ngưỡng này áp dụng cho phân vị 75 trong dữ liệu CrUX (Chrome User Experience Report), tức 75% lượt truy cập phải đạt mới được tính là tốt.
Trong năm 2026, Google vẫn coi LCP là một trong ba Core Web Vitals chính cùng với INP (Interaction to Next Paint, thay FID từ tháng 3/2024) và CLS (Cumulative Layout Shift). Khác với INP đo phản hồi tương tác, LCP đo cảm nhận "trang đã load xong" của người dùng, và thật lòng, nó là chỉ số khó tối ưu nhất vì nó phụ thuộc vào toàn bộ pipeline từ DNS, TLS, server, CDN tới việc decode ảnh trên thiết bị.
Một điều ít ai nói: LCP có thể thay đổi nhiều lần trong quá trình load. Trình duyệt báo cáo "LCP candidate" mỗi khi xuất hiện một phần tử lớn hơn. Giá trị cuối cùng chỉ chốt khi người dùng tương tác hoặc rời tab. Vì vậy đo LCP qua RUM (Real User Monitoring) chính xác hơn lab test rất nhiều.
Bốn phần con của LCP
Đây là khung phân tích mà tôi dùng cho mọi audit LCP. Chrome team đã chính thức chia LCP thành bốn subpart, và nó thay đổi cách bạn ưu tiên fix:
Time to First Byte (TTFB): từ lúc navigation start tới byte HTML đầu tiên về. Thường chiếm 40% LCP.
Resource load delay: từ TTFB tới lúc trình duyệt bắt đầu request LCP resource. Đây là vùng bị bỏ quên nhiều nhất.
Resource load time: thời gian tải LCP resource (ảnh hero chẳng hạn).
Element render delay: từ lúc resource load xong tới lúc vẽ trên screen. Thường bị block bởi CSS hoặc JavaScript.
Mở Chrome DevTools > Performance > ghi trace > mục "Timings" sẽ thấy LCP marker và bạn có thể tính từng subpart. Nếu resource load delay > 500ms, vấn đề nằm ở thứ tự discovery (preloader scanner không thấy ảnh sớm). Nếu element render delay lớn, nghi ngờ render-blocking CSS hoặc JS hydration. Trace là sự thật duy nhất, đừng đoán.
Cách xác định LCP element chính xác
Trước khi tối ưu phải biết bạn đang tối ưu cái gì. Có ba cách đáng tin:
1. Chrome DevTools Performance panel
Ghi một trace với throttling "Slow 4G" và "4x CPU slowdown". Sau khi load xong, tìm node "Largest Contentful Paint" trong track "Timings". Hover sẽ highlight element trên screen. Đây là cách chính xác nhất trong môi trường lab.
Đặt snippet này trong <head> sẽ log mọi LCP candidate. Trong production, hãy gửi entry cuối cùng về analytics khi user rời trang (sự kiện visibilitychange với state hidden).
3. Lighthouse và PageSpeed Insights
Lighthouse 12 (bundled trong Chrome 136) hiển thị LCP element trong mục "Largest Contentful Paint element" kèm screenshot và HTML snippet. PageSpeed Insights còn cho thêm dữ liệu field từ CrUX, quan trọng vì lab data thường lạc quan hơn thực tế 30-50%.
Sử dụng fetchpriority="high" đúng cách
Đây là tối ưu có ROI cao nhất bạn có thể làm trong 5 phút. Thuộc tính fetchpriority nói với trình duyệt mức ưu tiên tương đối khi fetch tài nguyên. Áp dụng cho ảnh LCP:
Theo tài liệu Chrome Developers về Fetch Priority, mặc định ảnh trong viewport được Chrome promote lên priority "High" sau khi layout xong, nhưng việc đó tốn khoảng 100-300ms. fetchpriority="high" bỏ qua bước chờ này.
Trên Next.js 15 với component <Image priority />, framework tự động thêm fetchpriority="high" kèm preload. Trên React thuần hoặc HTML tĩnh bạn phải tự gán. Tôi từng audit một site WooCommerce có LCP 4.1s; chỉ thêm fetchpriority="high" vào ảnh sản phẩm chính, LCP xuống 2.7s ngay trong lần build sau. Honestly, không có nhiều thay đổi 5 phút nào mang lại kết quả tương đương.
Preload, preconnect và resource hints
Khi LCP resource đến từ JavaScript (như background-image trong CSS, hoặc ảnh chèn động bởi React), preloader scanner của trình duyệt không thấy nó trong HTML thô. Hậu quả: resource load delay tăng vọt. Giải pháp là <link rel="preload">:
preconnect phải có crossorigin nếu origin trả về CORS. Nếu không, trình duyệt sẽ mở kết nối thứ hai và uổng phí.
imagesrcset và imagesizes trên link preload phải khớp chính xác với srcset/sizes trên <img>, nếu không trình duyệt sẽ tải hai lần (tôi đã debug đúng cái bug này hai lần năm ngoái).
Đừng preload font WOFF2 nếu nó không tham gia LCP. Font preload tranh băng thông với ảnh hero và có thể đẩy LCP tệ hơn.
Đây là tính năng "miễn phí" mà ít team thực sự bật. HTTP 103 Early Hints cho phép server gửi một response sơ bộ chứa các header Link để trình duyệt bắt đầu preload trong khi backend còn đang xử lý request. Hỗ trợ trong Chrome 103+, Firefox 120+, và Cloudflare/Fastly/Vercel/Netlify đều đã có ở edge. Chi tiết kỹ thuật được mô tả trong RFC 8297 chính thức của IETF.
HTTP/2 103 Early Hints
Link: </hero-1600.avif>; rel=preload; as=image; fetchpriority=high
Link: <https://cdn.example.com>; rel=preconnect; crossorigin
HTTP/2 200 OK
Content-Type: text/html
...
Trên Vercel với Next.js 15, Early Hints bật mặc định cho dynamic routes. Trên Cloudflare Workers bạn dùng response.headers.set('103-Early-Hints', ...) hoặc bật trong dashboard.
Số tôi đo: trên một WordPress site host trên Cloudflare, bật Early Hints cho ảnh hero giảm LCP P75 từ 2.9s xuống 2.4s, qua được ngưỡng "Good" mà không đổi gì backend. Lợi ích lớn nhất khi server-side render chậm (200ms+), vì khoảng thời gian đó vốn lãng phí trên main thread.
Speculation Rules API cho navigation tức thì
Thật lòng, trick mạnh nhất tôi học năm 2026 là Speculation Rules API trên MDN. Thay vì tối ưu LCP cho từng trang riêng lẻ, bạn ra lệnh trình duyệt prerender trang kế tiếp trước khi user click. Khi điều hướng xảy ra, LCP gần như bằng 0.
Bốn mức eagerness: immediate (ngay), eager (gần như ngay), moderate (khi hover ~200ms), conservative (khi pointerdown). Tôi mặc định dùng moderate vì cân bằng giữa hit rate prerender và chi phí băng thông/CPU.
Critical CSS và render-blocking
Ngay cả khi ảnh đã ở local, LCP vẫn không vẽ được trước khi CSS render-blocking được parse. CSS trong <head> mặc định là render-blocking. Hai chiến lược:
1. Inline critical CSS
Inline 14KB CSS đầu tiên trong <style> ở <head>, đủ để vẽ above-the-fold. Phần còn lại load async:
<head>
<style>/* critical CSS inline ~14KB */</style>
<!-- Phần còn lại không block render -->
<link
rel="preload"
href="/styles/main.css"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
>
<noscript><link rel="stylesheet" href="/styles/main.css"></noscript>
</head>
Công cụ tự động: Critters (Next.js dùng mặc định), Beasties (fork của Critters duy trì 2025+), hoặc Penthouse. Tôi prefer Beasties vì nó hỗ trợ font-display và container queries tốt hơn.
2. CSS Container Queries không block
Tin tốt: container queries (hỗ trợ phổ biến từ 2023) không gây render-blocking khác CSS thường. View Transitions API mới (Chrome 111+, Safari 18) cũng không block LCP.
Giảm TTFB với CDN edge và streaming SSR
Nếu TTFB > 800ms, mọi tối ưu phía client chỉ là dán băng cá nhân. TTFB phải xử lý trước. Ba đòn bẩy:
CDN edge gần user: Cloudflare, Fastly, Bunny.net, Vercel Edge. Chọn provider có PoP ở thị trường chính. Một request từ Hà Nội tới origin Mỹ đã ngốn 200ms RTT trước khi server kịp xử lý gì.
Streaming SSR: React 19 với renderToPipeableStream hoặc Next.js 15 App Router stream HTML theo từng phần. TTFB tính đến byte đầu tiên, và byte đầu tiên có thể chỉ là <head> với preload, đến trước khi React hoàn tất.
JavaScript trong main thread cũng ảnh hưởng gián tiếp tới LCP: nếu trang phải hydrate trước khi vẽ, hydration chiếm CPU làm chậm decode ảnh. Long tasks > 50ms trong khoảng load ban đầu là cờ đỏ. Kỹ thuật yield/chunk được mô tả trong bài tối ưu JavaScript bundle 2026 với code splitting và scheduler.yield().
Đo LCP trong thực tế (RUM)
Lab data (Lighthouse) hữu ích cho regression test, nhưng quyết định ranking Google dùng field data. Bạn có ba lựa chọn miễn phí:
web-vitals JS library của Google (<2KB), gửi LCP/INP/CLS qua sendBeacon:
import { onLCP } from 'web-vitals';
onLCP((metric) => {
// metric.value = LCP tính bằng ms
// metric.attribution chứa subpart phân tích
navigator.sendBeacon('/rum', JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
ttfb: metric.attribution.timeToFirstByte,
resourceLoadDelay: metric.attribution.resourceLoadDelay,
resourceLoadTime: metric.attribution.resourceLoadTime,
elementRenderDelay: metric.attribution.elementRenderDelay,
element: metric.attribution.element?.tagName
}));
});
CrUX API: dữ liệu 28 ngày từ người dùng Chrome thật, query bằng JSON API miễn phí.
Google Search Console > Core Web Vitals report: chia URL theo nhóm "Good / Needs Improvement / Poor" với mẫu lớn.
Tôi push cả ba vào Grafana và alert khi LCP P75 > 2.5s trong 1 giờ. Không có RUM, mọi quyết định tối ưu chỉ là dự đoán. Một lần tôi tin Lighthouse báo 1.9s nhưng CrUX lại nói 3.4s; hóa ra 40% user dùng điện thoại Android tầm trung ở vùng có 3G yếu, mà lab test thì giả lập "Slow 4G" trên CPU desktop. Bài học: luôn nhìn field data trước.
Câu hỏi thường gặp
LCP tốt là bao nhiêu giây trong năm 2026?
Google vẫn giữ ngưỡng LCP "Good" ở mức ≤ 2.5 giây tại phân vị 75 (P75) của lượt tải, áp dụng cho cả mobile và desktop. Từ 2.5-4.0s là "Needs Improvement" và > 4.0s bị xếp "Poor". Ngưỡng này được đo trên dữ liệu CrUX 28 ngày gần nhất.
fetchpriority="high" có thật sự giảm LCP không?
Có. Dữ liệu CrUX công khai của Chrome team cho thấy site áp dụng fetchpriority="high" cho ảnh LCP giảm trung bình 10% LCP. Lợi ích lớn nhất khi LCP là ảnh nằm trong HTML và có srcset, vì preloader scanner thường mất 100-300ms trước khi promote priority tự động.
Nên preload font hay preload ảnh hero trước?
Ưu tiên ảnh hero nếu nó là LCP element. Preload font chỉ hợp lý khi text là LCP (hiếm gặp trên trang có hero image) hoặc khi muốn tránh layout shift do font swap. Preload cả hai có thể khiến chúng tranh băng thông và làm tệ LCP.
Speculation Rules có tốn pin và data người dùng không?
Có ảnh hưởng nhỏ. Chrome tự giới hạn còn 10 prerender đồng thời và tự tắt khi user dùng Data Saver, có pin yếu, hoặc kết nối 2G/Slow 3G. Dùng eagerness: "moderate" hoặc "conservative" để chỉ prerender khi user thể hiện ý định click.
Tại sao LCP của tôi tốt trong Lighthouse nhưng tệ trong CrUX?
Lighthouse chạy trên máy mạnh, mạng giả lập "Slow 4G", và một profile. CrUX tổng hợp triệu lượt truy cập thật trên mọi thiết bị từ flagship tới điện thoại $100. Khoảng cách 30-50% giữa lab và field là bình thường, luôn ưu tiên field data để ra quyết định.