Khi làm việc với các chỉ số Core Web Vitals, chúng ta rất dễ phức tạp hóa vấn đề hiệu suất. Tuy nhiên, trên thực tế, nhiều trang web chậm chạp thường gặp phải những vấn đề cơ bản giống nhau. Bài viết này ghi lại quá trình tôi cải thiện chỉ số Largest Contentful Paint (LCP) bằng cách xử lý các tài nguyên chặn hiển thị (render-blocking), mà không cần thay đổi thiết kế hay trải nghiệm người dùng.
Trong bối cảnh SEO và giữ chân người dùng hiện nay, hiệu suất không còn là một yếu tố "có thì tốt" mà là bắt buộc. Thuật toán xếp hạng của Google tập trung mạnh vào Core Web Vitals, trong đó LCP là một trong những chỉ số quan trọng nhất. Nó đo lường thời gian để phần tử trực quan lớn nhất trên màn hình hiển thị cho người dùng. LCP chậm thường cho thấy trình duyệt đang gặp khó khăn trong việc tiếp cận phần "nội dung ý nghĩa" của trang do các nút thắt cổ chai về kiến trúc.
Tình trạng ban đầu
Trong một đợt kiểm tra hiệu suất định kỳ, Lighthouse đã gắn cờ một cơ hội lớn trong mục Eliminate render-blocking resources (Loại bỏ các tài nguyên chặn hiển thị), với mức tiết kiệm ước tính hơn sáu giây. Mặc dù thời gian phản hồi của máy chủ (TTFB) khá tốt, trang web vẫn tạo cảm giác chậm chạp và "kẹt" ở màn hình trắng trong vài giây.
Nhìn vào phần đầu (head) của HTML, vấn đề hiện ra rõ rệt. Trình duyệt bị buộc phải dừng lại và tải nhiều tệp bên ngoài trước khi có thể bắt đầu tính toán bố cục:
<link rel="stylesheet" href="/css/styles.css">
<script src="/js/script.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
Tất cả các tài nguyên này đều được tải đồng bộ. Cho đến khi chúng hoàn tất việc tải xuống và phân tích, công cụ hiển thị của trình duyệt thực tế đã bị tạm dừng. Đối với người dùng sử dụng kết nối 4G chậm hoặc thiết bị di động, điều này tạo ra một sự chậm trễ lớn trong cảm nhận hiệu suất.
Tại sao điều này gây hại cho LCP
Phần tử LCP của trang này nằm trong phần "hero" – một biểu ngữ hình ảnh độ phân giải cao kèm theo tiêu đề. Vì CSS và JavaScript đang chặn quy trình hiển thị, khung chứa phần hero không thể được vẽ sớm, ngay cả khi nội dung HTML thô đã được gửi đến từ máy chủ.
Điều này tạo ra hiệu ứng "màn hình trắng chết chóc": một màn hình trống không, sau đó là sự xuất hiện đột ngột của bố cục sau khi tệp CSS (nặng hơn 150KB) được xử lý hoàn toàn. Hành vi này dẫn đến chỉ số First Contentful Paint (FCP) kém và LCP cực kỳ thấp.
Bước 1: Trích xuất và chèn CSS quan trọng (Critical CSS)
Cải tiến kiến trúc lớn nhất đến từ việc tách biệt các kiểu (styles) cần thiết cho khung hình hiển thị đầu tiên (viewport). Chúng bao gồm bố cục header, khung hero, phông chữ cốt lõi và các khoảng cách thiết yếu. Đây được gọi là "Critical CSS".
Thay vì bắt người dùng đợi toàn bộ tệp stylesheet của trang web, tôi đã chèn trực tiếp các CSS cần thiết để hiển thị nội dung "trên màn hình đầu" vào thẻ <head>:
<head>
<style>
header { display: flex; align-items: center; height: 64px; }
.hero { min-height: 70vh; display: flex; align-items: center; background: #f0f0f0; }
.hero h1 { font-size: 2.5rem; line-height: 1.2; color: #111; }
/* Chỉ các style thiết yếu */
</style>
Các stylesheet "không quan trọng" còn lại sau đó được tải không đồng bộ để không chặn việc hiển thị:
<link rel="preload" href="/css/styles.css" as="style" onload="this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="/css/styles.css">
</noscript>
Điều này cho phép trình duyệt hiển thị nội dung có ý nghĩa gần như ngay lập tức, giảm đáng kể FCP và tạo cấu trúc cho phần tử LCP xuất hiện sớm hơn.
Bước 2: Trì hoãn JavaScript một cách an toàn
JavaScript của trang web xử lý các tính năng không thiết yếu như menu di động, phân tích dữ liệu và theo dõi của bên thứ ba. Không có tính năng nào trong số này cần thiết cho lần hiển thị hình ảnh đầu tiên.
Theo mặc định, các thẻ <script> sẽ chặn trình phân tích DOM. Tôi đã thay đổi cách triển khai từ tải tiêu chuẩn sang tải trì hoãn (defer):
<!-- Ban đầu: Chặn hiển thị -->
<script src="/js/script.js"></script>
<!-- Tối ưu: Trì hoãn (Defer) -->
<script src="/js/script.js" defer></script>
Với thuộc tính defer, trình duyệt tải xuống tập lệnh trong nền trong khi vẫn tiếp tục phân tích HTML. Tập lệnh chỉ thực thi sau khi tài liệu đã được phân tích đầy đủ, đảm bảo rằng các yếu tố trực quan được ưu tiên trước khả năng tương tác.
Bước 3: Tối ưu hóa Google Fonts
Phông chữ thường là "kẻ sát nhân thầm lặng" đối với LCP. Trang web dựa vào Google Fonts với nhiều độ dày khác nhau, yêu cầu thêm các truy vấn DNS và bắt tay TLS tới fonts.gstatic.com.
Đầu tiên, tôi đã thêm các gợi ý kết nối (connection hints) để tăng tốc quá trình bắt tay:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
Tuy nhiên, để đạt hiệu suất tối đa, cuối cùng tôi đã chuyển sang tự lưu trữ (self-hosting) các phông chữ ở định dạng WOFF2 và sử dụng thuộc tính font-display: swap. Điều này đảm bảo văn bản luôn hiển thị bằng phông chữ hệ thống cho đến khi phông chữ tùy chỉnh tải xong:
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
font-display: swap;
}
Bước 4: Tải trước hình ảnh LCP
Phần tử LCP là một hình ảnh hero WebP lớn. Nếu không có chỉ dẫn, trình duyệt chỉ phát hiện ra hình ảnh này sau khi CSS đã được phân tích và bố cục được tính toán. Để ưu tiên tài nguyên này, tôi đã sử dụng gợi ý preload với mức độ ưu tiên cao:
<link rel="preload" as="image" href="/images/hero.webp" fetchpriority="high">
Đồng thời, tôi đảm bảo rằng hình ảnh cụ thể này không được sử dụng lazy-load, vì lazy-load cho một hình ảnh LCP là một sai lầm phổ biến khiến việc hiển thị bị chậm trễ thêm.
Kết quả và Bài học chiến lược
Sau khi áp dụng những thay đổi cơ bản này, kết quả thu được rất ấn tượng:
- LCP: Giảm từ 7.2 giây xuống còn 1.1 giây.
- FCP: Giảm từ 2.4 giây xuống còn 0.6 giây.
- Điểm Lighthouse: Tăng từ 45 lên 98 trong hạng mục Hiệu suất (Performance).
Bài học quan trọng nhất là chúng ta đã đạt được những kết quả này mà không cần loại bỏ tính năng hay thay đổi thiết kế. Chúng ta chỉ đơn giản là sắp xếp lại cách phân phối tài nguyên phù hợp với cách thức hoạt động tự nhiên của trình duyệt.
Trước khi đầu tư vào việc chuyển đổi framework phức tạp hay nâng cấp máy chủ đắt tiền, hãy luôn kiểm tra các tài nguyên chặn hiển thị của bạn. Trong hầu hết các trường hợp, các yếu tố cơ bản của "Đường dẫn hiển thị quan trọng" (Critical Rendering Path) chính là nơi những trận chiến hiệu suất lớn nhất được định đoạt.