Critical CSS 2026: Trích xuất Above-the-Fold CSS cho LCP và FCP tốt hơn

Cách trích xuất Critical CSS above-the-fold và inline vào <head> để cải thiện LCP 300-1200ms. Bao gồm Beasties, Penthouse, Next.js 15, Nuxt 4, Astro và các bẫy phổ biến.

Critical CSS 2026: Tăng LCP & FCP

Cập nhật: 28 Tháng 7, 2026

Critical CSS là kỹ thuật trích xuất phần CSS tối thiểu cần thiết để render nội dung above-the-fold rồi inline trực tiếp vào <head>, giúp trình duyệt hiển thị màn hình đầu tiên mà không phải chờ file CSS bên ngoài tải xong. Trong năm 2026, với ngưỡng LCP "Good" là 2.5 giây và INP là 200ms, loại bỏ render-blocking CSS trở thành một trong những cải thiện có tác động cao nhất bạn có thể triển khai trong một buổi chiều. Bài viết này đi sâu vào công cụ, framework, và các bẫy mà tôi đã gặp khi tối ưu Critical CSS cho hàng chục dự án production.

  • Critical CSS inline giúp trình duyệt render above-the-fold trong lần round-trip đầu tiên, cải thiện FCP 200–800ms và LCP 300–1200ms tuỳ site.
  • Giữ Critical CSS dưới 14KB (gzipped) để nằm gọn trong TCP initial congestion window. Vượt ngưỡng này bạn mất lợi thế và bắt đầu tự bắn vào chân mình.
  • Beasties (fork được duy trì của Critters) là công cụ mặc định cho 2026; Penthouse vẫn tốt cho use case standalone và Puppeteer-based extraction.
  • Next.js 15 bật experimental.optimizeCss để inject Critical CSS tự động qua Beasties; Nuxt 4 và Astro hỗ trợ tương tự out-of-the-box.
  • Kỹ thuật media="print" + onload là cách chuẩn để async-load phần CSS còn lại mà không block render.
  • Đo trước và sau bằng WebPageTest hoặc DebugBear. Critical CSS làm hại hiệu năng nếu bạn inline nhầm class không dùng.

Critical CSS là gì và tại sao ảnh hưởng đến LCP?

Critical CSS là tập hợp con nhỏ nhất của stylesheet chứa mọi selector cần thiết để paint chính xác vùng viewport đầu tiên mà người dùng nhìn thấy. Khi trình duyệt gặp một thẻ <link rel="stylesheet"> trong <head>, nó coi tài nguyên đó là render-blocking: parser DOM tiếp tục nhưng render tree không được xây dựng cho đến khi CSSOM hoàn thành. Trên kết nối 3G Fast (RTT ~150ms), một CSS bundle 80KB có thể chặn first paint 400–900ms. Thời gian đó nằm thẳng trong ngân sách LCP của bạn.

Inline Critical CSS phá vỡ dependency chain đó. Vì HTML được stream về client trong lần round-trip đầu tiên, style rules đi kèm ngay trong HTML cũng đến cùng lúc, cho phép browser dựng render tree ngay sau khi DOM parser gặp đóng thẻ </style>. Trong Chromium 122+, kết hợp thêm với fetchpriority và Early Hints, bạn có thể đưa ảnh LCP vào paint ngay lúc CSS quan trọng đã sẵn sàng, đẩy LCP xuống dưới 1.8s trên phần lớn thiết bị mid-range.

So, có ba lý do khiến Critical CSS 2026 vẫn hiệu quả dù HTTP/3 và Server Push (đã deprecated) không giải quyết vấn đề: cross-origin CSS vẫn không hưởng lợi từ 103 Early Hints trên nhiều CDN, CSS-in-JS runtime vẫn cần hydration để inject rules, và initcwnd của Cloudflare/Fastly vẫn xoay quanh 10 segments (~14KB). Nói cách khác, một chút CSS inline gọn gàng vẫn là con đường ngắn nhất đến paint.

Cách trích xuất Critical CSS với Beasties, Penthouse và Critical

Ba công cụ định hình workflow Critical CSS năm 2026 là Beasties, Penthouse, và Critical. Google Chrome Labs đã archive Critters vào tháng 3/2024, và Beasties (bảo trì bởi Daniel Roe và nhóm Nuxt) là fork chính thức được cộng đồng chọn. Bạn có thể xem chi tiết tại repository Beasties trên GitHub.

Beasties chạy như một PostCSS plugin hoặc webpack/Rollup/Vite plugin, phân tích HTML output đã render và loại bỏ selector không match. Cài đặt và cấu hình cơ bản:

