Core Web Vitals 2026 — Tối ưu LCP, INP và CLS để website nhanh hơn
Posted on: 4/27/2026 4:11:58 AM
Table of contents
- 1. Core Web Vitals là gì và tại sao quan trọng hơn bao giờ hết?
- 2. LCP — Tối ưu tốc độ hiển thị nội dung chính
- 3. INP — Tối ưu khả năng phản hồi tương tác
- 4. CLS — Loại bỏ hiện tượng nhảy layout
- 5. Speculation Rules API — Điều hướng gần như tức thì
- 6. View Transitions API — Chuyển trang mượt mà không cần SPA
- 7. Islands Architecture — Hydrate đúng chỗ, tiết kiệm JavaScript
- 8. Back/Forward Cache — Điều hướng ngược tức thì
- 9. Đo lường và giám sát liên tục
- 10. Checklist tối ưu Core Web Vitals 2026
- Tham khảo
1. Core Web Vitals là gì và tại sao quan trọng hơn bao giờ hết?
Core Web Vitals (CWV) là bộ ba chỉ số đo lường trải nghiệm người dùng thực tế trên web, do Google định nghĩa và sử dụng trực tiếp trong thuật toán xếp hạng tìm kiếm. Sau bản cập nhật Core Update tháng 12/2025, Google đã siết chặt ngưỡng đánh giá và tăng trọng số của CWV trong ranking — biến việc tối ưu từ "nên làm" thành "bắt buộc" nếu muốn cạnh tranh trên kết quả tìm kiếm.
Năm 2026, ba chỉ số Core Web Vitals gồm:
Điểm mấu chốt
INP đã chính thức thay thế FID (First Input Delay) từ tháng 3/2024. Khác với FID chỉ đo lần tương tác đầu tiên, INP đo mọi tương tác trong suốt vòng đời trang và lấy giá trị xấu nhất ở phân vị 75 — khó "đánh lừa" hơn nhiều và phản ánh đúng trải nghiệm thực tế của người dùng.
2. LCP — Tối ưu tốc độ hiển thị nội dung chính
Largest Contentful Paint đo thời gian từ lúc người dùng bắt đầu tải trang đến khi phần tử nội dung lớn nhất (hero image, tiêu đề, video poster) được render hoàn chỉnh trên viewport. Đây thường là chỉ số dễ nhìn thấy nhất — nếu trang "trắng" quá lâu, người dùng sẽ thoát.
2.1. Làm cho LCP resource có thể phát hiện được từ HTML
Theo dữ liệu từ HTTP Archive, 73% trang mobile có phần tử LCP là hình ảnh, nhưng 35% trong số đó không có URL hình ảnh trong HTML ban đầu — trình duyệt phải chạy JavaScript rồi mới biết cần tải ảnh nào. Đây là "tội" lớn nhất gây chậm LCP.
<!-- SAI: Trình duyệt không thể preload vì URL nằm trong JS -->
<div class="hero" data-bg="/images/hero.avif"></div>
<!-- ĐÚNG: URL nằm trực tiếp trong HTML, trình duyệt scan được ngay -->
<img src="/images/hero.avif"
width="1200" height="600"
fetchpriority="high"
alt="Hero banner">
2.2. Sử dụng fetchpriority và preload
Chỉ 15% trang web đang sử dụng thuộc tính fetchpriority — một con số rất thấp cho một tối ưu đơn giản nhưng hiệu quả cao. Attribute này báo cho trình duyệt biết tài nguyên nào cần ưu tiên tải trước.
<!-- Ưu tiên cao cho ảnh LCP -->
<img src="/hero.avif" fetchpriority="high" alt="Banner">
<!-- Preload kết hợp fetchpriority cho ảnh responsive -->
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high"
imagesrcset="/hero-400.avif 400w, /hero-800.avif 800w, /hero-1200.avif 1200w"
imagesizes="100vw">
<!-- Preconnect tới CDN bên thứ ba -->
<link rel="preconnect" href="https://cdn.example.com">
Lưu ý quan trọng
KHÔNG đặt loading="lazy" cho phần tử LCP. Lazy loading trì hoãn tải hình ảnh cho đến khi nó gần viewport — điều ngược lại với mục tiêu LCP. Chỉ lazy load các hình ảnh bên dưới fold (below-the-fold).
2.3. Tối ưu định dạng ảnh: AVIF và JXL
Năm 2026, AVIF đã trở thành chuẩn nén ảnh mới thay thế WebP, với tỷ lệ nén tốt hơn 30-50%. JPEG XL (JXL) cũng đang được hỗ trợ ngày càng rộng rãi với ưu điểm hỗ trợ progressive decoding.
<picture>
<source srcset="/hero.jxl" type="image/jxl">
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" width="1200" height="600"
fetchpriority="high" alt="Hero banner">
</picture>
| Định dạng | Nén (so với JPEG) | Progressive Decode | Hỗ trợ trình duyệt |
|---|---|---|---|
| JPEG | Baseline | Có | 100% |
| WebP | ~25-35% tốt hơn | Không | ~97% |
| AVIF | ~50% tốt hơn | Không | ~93% |
| JPEG XL | ~55-60% tốt hơn | Có | ~72% (đang tăng) |
2.4. Server-Side Rendering tại Edge
Chuyển việc render HTML từ server trung tâm sang edge functions (Cloudflare Workers, Vercel Edge, Deno Deploy) giúp giảm đáng kể TTFB — yếu tố trực tiếp ảnh hưởng LCP. Khi HTML được tạo ngay tại node CDN gần người dùng nhất, thời gian chờ mạng giảm từ 200-500ms xuống còn 20-50ms.
// Cloudflare Workers — SSR tại Edge
export default {
async fetch(request, env) {
const url = new URL(request.url);
const cache = caches.default;
// Kiểm tra edge cache trước
let response = await cache.match(request);
if (response) return response;
// Render HTML tại edge
const html = await renderPage(url.pathname, env);
response = new Response(html, {
headers: {
'Content-Type': 'text/html;charset=utf-8',
'Cache-Control': 's-maxage=60, stale-while-revalidate=600',
}
});
// Lưu vào edge cache
await cache.put(request, response.clone());
return response;
}
};
3. INP — Tối ưu khả năng phản hồi tương tác
Interaction to Next Paint đo thời gian từ khi người dùng tương tác (click, tap, nhấn phím) đến khi trình duyệt vẽ lại giao diện phản hồi. INP ghi nhận tất cả tương tác trong suốt phiên truy cập và báo cáo giá trị ở phân vị 75 (percentile 75th) — nghĩa là 75% tương tác phải nhanh hơn ngưỡng 200ms.
graph LR
A["Người dùng click"] --> B["Input Delay"]
B --> C["Processing Time"]
C --> D["Presentation Delay"]
D --> E["Next Paint"]
style A fill:#e94560,stroke:#fff,color:#fff
style B fill:#ff9800,stroke:#fff,color:#fff
style C fill:#2c3e50,stroke:#fff,color:#fff
style D fill:#ff9800,stroke:#fff,color:#fff
style E fill:#4CAF50,stroke:#fff,color:#fff
Hình 1: Cấu trúc thời gian của INP — Input Delay + Processing Time + Presentation Delay
3.1. Chia nhỏ Long Tasks với scheduler.yield()
Khi một task JavaScript chạy quá 50ms, nó trở thành "Long Task" — chặn main thread và khiến trình duyệt không thể phản hồi tương tác. Giải pháp: chia nhỏ task và trả quyền cho trình duyệt giữa các bước.
// SAI: Một task dài chặn main thread 300ms
function processAllItems(items) {
for (const item of items) {
heavyComputation(item); // 300ms blocked!
}
}
// ĐÚNG: Yield giữa các item để trình duyệt xử lý tương tác
async function processAllItems(items) {
for (const item of items) {
heavyComputation(item);
// Trả quyền cho trình duyệt sau mỗi item
if (navigator.scheduling?.isInputPending?.() ||
performance.now() - startTime > 50) {
await scheduler.yield();
}
}
}
// CÁCH 2: Dùng requestIdleCallback cho task không khẩn cấp
function processInBackground(items) {
let index = 0;
function chunk(deadline) {
while (index < items.length && deadline.timeRemaining() > 5) {
heavyComputation(items[index++]);
}
if (index < items.length) {
requestIdleCallback(chunk);
}
}
requestIdleCallback(chunk);
}
3.2. Code Splitting và Tree Shaking
JavaScript không sử dụng là kẻ thù số một của INP. Sử dụng Chrome DevTools Coverage tool để phát hiện unused code, sau đó áp dụng code splitting theo route.
// Vue.js — Lazy load component theo route
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
},
{
path: '/settings',
component: () => import('./views/Settings.vue')
}
];
// Dynamic import cho component nặng
const HeavyChart = defineAsyncComponent(() =>
import('./components/HeavyChart.vue')
);
3.3. Web Workers cho tính toán nặng
Đẩy các tác vụ tính toán nặng (xử lý ảnh, parse data lớn, crypto) sang Web Worker — thread riêng biệt không chặn main thread.
// main.js — Gửi task sang Worker
const worker = new Worker('/heavy-worker.js');
worker.postMessage({ type: 'PARSE_CSV', data: rawCsvData });
worker.onmessage = (e) => {
const parsedData = e.data;
updateUI(parsedData); // Main thread chỉ cập nhật UI
};
// heavy-worker.js — Xử lý nặng ở thread riêng
self.onmessage = (e) => {
if (e.data.type === 'PARSE_CSV') {
const result = parseAndTransform(e.data.data);
self.postMessage(result);
}
};
4. CLS — Loại bỏ hiện tượng nhảy layout
Cumulative Layout Shift đo mức độ "nhảy" của các phần tử trên trang khi tải. Khi một banner quảng cáo hoặc cookie consent đẩy nội dung xuống, người dùng đang đọc dở bỗng mất vị trí — đó chính là CLS cao. Ngưỡng tốt là dưới 0.1.
4.1. Luôn khai báo kích thước ảnh và iframe
Theo HTTP Archive, 66% trang web có ít nhất một ảnh không khai báo kích thước. Đây là nguyên nhân CLS phổ biến nhất và dễ fix nhất.
<!-- SAI: Trình duyệt không biết kích thước, gây layout shift khi ảnh tải xong -->
<img src="/photo.jpg" alt="Photo">
<!-- ĐÚNG: Khai báo width/height, trình duyệt reserve không gian ngay -->
<img src="/photo.jpg" width="800" height="600" alt="Photo">
<!-- ĐÚNG: Dùng aspect-ratio cho ảnh responsive -->
<img src="/photo.jpg" style="aspect-ratio: 4/3; width: 100%;" alt="Photo">
<!-- Reserve không gian cho nội dung dynamic -->
<div style="min-height: 250px;">
<!-- Quảng cáo hoặc nội dung load async -->
</div>
4.2. Cookie Consent và Overlay thay vì Push
Cookie consent banner đẩy nội dung xuống là một trong những nguồn CLS phổ biến nhất năm 2026. Giải pháp: dùng fixed/sticky positioning overlay lên nội dung thay vì insert vào đầu DOM.
/* SAI: Banner chèn vào đầu body, đẩy mọi thứ xuống */
.consent-banner {
width: 100%;
padding: 16px;
/* Không position: fixed → gây layout shift */
}
/* ĐÚNG: Overlay không ảnh hưởng layout */
.consent-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
z-index: 1000;
background: #fff;
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
}
4.3. Dùng transform thay vì thay đổi layout properties
Animation sử dụng top, left, margin, width sẽ trigger layout recalculation — gây CLS. Dùng transform và opacity để animation chạy trên GPU compositor, không ảnh hưởng layout.
/* SAI: Thay đổi layout property → CLS */
@keyframes slideIn {
from { left: -100%; }
to { left: 0; }
}
/* ĐÚNG: Transform chạy trên compositor, không gây CLS */
@keyframes slideIn {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
/* Containment — báo trình duyệt element này độc lập */
.card {
contain: layout style paint;
content-visibility: auto;
contain-intrinsic-size: 0 200px;
}
5. Speculation Rules API — Điều hướng gần như tức thì
Speculation Rules API là API mới cho phép trình duyệt prefetch hoặc prerender trang đích trước khi người dùng thực sự click link. Kết quả: khi người dùng nhấn link, trang đã sẵn sàng trong bộ nhớ — LCP gần bằng 0.
<!-- Prefetch: Tải trước tài nguyên của trang đích -->
<script type="speculationrules">
{
"prefetch": [
{
"source": "list",
"urls": ["/pricing", "/features", "/docs"]
}
]
}
</script>
<!-- Prerender: Render sẵn toàn bộ trang trong background -->
<script type="speculationrules">
{
"prerender": [
{
"source": "document",
"where": {
"and": [
{"href_matches": "/*"},
{"not": {"href_matches": "/logout"}}
]
},
"eagerness": "moderate"
}
]
}
</script>
| Eagerness | Thời điểm kích hoạt | Use case |
|---|---|---|
| immediate | Ngay khi rule được parse | Trang chắc chắn sẽ truy cập (checkout flow) |
| eager | Sớm nhất có thể | CTA chính, link nổi bật |
| moderate | Khi hover link 200ms | Navigation links thông thường |
| conservative | Khi bắt đầu click/tap | Tiết kiệm bandwidth, mobile |
Kết hợp với View Transitions API
Khi dùng Speculation Rules cùng View Transitions API, bạn có thể tạo trải nghiệm chuyển trang mượt mà như native app — trang đã prerender sẵn + animation chuyển cảnh GPU-accelerated. Đây là combo mạnh nhất cho Multi-Page Application trong 2026.
6. View Transitions API — Chuyển trang mượt mà không cần SPA
Trước đây, để có animation chuyển trang mượt, bạn buộc phải dùng SPA framework (Vue Router, React Router). View Transitions API mang khả năng này đến cho Multi-Page Application (MPA) — trình duyệt tự tạo snapshot của trang cũ và animate sang trang mới.
/* CSS cho cross-document view transitions */
@view-transition {
navigation: auto; /* Bật cho mọi same-origin navigation */
}
/* Tùy chỉnh animation cho phần tử cụ thể */
.hero-image {
view-transition-name: hero;
}
::view-transition-old(hero) {
animation: fadeOut 0.3s ease-out;
}
::view-transition-new(hero) {
animation: fadeIn 0.3s ease-in;
}
/* Animation mặc định cho toàn trang */
::view-transition-old(root) {
animation: slide-out 0.25s ease;
}
::view-transition-new(root) {
animation: slide-in 0.25s ease;
}
7. Islands Architecture — Hydrate đúng chỗ, tiết kiệm JavaScript
Islands Architecture là mô hình render trong đó phần lớn trang là HTML tĩnh (biển — ocean), còn các component tương tác là những "hòn đảo" (islands) được hydrate riêng biệt. Thay vì tải và chạy JavaScript cho toàn bộ trang, trình duyệt chỉ hydrate những phần thực sự cần tương tác.
graph TD
A["Trang web hoàn chỉnh"] --> B["Static HTML Ocean"]
A --> C["Interactive Islands"]
B --> D["Header - Tĩnh"]
B --> E["Nội dung bài viết - Tĩnh"]
B --> F["Footer - Tĩnh"]
C --> G["Search Bar - Hydrate on load"]
C --> H["Comment Form - Hydrate on visible"]
C --> I["Share Button - Hydrate on idle"]
style A fill:#e94560,stroke:#fff,color:#fff
style B fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style C fill:#4CAF50,stroke:#fff,color:#fff
style G fill:#ff9800,stroke:#fff,color:#fff
style H fill:#ff9800,stroke:#fff,color:#fff
style I fill:#ff9800,stroke:#fff,color:#fff
Hình 2: Islands Architecture — Static HTML làm nền, chỉ hydrate các component tương tác
<!-- Astro — Islands với directive điều khiển hydration -->
<!-- Hydrate ngay khi trang load (critical interactive) -->
<SearchBar client:load />
<!-- Hydrate khi component xuất hiện trong viewport -->
<CommentSection client:visible />
<!-- Hydrate khi trình duyệt rảnh (idle) -->
<ShareButtons client:idle />
<!-- Chỉ hydrate trên screen đủ rộng -->
<DesktopSidebar client:media="(min-width: 768px)" />
8. Back/Forward Cache — Điều hướng ngược tức thì
bfcache (Back/Forward Cache) lưu trữ snapshot hoàn chỉnh của trang trong bộ nhớ khi người dùng rời đi. Khi nhấn nút Back hoặc Forward, trang được khôi phục ngay lập tức từ bộ nhớ — LCP = 0, CLS = 0. Đây là tối ưu có ROI cao nhất mà nhiều developer bỏ qua.
Các yếu tố ngăn bfcache hoạt động:
// SAI: Những thứ này ngăn bfcache
// 1. Sử dụng event unload
window.addEventListener('unload', () => {
// Ngăn bfcache! Dùng pagehide thay thế
});
// 2. Cache-Control: no-store
// Header này ngăn bfcache trên một số trình duyệt
// 3. Window.opener reference
window.open(url); // Trang mở bởi opener không bfcache
// ĐÚNG: Dùng pagehide + pageshow
window.addEventListener('pagehide', (event) => {
if (event.persisted) {
// Trang sẽ vào bfcache, cleanup connections
}
});
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
// Trang được khôi phục từ bfcache
refreshStaleData();
}
});
9. Đo lường và giám sát liên tục
Tối ưu không có nghĩa nếu không đo lường. Sử dụng kết hợp Lab data (Lighthouse, WebPageTest) để phát hiện vấn đề trong development và Field data (CrUX, RUM) để đo trải nghiệm thực tế của người dùng.
| Công cụ | Loại | Ưu điểm | Miễn phí |
|---|---|---|---|
| Lighthouse | Lab | Tích hợp Chrome DevTools, CI/CD | Có |
| PageSpeed Insights | Lab + Field | Kết hợp CrUX real-user data | Có |
| WebPageTest | Lab | Test từ nhiều location, connection | Có |
| CrUX Dashboard | Field | Dữ liệu thực từ Chrome users | Có |
| web-vitals.js | RUM | Thu thập CWV từ chính site bạn | Có |
// Tích hợp web-vitals vào ứng dụng để thu thập RUM data
import { onLCP, onINP, onCLS } from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // "good", "needs-improvement", "poor"
delta: metric.delta,
id: metric.id,
navigationType: metric.navigationType,
url: location.href,
});
// Dùng sendBeacon để không ảnh hưởng performance
navigator.sendBeacon('/api/vitals', body);
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
10. Checklist tối ưu Core Web Vitals 2026
Dưới đây là checklist tổng hợp các kỹ thuật theo thứ tự ưu tiên — từ dễ triển khai và impact cao đến phức tạp hơn:
| # | Kỹ thuật | Chỉ số | Độ khó | Impact |
|---|---|---|---|---|
| 1 | Khai báo width/height cho mọi ảnh | CLS | Thấp | Cao |
| 2 | fetchpriority="high" cho ảnh LCP | LCP | Thấp | Cao |
| 3 | Bỏ loading="lazy" trên ảnh LCP | LCP | Thấp | Cao |
| 4 | Chuyển ảnh sang AVIF/JXL | LCP | Trung bình | Cao |
| 5 | Inline critical CSS | LCP | Trung bình | Cao |
| 6 | Code splitting theo route | INP | Trung bình | Cao |
| 7 | Cookie consent overlay (fixed) | CLS | Thấp | Trung bình |
| 8 | Speculation Rules prefetch | LCP | Thấp | Rất cao |
| 9 | scheduler.yield() cho long tasks | INP | Trung bình | Cao |
| 10 | Web Workers cho heavy computation | INP | Cao | Cao |
| 11 | SSR at Edge | LCP | Cao | Rất cao |
| 12 | Đảm bảo bfcache eligibility | LCP + CLS | Trung bình | Rất cao |
Bắt đầu từ đâu?
Chạy PageSpeed Insights cho trang quan trọng nhất của bạn. Xem phần "Diagnostics" để biết chính xác vấn đề nào đang ảnh hưởng từng chỉ số. Ưu tiên fix những mục có Impact cao và Độ khó thấp trước — thường chỉ cần 30 phút có thể cải thiện đáng kể điểm số.
Tham khảo
- The most effective ways to improve Core Web Vitals — web.dev
- Speculative Loading — MDN Web Docs
- Understanding Core Web Vitals and Google Search Results — Google
- The Ultimate Guide to Modern Web Performance Optimization in 2026 — DEV Community
- The Most Important Core Web Vitals Metrics in 2026 — NitroPack
Azure Container Apps Dynamic Sessions — Sandbox Hyper-V cho AI Agent chạy code an toàn
Cloudflare Pages — Deploy Full-Stack miễn phí trên Edge toàn cầu
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.