Core Web Vitals 2026: INP thay FID và cách optimize hiệu quả
INP chính thức thay thế FID từ tháng 3/2024 và đến 2026 đã trở thành metric quyết định ranking. Bài viết hướng dẫn cách đo lường và optimize INP cho ứng dụng frontend.
Nếu bạn vẫn đang optimize FID (First Input Delay) thì đã quá muộn rồi. Từ tháng 3/2024, Google chính thức thay FID bằng INP (Interaction to Next Paint) trong Core Web Vitals. Và đến 2026, INP đã trở thành yếu tố ranking cực kỳ quan trọng.
FID vs INP: Khác nhau ở đâu?
FID chỉ đo lần tương tác đầu tiên — tức là user click lần đầu và browser mất bao lâu để bắt đầu xử lý. Vấn đề là: FID bỏ qua tất cả các interaction sau đó. Một trang có thể pass FID nhưng lag kinh khủng khi user scroll, click button, hay mở dropdown.
INP đo toàn bộ interactions trong suốt lifecycle của page, rồi lấy giá trị worst-case (thực tế là percentile 98). Threshold:
- Good: ≤ 200ms
- Needs Improvement: 200-500ms
- Poor: > 500ms
Đo INP như thế nào?
Cách đơn giản nhất là dùng web-vitals library:
import { onINP } from "web-vitals";
onINP((metric) => {
console.log("INP:", metric.value, "ms");
console.log("Element:", metric.attribution?.interactionTarget);
console.log("Type:", metric.attribution?.interactionType);
// Gửi về analytics
sendToAnalytics({
name: "INP",
value: metric.value,
target: metric.attribution?.interactionTarget,
// interactionType: pointer, keyboard, etc.
type: metric.attribution?.interactionType,
});
});
Ngoài ra, Chrome DevTools → Performance tab cũng đã hiển thị INP trực tiếp. Bật "Web Vitals" overlay trong DevTools để xem real-time.
5 kỹ thuật optimize INP
1. Tách long tasks bằng yield
Đây là kỹ thuật hiệu quả nhất. Khi có heavy computation, break nó ra để browser có cơ hội paint:
// ❌ Block main thread 300ms
function processItems(items: Item[]) {
items.forEach(item => heavyComputation(item));
}
// ✅ Yield sau mỗi chunk
async function processItems(items: Item[]) {
const CHUNK = 50;
for (let i = 0; i < items.length; i += CHUNK) {
const chunk = items.slice(i, i + CHUNK);
chunk.forEach(item => heavyComputation(item));
// Yield to main thread
await scheduler.yield();
}
}
scheduler.yield() là API mới, đã available trong Chrome. Cho các browser khác, fallback bằng setTimeout(0).
2. Dùng CSS contain để giới hạn layout thrashing
Khi một element thay đổi, browser có thể phải recalculate layout cho toàn bộ page. contain: content giới hạn scope của recalculation.
3. Virtualize danh sách dài
List 1000 items = 1000 DOM nodes = chậm. Dùng @tanstack/react-virtual hoặc react-window để chỉ render items trong viewport.
4. Debounce input handlers thông minh
Đừng debounce quá 100ms cho visual feedback. User cần thấy phản hồi ngay lập tức — chỉ debounce phần API call hoặc heavy logic.
5. Tránh forced synchronous layout
Đọc layout property (offsetHeight, getBoundingClientRect) ngay sau khi write DOM sẽ force browser layout lại. Batch reads trước, writes sau.
Monitoring trong production
Dùng CrUX (Chrome User Experience Report) data qua PageSpeed Insights hoặc BigQuery để track INP của real users. Đừng chỉ dựa vào lab data — INP phụ thuộc rất nhiều vào device thực tế của user.
INP khó hơn FID nhiều vì nó đo mọi interaction, không chỉ lần đầu. Nhưng nếu bạn focus vào việc giữ main thread nhẹ và yield đúng lúc, 200ms threshold hoàn toàn đạt được.
Admin
Bình luận (0)
Đăng nhập để bình luận
Chưa có bình luận nào. Hãy là người đầu tiên!