// vite.config.js
import { defineConfig } from 'vite'
import Beasties from 'beasties'

const beasties = new Beasties({
  path: 'dist',
  preload: 'swap',        // dùng media="print" swap cho phần còn lại
  pruneSource: false,     // giữ file CSS gốc để không phá cache
  reduceInlineStyles: false,
  mergeStylesheets: true,
  additionalStylesheets: ['**/critical-*.css'],
  logLevel: 'info',
})

export default defineConfig({
  plugins: [
    {
      name: 'beasties',
      transformIndexHtml: async (html) => beasties.process(html),
    },
  ],
})

Penthouse thì đi theo hướng khác: launch một Puppeteer instance thật, mở URL của bạn trong headless Chrome ở kích thước viewport chỉ định, sau đó xét mọi rule trong CSS gốc và giữ lại những rule mà DOM tại thời điểm paint có match. Cách này chính xác hơn khi bạn có CSS phức tạp với pseudo-class, nhưng chậm hơn Beasties 5–20 lần và cần Chrome dependency.

// scripts/generate-critical.mjs
import penthouse from 'penthouse'
import { readFile, writeFile } from 'node:fs/promises'

const routes = ['/', '/products', '/blog']

for (const route of routes) {
  const css = await readFile('dist/assets/main.css', 'utf8')
  const critical = await penthouse({
    url: `http://localhost:4173${route}`,
    cssString: css,
    width: 1300,
    height: 900,
    timeout: 30000,
    keepLargerMediaQueries: false,
    forceInclude: [/^\.hero-/, '.site-nav'],
    forceExclude: [/^\.print-/],
  })
  await writeFile(
    `dist/critical${route.replace(/\//g, '_') || '_home'}.css`,
    critical,
  )
}

Critical (bởi Addy Osmani) là wrapper cấp cao gọi Penthouse phía dưới nhưng tự động hoá phần inline HTML, phù hợp cho build pipeline đơn giản. Với site có hàng nghìn route sinh động, tôi khuyến nghị Beasties chạy tại request time trên edge worker. Với static site có ít layout, Penthouse chạy tại build time cho kết quả tinh khiết nhất.

Critical CSS trong Next.js 15, Nuxt 4 và Astro

Các meta-framework hiện đại đã tích hợp sẵn Critical CSS, giảm boilerplate đáng kể. Trong Next.js 15, flag experimental.optimizeCss bật Beasties trong pipeline build; khi bạn dùng App Router với Server Components, framework tự tách CSS per-route và chỉ inline phần khớp với current route:

// next.config.js
/** @type {import('next').NextConfig} */
module.exports = {
  experimental: {
    optimizeCss: true,          // bật Beasties
    inlineCss: true,            // Next 15.2+: inline critical CSS trực tiếp
  },
  compiler: {
    removeConsole: process.env.NODE_ENV === 'production',
  },
}

Kể từ Next.js 15.2 (tháng 2/2025), option inlineCss áp dụng cho tất cả static route và ISR pages; dynamic route render trên edge sẽ inline thông qua streaming SSR nếu bạn dùng runtime = 'edge'. Xem thêm tại tài liệu inlineCss chính thức của Next.js.

Nuxt 4 mặc định bật Beasties từ v3.7. Bạn có thể tinh chỉnh trong nuxt.config.ts:

// nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    inlineRouteRules: true,
  },
  nitro: {
    prerender: { crawlLinks: true },
    routeRules: {
      '/': { prerender: true, headers: { 'cache-control': 's-maxage=3600' } },
    },
  },
  hooks: {
    'build:manifest': (manifest) => {
      // Loại preload cho CSS đã được inline
      for (const key in manifest) {
        const file = manifest[key]
        if (file.css) file.preload = false
      }
    },
  },
})

Astro không có option Critical CSS trực tiếp vì mặc định đã chỉ ship CSS của component được dùng trên trang đó. Mô hình islands architecture của Astro làm phần lớn công việc. Tuy nhiên, với trang có nhiều island, bạn vẫn nên chạy Beasties qua integration astro-critters để dedupe và inline phần chung.

Với site React thuần dùng Vite hoặc CRA, tôi khuyến nghị cấu hình Beasties trong vite-plugin-html hoặc chạy như post-build step. Cách tiếp cận này đã được kiểm chứng trong workflow tối ưu JavaScript bundle mà tôi đã mô tả trước đó.

Async-load phần CSS còn lại bằng media="print" trick

Inline Critical CSS chỉ giải quyết above-the-fold; phần CSS còn lại (component below-the-fold, modal, dark mode) vẫn cần tải nhưng không được block render. Kỹ thuật chuẩn là media="print" + onload:

