Tối ưu Core Web Vitals với INP & LoAF — Bộ Đôi Định Nghĩa UX Performance Hiện Đại
Nhiều anh em làm FE chắc từng gặp cảnh này: Lighthouse chấm điểm Performance xanh nhưng khi đưa cho khách hàng dùng thì họ vẫn than "Em ơi sao bấm vào cái nút này nó cứ khựng?", "Nó cứ kiểu bị delay á em?". Đó là lúc chúng ta cần nói về INP và LoAF.
1. INP (Interaction to Next Paint) là gì?
Trước đây ta hay nhìn vào FID (độ trễ lần tương tác đầu tiên). Nhưng FID quá hời hợt, nó chỉ tính cái click đầu lúc trang web mới load xong.
INP thì khác. Nó theo dõi tất cả các lần người dùng click, tap hay nhấn phím trên trang từ đầu đến cuối. Sau đó, nó chọn ra lần phản hồi chậm nhất để làm đại diện.
Dễ hiểu là: Nếu trang web của bạn có 10 cái nút, 9 cái bấm phát ăn ngay, nhưng cái nút thứ 10 bấm vào phải đợi nửa giây mới thấy phản hồi, thì INP của bạn sẽ bị tính theo cái nút thứ 10 đó.
Ví dụ: Bạn làm một cái menu mobile. Người dùng bấm vào icon "Hamburger". Nếu code xử lý logic quá nặng, trình duyệt mất 300ms mới mở được menu -> INP sẽ cao (xấu). Người dùng sẽ cảm thấy trang web bị "đơ".

2. LoAF (Long Animation Frames) - Công cụ "soi" lỗi tận gốc
Nếu INP nói cho bạn biết "Trang web đang lag đấy", thì LoAF sẽ chỉ tận tay "Thằng nào làm lag". Trước đây chúng ta có Long Tasks, nhưng nó chỉ báo chung chung là có một tác vụ chạy quá 50ms. Anh em dev lại phải đi mò xem đó là đoạn code nào. LoAF nâng cấp hơn. Nó cho bạn biết:
- Đoạn script nào đang chạy (file nào, dòng nào).
- Mất bao nhiêu thời gian để tính toán, bao nhiêu thời gian để vẽ (render) lên màn hình.
- Thậm chí là do script của mình hay script của bên thứ ba (quảng cáo, tracker) gây ra.
3. Cách dùng LoAF để "fix" INP
Giả sử mình có một chức năng nặng như sau:
export function blockingHeavyCompute(durationMs: number = 300): void {
const start = performance.now();
// === VÙNG BLOCKING — đây là nơi LoAF sẽ trỏ đến ===
let result = 0;
while (performance.now() - start < durationMs) {
for (let i = 0; i < 1_000_000; i++) {
result += Math.sqrt(i) * Math.random();
}
}
if (result < 0) console.log("never");
}
Trong App.tsx mình call khi click button:
function handleRunHeavyTask() {
// → browser schedule trực tiếp runHeavyTask → LoAF thấy code của mình
requestAnimationFrame(function runHeavyTask() {
blockingHeavyCompute(400);
});
}
Thay vì đoán mò, mình dùng đoạn code này để soi:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.group(`🚀 Khung hình chậm phát hiện! (${entry.duration.toFixed(2)}ms)`);
entry.scripts.forEach((s, i) => {
// Dùng groupCollapsed để mặc định nó đóng lại, khi nào cần mới click vào xem
console.groupCollapsed(`Script #${i + 1}: ${s.invokerType} - ${s.duration.toFixed(2)}ms`);
console.log("File:", s.sourceLocation || "N/A"); // sourceLocation bao gồm cả URL, dòng và cột
console.log("Function:", s.functionName || "(anonymous)");
console.log("Invoker Type:", s.invokerType); // Ví dụ: 'user-callback', 'event-listener', v.v.
console.log("Char Position:", s.charPosition);
console.groupEnd();
});
console.groupEnd();
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
Mình có show ra UI cho dễ nhìn. Ban đầu mới vào:

Khi click button thì ta sẽ có kết quả như sau:

Fix khá đơn giản, chỉ cần thêm settimeout bọc ngoài
setTimeout(() => {
blockingHeavyCompute(400);
}, 0);
Nhưng tốt nhất là nên tối ưu lại logic bên trong hàm, tránh xử lý tác vụ nặng
Cách xử lý hay dùng:
- Chia nhỏ Task: Đừng bắt trình duyệt xử lý 1000 kết quả tìm kiếm cùng lúc. Hãy chia nhỏ ra bằng setTimeout hoặc requestIdleCallback.
- Ưu tiên hiển thị trước: Khi người dùng bấm nút, hãy hiển thị cái loading ngay lập tức (phản hồi thị giác), rồi mới xử lý logic nặng phía sau.
- Dùng Web Worker: Nếu có tính toán gì quá khủng khiếp, hãy đẩy nó sang một luồng khác (Worker), đừng để nó chiếm dụng Main Thread của trình duyệt.
4. Tổng kết
- INP là cái thước đo trải nghiệm thực tế: Đừng để người dùng phải chờ sau khi click.
- LoAF là cái kính hiển vi: Dùng nó để tìm ra chính xác dòng code nào đang chiếm dụng tài nguyên.
Tối ưu Web giờ không chỉ là lo load nhanh (LCP) nữa, mà phải là tương tác mượt. Hy vọng bài chia sẻ ngắn này giúp anh em có cái nhìn thực tế hơn để tối ưu dự án của mình.
All rights reserved