Sejak Google secara resmi menggantikan metrik First Input Delay (FID) dengan Interaction to Next Paint (INP) sebagai salah satu indikator inti Core Web Vitals, banyak aplikasi web modern berbasis React dan Next.js mengalami penurunan skor kepatuhan secara tiba-tiba di Google Search Console.
Jika FID hanya mengukur penundaan input pertama saat halaman dimuat, INP mengukur seluruh interaksi pengguna sepanjang siklus hidup sesi (klik tombol, pengetikan form, pembukaan menu dropdown, hingga interaksi filter tabel). INP mencatat interaksi dengan latensi visual terburuk (persentil ke-98).
Skor INP yang baik wajib berada di bawah 200 milidetik. Aplikasi dengan INP di atas 500 milidetik diklasifikasikan berkinerja buruk dan berisiko kehilangan peringkat dalam hasil pencarian organik.
Tiga Komponen Pembentuk Latensi INP #
Untuk memperbaiki skor INP, pengembang harus memahami bahwa latensi sebuah interaksi visual terbagi menjadi tiga fase berurutan:
Total Waktu INP (Wajib < 200 ms)
┌──────────────────────┬──────────────────────┬──────────────────────┐
│ 1. Input Delay │ 2. Processing Time │ 3. Presentation │
│ (Antrean Event) │ (Eksekusi JS) │ Delay (Paint) │
└──────────────────────┴──────────────────────┴──────────────────────┘
Main thread sibuk Event listener jalan Browser melakukan
menjalankan task (React state update & Style, Layout,
sebelumnya re-render komponen) Composite, dan Paint
- Input Delay: Waktu tunggu sejak pengguna menekan tombol hingga browser benar-benar mulai menjalankan callback event listener. Fase ini memanjang jika main thread sedang terblokir oleh skrip pihak ketiga atau hidrasi komponen berat.
- Processing Time: Waktu yang dibutuhkan kode JavaScript untuk mengeksekusi logika event handler, memproses manipulasi state React, dan merangkai Virtual DOM baru.
- Presentation Delay: Waktu yang dihabiskan browser untuk menghitung ulang kalkulasi CSS (Style Calculation), menentukan posisi elemen fisik (Layout/Reflow), menggambar piksel (Paint), dan menyusun layer ke GPU (Compositing).
Mendiagnosis Long Tasks Menggunakan Chrome DevTools Performance Panel #
Langkah pertama dalam audit teknis adalah mengidentifikasi Long Tasks—eksekusi kode JavaScript yang memonopoli main thread selama lebih dari 50 milidetik.
Langkah audit di DevTools:
- Buka tab Performance di Google Chrome.
- Centang opsi Web Vitals dan aktifkan simulasi CPU Throttling (4x Slowdown) untuk merefleksikan perangkat mobile rata-rata.
- Klik Record, lakukan interaksi pada antarmuka (misalnya mengetik pada kotak pencarian katalog atau membuka accordion tabel), lalu klik Stop.
- Cari penanda segitiga merah bertuliskan Long Task pada grafik main thread.
// Contoh implementasi PerformanceObserver untuk logging interaksi INP buruk secara real-time
if (typeof window !== 'undefined' && 'PerformanceObserver' in window) {
const observer = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// Filter hanya interaksi dengan durasi melebihi batas 200ms
if (entry.duration > 200) {
console.warn(`[Peringatan INP] Interaksi lambat terdeteksi: ${entry.name}`, {
duration: `${entry.duration.toFixed(1)}ms`,
inputDelay: `${(entry.processingStart - entry.startTime).toFixed(1)}ms`,
processingTime: `${(entry.processingEnd - entry.processingStart).toFixed(1)}ms`,
presentationDelay: `${(entry.startTime + entry.duration - entry.processingEnd).toFixed(1)}ms`,
targetElement: entry.target,
});
}
}
});
// Observe event entries
observer.observe({ type: 'event', buffered: true, durationThreshold: 50 });
}
Teknik Remidiasi 1: Pemecahan Task Menggunakan scheduler.yield()
#
Penyebab paling sering tingginya Processing Time adalah kalkulasi data masif (seperti memfilter 5.000 baris data produk) yang dijalankan secara sinkron di dalam event listener. Browser tidak dapat memperbarui tampilan visual sebelum seluruh loop komputasi selesai.
Solusi modern berstandar Web Platform API adalah menggunakan scheduler.yield() untuk memecah tugas panjang menjadi beberapa mikro-tugas, memberi kesempatan bagi browser untuk merender frame antarmuka di sela-sela pemrosesan:
// Helper scheduler.yield dengan fallback ke setTimeout
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));
}
// Handler pencarian data berat yang dipecah agar main thread tidak terblokir
async function handleFilterData(items: any[], filterQuery: string) {
const results: any[] = [];
const CHUNK_SIZE = 250;
for (let i = 0; i < items.length; i++) {
// Jalankan perbandingan data
if (items[i].title.toLowerCase().includes(filterQuery.toLowerCase())) {
results.push(items[i]);
}
// Setiap 250 item, kembalikan kontrol ke browser untuk merender frame
if (i % CHUNK_SIZE === 0 && i > 0) {
await yieldToMain();
}
}
return results;
}
Teknik Remidiasi 2: Virtualisasi Tabel dan Daftar Panjang #
Ketika merender tabel katalog B2B berisi ratusan baris, merender ribuan elemen DOM fisik sekaligus akan melumpuhkan Presentation Delay browser. Setiap pembaruan state memicu perhitungan Layout dan Recalculate Style yang berat.
Gunakan teknik Virtualization (menggunakan pustaka seperti @tanstack/react-virtual):
- Hanya merender elemen DOM yang sedang berada di dalam viewport layar pengguna (misalnya 15 baris aktif).
- Menggantikan elemen di luar layar dengan elemen kontainer kosong berukuran proporsional.
- Mengurangi total node DOM dari 8.000 elemen menjadi kurang dari 150 elemen, memangkas waktu layout browser dari 180 milidetik menjadi di bawah 8 milidetik.
Teknik Remidiasi 3: Memisahkan Transisi UI dengan useTransition
#
Pada React 18 dan 19, gunakan hook useTransition untuk membedakan antara pembaruan state mendesak (urgent update) dan pembaruan sekunder (non-urgent transition):
import React, { useState, useTransition } from 'react';
export function SearchCatalogInput({ onSearch }: { onSearch: (val: string) => void }) {
const [inputValue, setInputValue] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const nextVal = e.target.value;
// 1. URGENT: Perbarui nilai input teks segera agar karakter muncul tanpa jeda
setInputValue(nextVal);
// 2. NON-URGENT: Tunda pemfilteran daftar data berat di latar belakang
startTransition(() => {
onSearch(nextVal);
});
}
return (
<div className="relative">
<input
type="text"
value={inputValue}
onChange={handleChange}
placeholder="Ketik nama spesifikasi atau SKU..."
className="w-full px-4 py-2 border rounded"
/>
{isPending && <span className="text-xs text-neutral-500">Menyaring data...</span>}
</div>
);
}
Dengan membungkus operasi pemfilteran data berat di dalam startTransition, React tidak akan memblokir pengetikan keyboard pengguna. Main thread tetap leluasa merespons input dengan latensi di bawah 50 milidetik, menjamin skor INP tetap hijau pada kategori Good.