<!-- Critical CSS inline -->
<style>
  /* rules extracted by Beasties */
  body{margin:0;font-family:system-ui}
  .hero{min-height:60vh;display:grid;place-items:center}
</style>

<!-- Phần còn lại: async, non-blocking -->
<link
  rel="preload"
  href="/assets/main.css"
  as="style"
  onload="this.onload=null;this.rel='stylesheet'"
/>
<noscript><link rel="stylesheet" href="/assets/main.css"></noscript>

Kỹ thuật này hoạt động vì trình duyệt xem CSS media="print" là non-render-blocking cho viewport screen, nhưng vẫn tải file với ưu tiên thấp. Khi tải xong, callback onload đổi media thành all, và CSSOM được cập nhật mà không block first paint.

Với LCP là ảnh hero background, kết hợp thêm fetchpriority="high" cho ảnh và preconnect cho CDN:

<link rel="preconnect" href="https://cdn.webperfclinic.com" crossorigin>
<link
  rel="preload"
  as="image"
  href="https://cdn.webperfclinic.com/hero.avif"
  fetchpriority="high"
  imagesrcset="/hero-480.avif 480w, /hero-960.avif 960w"
  imagesizes="100vw"
>

Nếu bạn dùng nonce cho CSP, nhớ apply nonce cho thẻ <style> inline. Nếu không, browser sẽ silently từ chối apply rules và bạn nhận FOUC nhẹ nhưng LCP tệ:

<style nonce="{{cspNonce}}">/* critical rules */</style>

Đo lường tác động Critical CSS đến LCP, FCP và INP

Đừng deploy Critical CSS mà không đo lường. Tôi đã thấy nhiều case nơi kỹ thuật này làm LCP tệ đi vì extraction bỏ sót selector cho ảnh LCP hoặc inline quá nhiều class không dùng. Baseline bằng WebPageTest hoặc Lighthouse, ghi lại ba con số: FCP, LCP, và Total Blocking Time.

Trên thực địa, monitoring bằng web-vitals library và gửi RUM data về endpoint của bạn:

import { onLCP, onFCP, onINP } from 'web-vitals'

function report({ name, value, id, rating }) {
  const body = JSON.stringify({
    name, value, id, rating,
    url: location.pathname,
    connection: navigator.connection?.effectiveType,
    critical: document.querySelector('style[data-critical]') !== null,
  })
  navigator.sendBeacon('/rum', body)
}

onLCP(report)
onFCP(report)
onINP(report)

Attribute data-critical cho phép bạn split RUM data thành hai bucket: request có/không có Critical CSS đã inject. Sau 1–2 tuần thu thập, chạy phân tích percentile để so sánh p75 LCP giữa hai nhóm. Nếu Critical CSS thực sự có ích, delta sẽ hiện rõ trên connection 4G và slow. Chi tiết về pipeline RUM, xem hướng dẫn RUM cho Layout Stability.

Chrome DevTools cung cấp "Coverage" tab (Cmd+Shift+P → Show Coverage) để xem CSS/JS nào thực sự được dùng khi load trang. Rules màu đỏ trong Coverage là candidate loại khỏi Critical CSS, nhưng cẩn thận với modal/dropdown chỉ hiển thị sau tương tác.

Sai lầm phổ biến khi triển khai Critical CSS

Honestly, sau khi audit hơn 40 site production trong năm qua, đây là các bẫy tôi gặp lặp đi lặp lại:

  1. Inline quá nhiều CSS. Đội ngũ thấy Critical CSS "tốt" và inline luôn 40KB. Kết quả: HTML vượt initcwnd, first-byte trở nên đắt hơn, và cache hit rate giảm vì HTML không thể cache lâu như CSS.
  2. Không dedupe giữa các route. Home page và product page share 80% CSS nhưng mỗi HTML inline độc lập. Tổng transfer size của tất cả navigation tăng thay vì giảm.
  3. Bỏ quên fonts và @font-face. Font rules là part of CSSOM; nếu không có trong Critical CSS, text sẽ FOUT với fallback font, kéo CLS lên và LCP text-based tệ đi. Kết hợp với kỹ thuật tối ưu font web với size-adjust và fallback metrics.
  4. Extract từ HTML tĩnh thay vì hydrated DOM. Với SPA/CSR, Beasties chạy trên HTML rỗng sẽ không thấy class từ component, và output là stylesheet gần như trống rỗng.
  5. Không invalidate cache khi CSS thay đổi. Nếu bạn inline Critical CSS vào HTML nhưng cache HTML 24h ở CDN, deploy mới sẽ khiến visitor cũ nhận HTML có Critical CSS lỗi thời tham chiếu class đã bị đổi tên.
  6. Ignore CSP nonce. Đã đề cập ở trên: thẻ <style> không nonce trên site có strict-CSP sẽ bị browser drop im lặng.

