Core Web Vitals 2026 — Tối ưu LCP, INP và CLS để website nhanh hơn

Posted on: 4/27/2026 4:11:58 AM

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:

< 2.5sLCP — Largest Contentful Paint
< 200msINP — Interaction to Next Paint
< 0.1CLS — Cumulative Layout Shift

Đ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ạngNén (so với JPEG)Progressive DecodeHỗ trợ trình duyệt
JPEGBaseline100%
WebP~25-35% tốt hơnKhông~97%
AVIF~50% tốt hơnKhông~93%
JPEG XL~55-60% tốt hơn~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>

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 transformopacity để 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>
EagernessThời điểm kích hoạtUse case
immediateNgay khi rule được parseTrang chắc chắn sẽ truy cập (checkout flow)
eagerSớm nhất có thểCTA chính, link nổi bật
moderateKhi hover link 200msNavigation links thông thường
conservativeKhi bắt đầu click/tapTiế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ểmMiễn phí
LighthouseLabTích hợp Chrome DevTools, CI/CD
PageSpeed InsightsLab + FieldKết hợp CrUX real-user data
WebPageTestLabTest từ nhiều location, connection
CrUX DashboardFieldDữ liệu thực từ Chrome users
web-vitals.jsRUMThu thập CWV từ chính site bạn
// 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ậtChỉ sốĐộ khóImpact
1Khai báo width/height cho mọi ảnhCLSThấpCao
2fetchpriority="high" cho ảnh LCPLCPThấpCao
3Bỏ loading="lazy" trên ảnh LCPLCPThấpCao
4Chuyển ảnh sang AVIF/JXLLCPTrung bìnhCao
5Inline critical CSSLCPTrung bìnhCao
6Code splitting theo routeINPTrung bìnhCao
7Cookie consent overlay (fixed)CLSThấpTrung bình
8Speculation Rules prefetchLCPThấpRất cao
9scheduler.yield() cho long tasksINPTrung bìnhCao
10Web Workers cho heavy computationINPCaoCao
11SSR at EdgeLCPCaoRất cao
12Đảm bảo bfcache eligibilityLCP + CLSTrung bìnhRấ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