Một website đẹp nhưng tải chậm hoặc đơ lag khi người dùng click chuột sẽ làm giảm tỷ lệ chuyển đổi và tụt thứ hạng SEO trên Google. Kể từ khi Google chính thức áp dụng Interaction to Next Paint (INP) vào bộ chỉ số Core Web Vitals, bài toán tối ưu hiệu năng không chỉ dừng ở việc nén ảnh mà đòi hỏi tối ưu sâu vào luồng xử lý JavaScript trên Main Thread.
Bài viết này chia sẻ kinh nghiệm thực tế giúp đưa điểm Lighthouse từ 60 lên 99+ và giữ vững các chỉ số xanh trên báo cáo người dùng thực tế (CrUX).
1. Ba chỉ số Core Web Vitals quyết định thứ hạng
Google đánh giá chất lượng trải nghiệm trang web dựa trên 3 tiêu chí trọng yếu:
| Chỉ số | Tên đầy đủ | Mục tiêu chuẩn | Nguyên nhân chính gây chậm |
|---|---|---|---|
| LCP | Largest Contentful Paint | < 2.5 giây | Ảnh Hero nặng, render-blocking CSS/JS, Server phản hồi chậm |
| INP | Interaction to Next Paint | < 200 mili-giây | Main thread bị nghẽn bởi heavy JavaScript khi user tương tác |
| CLS | Cumulative Layout Shift | < 0.1 | Ảnh/quảng cáo không khai báo width/height, Web fonts gây giật chữ |
2. Tối ưu INP: Giải phóng Main Thread với scheduler.yield()
Khi người dùng bấm nút hoặc gõ phím, nếu trình duyệt đang bận xử lý một tác vụ dài (Long Task > 50ms), tương tác sẽ bị trì hoãn.
Thay vì chạy một vòng lặp nặng khóa cứng trình duyệt, chúng ta có thể chia nhỏ tác vụ và nhường quyền xử lý lại cho trình duyệt bằng API scheduler.yield() (hoặc fallback requestIdleCallback / setTimeout):
// Hàm hỗ trợ chia nhỏ tác vụ nhường quyền cho Main Thread
export async function yieldToMain() {
if ("scheduler" in window && "yield" in (window as any).scheduler) {
return await (window as any).scheduler.yield();
}
return new Promise((resolve) => setTimeout(resolve, 0));
}
// Ứng dụng trong việc xử lý danh sách lớn
export async function processLargeDataset(items: any[]) {
const results = [];
for (let i = 0; i < items.length; i++) {
results.push(heavyCompute(items[i]));
// Cứ mỗi 50 items lại yield một lần để trình duyệt kịp vẽ lại UI
if (i % 50 === 0) {
await yieldToMain();
}
}
return results;
}3. Triệt tiêu Layout Shift (CLS) và tối ưu LCP hình ảnh
Để đạt điểm tuyệt đối về LCP và CLS:
- Luôn đặt priority cho ảnh Hero: Dùng component next/image với thuộc tính priority={true} để trình duyệt thêm preload header.
- Khai báo tỷ lệ khung hình (Aspect Ratio): Đặt explicit width, height hoặc dùng aspect-video, aspect-square để trình duyệt dành sẵn không gian hiển thị, không làm xô lệch giao diện khi ảnh load xong.
- Tối ưu Web Fonts: Dùng font-display: swap kết hợp khai báo fallback metric (size-adjust, ascent-override).
import Image from "next/image";
export function HeroBanner() {
return (
<div className="relative w-full aspect-[16/9] md:aspect-[21/9] overflow-hidden rounded-2xl">
<Image
src="/hero-banner.webp"
alt="Hero Showcase"
fill
priority // Preload ngay từ HTML header
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
quality={85}
className="object-cover"
/>
</div>
);
}Bạn đang gặp vấn đề tương tự?
Tôi có thể giúp bạn tối ưu website — liên hệ để trao đổi miễn phí.
Kết luận
Tối ưu Web Performance không phải là công việc làm một lần rồi bỏ đó, mà là một thói quen tư duy trong suốt quá trình viết code. Bằng cách hiểu rõ cách trình duyệt render và giải phóng tài nguyên CPU hợp lý, bạn sẽ mang lại trải nghiệm tuyệt vời nhất cho người dùng của mình.
Muốn thảo luận thêm?
Nếu bạn có câu hỏi cụ thể về web performance, React, hoặc muốn hợp tác dự án — liên hệ trực tiếp với tôi.