So sánh Critical CSS, Inline Styles và CSS-in-JS

Ba cách tiếp cận styling khác nhau có đặc điểm hiệu năng khác nhau. Bảng dưới đây tổng hợp trade-off dựa trên benchmark chạy trên Vercel Edge Network với payload trung bình:

Tiêu chíCritical CSS + external CSSFull inline stylesCSS-in-JS runtime
Ảnh hưởng LCP (p75)Cải thiện 300–1200msCải thiện 400–1400msXấu 100–300ms (hydration)
Ảnh hưởng INPTrung tínhTrung tínhXấu (rules inject tại runtime)
Bundle size overhead+5–14KB HTML+40–100KB HTML+15–40KB JS
Cache hit rateCao (CSS cached)Thấp (HTML không cache lâu)Trung bình
Setup complexityTrung bìnhThấpCao
Framework supportNative trong Next/Nuxt/AstroManualEmotion, styled-components (đang thoái trào)
Best forSite content đa routeLanding page tĩnhDesign system phức tạp (build-time)

Kết luận thực dụng: với site content-heavy (blog, e-commerce, marketing), Critical CSS + external stylesheet là default tốt nhất. Với landing page single-purpose, inline full CSS nếu tổng size dưới 20KB. Với CSS-in-JS, ưu tiên giải pháp zero-runtime như Vanilla Extract, Panda CSS, hoặc Linaria (compile-time extraction). Emotion và styled-components runtime injection đang lỗi thời trong 2026 vì impact INP đo được rõ. MDN có tổng quan kỹ về critical rendering path và tối ưu paint nếu bạn muốn hiểu sâu cơ chế trình duyệt.

Cuối cùng, đừng chạy Critical CSS một lần rồi quên. Thêm nó vào pipeline Lighthouse CI với performance budget cho mỗi PR. Mỗi lần CSS bundle mở rộng, extraction cần chạy lại và budget cần verify không vượt ngưỡng 14KB. Tôi đã thấy nhiều team implement Critical CSS tuyệt vời trong quý 1 rồi để nó rot dần trong quý 2 khi designer thêm variant mới.

Câu hỏi thường gặp

Kích thước Critical CSS nên là bao nhiêu?

Giữ Critical CSS dưới 14KB sau gzip để nằm trong TCP initial congestion window (~10 segments của Cloudflare, Fastly, Vercel Edge). Vượt ngưỡng này, TCP cần thêm round-trip để chuyển hết HTML, xoá bỏ lợi ích chính của việc inline. Thực tế mục tiêu 8–12KB gzipped là ngưỡng an toàn với site content-heavy.

Critical CSS có bắt buộc cho website nhỏ không?

Không bắt buộc. Nếu tổng CSS của site dưới 20KB, HTTP/3 đã bật, và server hỗ trợ 103 Early Hints cho stylesheet, benefit thường dưới 100ms, nằm trong noise. Ưu tiên tối ưu ảnh, font, và third-party script trước; xử lý Critical CSS khi thấy LCP trên field data vượt 2.5s.

Có nên dùng Critical CSS với Tailwind CSS không?

Có, nhưng cách tiếp cận khác. Tailwind purge (JIT engine 2026) đã loại class không dùng ở build time, nên CSS output thường 5–15KB. Với size đó, bạn có thể inline toàn bộ file thay vì extract Critical CSS. Đơn giản hơn và tránh miss-match giữa above-the-fold và Tailwind class dùng động.

Critical CSS có ảnh hưởng đến SEO không?

Có, gián tiếp qua Core Web Vitals. Google dùng LCP và INP làm ranking signal từ 2024; cải thiện LCP 300–1200ms bằng Critical CSS có thể đẩy site vượt ngưỡng "Good" (2.5s), giúp xếp hạng cao hơn trong các ngành cạnh tranh. Bản thân việc inline CSS không được index, Googlebot vẫn crawl HTML render như bình thường.

Framework nào tự động xử lý Critical CSS?

Next.js 15 (với experimental.optimizeCssinlineCss), Nuxt 4 (bật mặc định qua Beasties từ v3.7), Astro (islands architecture chỉ ship CSS component dùng), và SvelteKit (per-route CSS splitting). Với React thuần dùng Vite, cần thêm plugin Beasties thủ công như ví dụ trong bài.

Editorial Team
Về Tác Giả Editorial Team

Our team of expert writers and editors.