Panduan Lengkap Optimasi INP (Interaction to Next Paint): Diagnosis, Teknik, dan Implementasi

Panduan praktis mengoptimasi INP — mulai dari diagnosis dengan Long Animation Frames API, memecah long tasks pakai scheduler.yield(), optimasi rendering dengan CSS containment, hingga navigasi instan lewat Speculation Rules API.

Apa Itu Interaction to Next Paint (INP)?

Sejak Maret 2024, Google resmi menggantikan First Input Delay (FID) dengan Interaction to Next Paint (INP) sebagai bagian dari Core Web Vitals. Dan jujur, ini bukan sekadar ganti nama — perubahan ini cukup game-changing.

Kalau FID cuma mengukur latensi dari interaksi pertama pengguna, INP jauh lebih menyeluruh. Metrik ini melacak semua interaksi selama siklus hidup halaman dan melaporkan skor berdasarkan interaksi terburuk (atau yang mendekati terburuk). Jadi, nggak ada lagi halaman yang "terlihat cepat" di awal tapi kemudian terasa lambat.

INP mengukur tiga fase kritis dalam setiap interaksi:

  1. Input Delay — waktu antara pengguna melakukan aksi (klik, tap, keypress) hingga event handler mulai dieksekusi
  2. Processing Time — durasi yang dibutuhkan untuk menjalankan semua event handler terkait
  3. Presentation Delay — waktu dari selesainya event handler hingga browser menampilkan frame visual berikutnya

Skor INP yang baik? Di bawah 200 milidetik. Antara 200-500ms berarti perlu perbaikan, dan di atas 500ms sudah masuk kategori buruk. Yang menarik, data dari tim Web Vitals Google menunjukkan bahwa memperbaiki INP dari 500ms ke 200ms bisa meningkatkan metrik keterlibatan pengguna hingga 22%. Angka yang cukup signifikan, kan?

Mengapa INP Lebih Penting dari FID?

Buat kamu yang sudah nyaman dengan FID, mungkin bertanya-tanya: kenapa sih Google harus mengganti metrik yang sudah mapan?

Jawabannya sederhana — FID punya keterbatasan fundamental yang cukup serius.

FID hanya mengukur input delay dari interaksi pertama. Artinya, ia mengabaikan processing time dan presentation delay. Lebih parahnya lagi, FID cuma memperhatikan interaksi pertama, yang biasanya terjadi saat halaman masih loading dan belum sepenuhnya interaktif. Hasilnya? Halaman yang terasa sangat lambat setelah loading awal bisa saja mendapat skor FID yang sempurna. Ironis, ya.

INP memperbaiki semua keterbatasan ini:

  • Mengukur seluruh durasi interaksi, bukan hanya input delay
  • Memantau semua interaksi sepanjang siklus hidup halaman
  • Melaporkan skor berdasarkan persentil ke-98, sehingga satu outlier tunggal tidak langsung merusak skor keseluruhan
  • Memberikan gambaran yang jauh lebih realistis tentang pengalaman pengguna sebenarnya

Mengidentifikasi Masalah INP dengan Long Animation Frames (LoAF)

Oke, sebelum langsung loncat ke optimasi, kita perlu tahu dulu di mana masalahnya. Nah, di sinilah Long Animation Frames API (LoAF) jadi senjata utama kita.

LoAF adalah API browser yang sudah stabil di Chrome 123. Dia menggantikan Long Tasks API dan memberikan insight yang jauh lebih detail — dan honestly, perbedaannya seperti bumi dan langit.

Apa Itu Animation Frame?

Singkatnya, animation frame mencakup semua pekerjaan browser untuk menghasilkan satu frame visual baru: eksekusi JavaScript, kalkulasi style, layout, paint, dan compositing. Sebuah animation frame dianggap "panjang" kalau durasinya melebihi 50 milidetik.

Kalau Long Tasks API cuma bilang "hey, ada task yang lama nih" tanpa detail lebih lanjut, LoAF memberikan informasi spesifik tentang:

  • Script mana yang menyebabkan frame panjang
  • Fungsi spesifik yang jadi biang keladinya
  • Berapa lama waktu yang dihabiskan untuk style, layout, dan rendering
  • Apakah ada forced reflow atau synchronous layout

Implementasi Monitoring LoAF

Berikut cara mengimplementasikan monitoring LoAF di situs kamu:

// Observer untuk mendeteksi Long Animation Frames
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Hanya proses frame yang lebih dari 50ms
    if (entry.duration > 50) {
      console.log('Long Animation Frame terdeteksi:', {
        durasi: `${entry.duration}ms`,
        blockingDuration: `${entry.blockingDuration}ms`,
        startTime: entry.startTime,
        renderStart: entry.renderStart,
        styleAndLayoutStart: entry.styleAndLayoutStart,
      });

      // Analisis script yang berkontribusi
      for (const script of entry.scripts) {
        console.log('Script penyebab:', {
          sourceURL: script.sourceURL,
          sourceFunctionName: script.sourceFunctionName,
          invokerType: script.invokerType,
          invoker: script.invoker,
          duration: `${script.duration}ms`,
          executionStart: script.executionStart,
          forcedStyleAndLayoutDuration:
            `${script.forcedStyleAndLayoutDuration}ms`,
        });
      }
    }
  }
});

// Mulai observasi
observer.observe({ type: 'long-animation-frame', buffered: true });

Dengan data dari LoAF, kamu bisa tahu persis script atau fungsi mana yang perlu dioptimasi. Ini jauh lebih efektif dibandingkan menebak-nebak atau cuma mengandalkan skor Lighthouse (yang, let's be honest, kadang bikin frustrasi sendiri).

Mengirim Data LoAF ke Analytics

Untuk monitoring di lingkungan produksi, kamu bisa mengirim data LoAF ke layanan analytics. Ini penting supaya kamu punya data real dari pengguna sungguhan, bukan cuma dari lab testing:

// Kirim data LoAF ke endpoint analytics
function reportLoAF(entry) {
  const data = {
    url: window.location.href,
    duration: entry.duration,
    blockingDuration: entry.blockingDuration,
    timestamp: Date.now(),
    scripts: entry.scripts.map(s => ({
      source: s.sourceURL,
      function: s.sourceFunctionName,
      duration: s.duration,
      type: s.invokerType,
    })),
  };

  // Gunakan sendBeacon untuk pengiriman yang reliable
  navigator.sendBeacon('/api/analytics/loaf',
    JSON.stringify(data)
  );
}

const loafObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 100) { // Threshold lebih tinggi untuk produksi
      reportLoAF(entry);
    }
  }
});

loafObserver.observe({ type: 'long-animation-frame', buffered: true });

Teknik Optimasi INP: Memecah Long Tasks

Penyebab utama skor INP yang buruk adalah long tasks — blok JavaScript yang berjalan terlalu lama di main thread tanpa memberikan kesempatan browser untuk memproses input pengguna. So, mari kita bahas beberapa teknik untuk memecahnya.

1. Menggunakan scheduler.yield()

Ini salah satu API favorit saya untuk optimasi INP. scheduler.yield() berbeda dari setTimeout yang menempatkan kelanjutan task di akhir antrean — scheduler.yield() menempatkannya di depan antrean. Artinya, task kamu dilanjutkan lebih cepat setelah browser sempat memproses interaksi pengguna.

// Contoh: Memproses array besar dengan yield
async function processLargeArray(items) {
  const results = [];

  for (let i = 0; i < items.length; i++) {
    // Proses satu item
    results.push(heavyComputation(items[i]));

    // Yield setiap 5 item agar browser bisa memproses interaksi
    if (i % 5 === 0) {
      await scheduler.yield();
    }
  }

  return results;
}

// Dengan fallback untuk browser yang belum mendukung
async function yieldToMain() {
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }
  // Fallback ke setTimeout
  return new Promise(resolve => setTimeout(resolve, 0));
}

2. Menggunakan Web Workers untuk Komputasi Berat

Untuk komputasi yang benar-benar berat (sorting ribuan item, parsing data kompleks, dll.), pindahkan pekerjaan ke Web Worker supaya main thread tetap responsif:

// worker.js
self.addEventListener('message', (event) => {
  const { data, operation } = event.data;

  switch (operation) {
    case 'sort':
      const sorted = data.sort((a, b) => {
        // Algoritma sorting kompleks
        return complexCompare(a, b);
      });
      self.postMessage({ result: sorted });
      break;

    case 'filter':
      const filtered = data.filter(item =>
        complexFilterLogic(item)
      );
      self.postMessage({ result: filtered });
      break;
  }
});

// main.js — Penggunaan di main thread
const worker = new Worker('/worker.js');

function processDataOffThread(data, operation) {
  return new Promise((resolve) => {
    worker.onmessage = (event) => {
      resolve(event.data.result);
    };
    worker.postMessage({ data, operation });
  });
}

// Contoh penggunaan
document.getElementById('sortBtn').addEventListener('click', async () => {
  // Main thread tetap responsif saat sorting berlangsung
  const sorted = await processDataOffThread(largeDataset, 'sort');
  renderResults(sorted);
});

3. Menghindari Forced Synchronous Layout

Forced synchronous layout — atau yang sering disebut layout thrashing — adalah salah satu penyebab paling umum skor INP yang jelek. Ini terjadi ketika kamu mengubah DOM lalu langsung membaca properti layout, memaksa browser melakukan kalkulasi ulang secara sinkron.

Saya sudah beberapa kali menemukan ini jadi sumber masalah di proyek nyata, dan perbaikannya seringkali cukup simpel:

// BURUK - Menyebabkan layout thrashing
function updateElements(elements) {
  elements.forEach(el => {
    // Baca layout property
    const height = el.offsetHeight;
    // Tulis perubahan — memaksa recalculation di iterasi berikutnya
    el.style.height = (height * 2) + 'px';
  });
}

// BAIK - Batch read dan write secara terpisah
function updateElementsOptimized(elements) {
  // Phase 1: Baca semua nilai dulu
  const heights = elements.map(el => el.offsetHeight);

  // Phase 2: Tulis semua perubahan sekaligus
  elements.forEach((el, i) => {
    el.style.height = (heights[i] * 2) + 'px';
  });
}

// LEBIH BAIK - Gunakan requestAnimationFrame
function updateElementsRAF(elements) {
  const heights = elements.map(el => el.offsetHeight);

  requestAnimationFrame(() => {
    elements.forEach((el, i) => {
      el.style.height = (heights[i] * 2) + 'px';
    });
  });
}

4. Debouncing dan Throttling Event Handler

Untuk event yang sering dipicu — scroll, resize, atau input — pakai debounce atau throttle. Tanpa ini, handler kamu bisa dipanggil ratusan kali per detik, dan itu jelas bikin main thread kewalahan:

// Throttle untuk event yang perlu respons berkala (scroll, resize)
function throttle(fn, delay) {
  let lastCall = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastCall >= delay) {
      lastCall = now;
      fn.apply(this, args);
    }
  };
}

// Debounce untuk event yang hanya perlu respons terakhir (search input)
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Penggunaan
window.addEventListener('scroll', throttle(() => {
  updateScrollIndicator();
}, 100));

searchInput.addEventListener('input', debounce((e) => {
  performSearch(e.target.value);
}, 300));

Optimasi Rendering untuk INP yang Lebih Baik

Selain memecah long tasks, cara browser melakukan rendering juga sangat berpengaruh pada skor INP. Area ini sering terlewatkan, padahal dampaknya bisa cukup besar.

Menggunakan CSS Containment

CSS Containment pada dasarnya memberi tahu browser bahwa elemen tertentu dan kontennya independen dari sisa halaman. Dengan informasi ini, browser bisa mengoptimasi kalkulasi layout dan paint secara signifikan:

/* Containment untuk komponen yang sering berubah */
.widget-container {
  contain: layout style paint;
}

/* Content-visibility untuk elemen off-screen */
.article-card {
  content-visibility: auto;
  contain-intrinsic-size: 0 400px;
}

/* Isolasi compositing layer */
.animated-element {
  will-change: transform;
  /* Atau gunakan translateZ untuk memaksa layer baru */
  transform: translateZ(0);
}

Mengoptimasi CSS Selector

Ini hal yang kadang diabaikan developer — CSS selector yang kompleks bisa memperlambat style recalculation. Kenapa? Karena browser mengevaluasi selector dari kanan ke kiri. Selector yang terlalu umum di sisi kanan memaksa browser memeriksa banyak elemen:

/* BURUK - Selector terlalu dalam dan kompleks */
.main-content .sidebar .widget-list li a span.icon {
  color: red;
}

/* BAIK - Lebih spesifik dan dangkal */
.widget-icon {
  color: red;
}

/* BURUK - Universal selector di sisi kanan */
.sidebar * {
  box-sizing: border-box;
}

/* BAIK - Gunakan class spesifik */
.sidebar-item {
  box-sizing: border-box;
}

Speculation Rules API: Navigasi Instan dengan INP Nol

Nah, ini yang menurut saya paling exciting. Speculation Rules API adalah salah satu teknik paling revolusioner untuk performa web saat ini. API ini memungkinkan browser melakukan prefetch atau bahkan prerender halaman yang kemungkinan akan dikunjungi pengguna — sehingga navigasi terasa benar-benar instan.

Perbedaan Prefetch dan Prerender

  • Prefetch — mengunduh dokumen HTML (dan mungkin sub-resource) dari halaman target, lalu menyimpannya di cache. Ini menghemat waktu network, tapi browser masih perlu mem-parse dan merender halaman saat navigasi terjadi.
  • Prerender — ini yang lebih canggih. Browser melakukan rendering lengkap halaman di background, termasuk eksekusi JavaScript. Ketika pengguna bernavigasi, halaman langsung muncul — hampir tanpa delay sama sekali.

Implementasi Speculation Rules

<!-- Pendekatan sederhana: prerender link tertentu -->
<script type="speculationrules">
{
  "prerender": [
    {
      "urls": ["/produk", "/tentang-kami", "/kontak"]
    }
  ]
}
</script>

<!-- Pendekatan dinamis: prerender berdasarkan hover -->
<script type="speculationrules">
{
  "prerender": [
    {
      "where": {
        "and": [
          { "href_matches": "/*" },
          { "not": { "href_matches": "/logout" } },
          { "not": { "href_matches": "/api/*" } }
        ]
      },
      "eagerness": "moderate"
    }
  ]
}
</script>

Tingkat Eagerness

Speculation Rules API menyediakan empat tingkat eagerness yang mengontrol kapan spekulasi dimulai:

  • immediate — spekulasi dimulai segera setelah rules diparse
  • eager — mirip immediate, tapi browser punya fleksibilitas untuk menunda sedikit
  • moderate — dimulai saat pengguna hover pada link selama 200ms, atau saat pointerdown
  • conservative — dimulai hanya saat pointerdown atau touchstart

Untuk kebanyakan kasus, moderate adalah pilihan terbaik — keseimbangan yang pas antara performa dan penggunaan resource. Dan hasilnya bukan main-main: studi kasus dari Ray-Ban menunjukkan implementasi Speculation Rules API menghasilkan peningkatan konversi hingga 2x lipat dan pengurangan exit rate sebesar 13%.

Implementasi Dinamis dengan JavaScript

// Tambahkan speculation rules secara dinamis
function addSpeculationRules(urls) {
  // Periksa dukungan browser
  if (!HTMLScriptElement.supports ||
      !HTMLScriptElement.supports('speculationrules')) {
    console.log('Browser tidak mendukung Speculation Rules API');
    return;
  }

  const script = document.createElement('script');
  script.type = 'speculationrules';
  script.textContent = JSON.stringify({
    prerender: [{
      urls: urls,
      eagerness: 'moderate'
    }]
  });

  document.head.appendChild(script);
}

// Prerender halaman produk yang sering dikunjungi
addSpeculationRules([
  '/produk/terlaris',
  '/kategori/promo',
  '/keranjang'
]);

Mengoptimasi bfcache untuk Navigasi Back/Forward Instan

Back/Forward Cache (bfcache) menyimpan snapshot lengkap halaman di memori saat pengguna bernavigasi, jadi navigasi back dan forward terasa instan. Ini fitur browser yang sudah ada cukup lama, tapi banyak developer tanpa sadar memblokir-nya.

Seberapa besar dampaknya? Data dari Ray-Ban menunjukkan bahwa mengaktifkan bfcache menghasilkan peningkatan LCP hampir 30% dan peningkatan CLS sebesar 83%. Angka yang lumayan fantastis.

Hal yang Dapat Memblokir bfcache

Beberapa pola koding yang mungkin kamu pakai sehari-hari ternyata bisa mencegah halaman disimpan di bfcache:

// BURUK - Event unload memblokir bfcache
window.addEventListener('unload', () => {
  sendAnalytics();
});

// BAIK - Gunakan pagehide sebagai gantinya
window.addEventListener('pagehide', (event) => {
  if (!event.persisted) {
    // Halaman benar-benar di-unload
    sendAnalytics();
  }
});

// BAIK - Gunakan visibilitychange untuk kasus umum
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    navigator.sendBeacon('/api/analytics', data);
  }
});

// Menangani restorasi dari bfcache
window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Halaman direstorasi dari bfcache
    // Perbarui data yang mungkin sudah stale
    refreshTimestamps();
    checkAuthStatus();
  }
});

Checklist Kompatibilitas bfcache

  • Jangan gunakan event listener unload — ganti dengan pagehide
  • Tutup koneksi WebSocket atau IndexedDB di pagehide dan buka kembali di pageshow
  • Hindari header Cache-Control: no-store kalau nggak benar-benar diperlukan
  • Pastikan tidak ada referensi ke window.opener
  • Gunakan Chrome DevTools > Application > Back/forward cache untuk menguji eligibilitas

Optimasi Third-Party Scripts

Ini topik yang bikin saya sedikit gemas. Script pihak ketiga — analytics, iklan, chat widget, social media embeds — seringkali jadi biang keladi utama skor INP yang buruk. Mereka berjalan di main thread yang sama dengan kode kamu dan sering melakukan pekerjaan berat tanpa peduli optimasi.

Strategi Loading Third-Party Scripts

<!-- BURUK - Blocking script -->
<script src="https://analytics.example.com/tracker.js"></script>

<!-- LEBIH BAIK - Async loading -->
<script async src="https://analytics.example.com/tracker.js"></script>

<!-- TERBAIK - Lazy load setelah interaksi pengguna -->
<script>
function loadAnalytics() {
  const script = document.createElement('script');
  script.src = 'https://analytics.example.com/tracker.js';
  document.head.appendChild(script);
  // Hapus listener setelah load pertama
  removeInteractionListeners();
}

function removeInteractionListeners() {
  ['click', 'scroll', 'keydown', 'touchstart'].forEach(event => {
    document.removeEventListener(event, loadAnalytics, { once: true });
  });
}

// Load analytics hanya setelah interaksi pertama
['click', 'scroll', 'keydown', 'touchstart'].forEach(event => {
  document.addEventListener(event, loadAnalytics, { once: true });
});
</script>

Mengisolasi Third-Party Scripts dengan iframe

Kalau ada script yang benar-benar berat dan nggak bisa dioptimasi lebih lanjut, pertimbangkan untuk menjalankannya di iframe terpisah. Dengan cara ini, mereka mendapat main thread sendiri dan nggak mengganggu responsivitas halaman utama:

// Jalankan third-party script di iframe terisolasi
function loadInIframe(scriptUrl) {
  const iframe = document.createElement('iframe');
  iframe.style.display = 'none';
  iframe.sandbox = 'allow-scripts';

  iframe.srcdoc = `
    <!DOCTYPE html>
    <html>
      <head>
        <script src="${scriptUrl}"></script>
      </head>
      <body></body>
    </html>
  `;

  document.body.appendChild(iframe);

  // Komunikasi via postMessage
  iframe.contentWindow.postMessage(
    { type: 'init', config: analyticsConfig },
    '*'
  );
}

Mengukur dan Memantau INP di Produksi

Optimasi tanpa pengukuran itu cuma menebak. Titik. Berikut cara mengimplementasikan monitoring INP yang komprehensif menggunakan library web-vitals dari Google:

import { onINP } from 'web-vitals';

// Monitoring INP dasar
onINP((metric) => {
  console.log('INP:', metric.value, 'ms');
  console.log('Rating:', metric.rating); // 'good', 'needs-improvement', 'poor'

  // Dapatkan atribusi detail
  if (metric.attribution) {
    const { eventTarget, eventType, loadState } = metric.attribution;
    console.log('Element:', eventTarget);
    console.log('Event type:', eventType);
    console.log('Load state:', loadState);

    // Detail breakdown
    const {
      inputDelay,
      processingDuration,
      presentationDelay
    } = metric.attribution;

    console.log(`Input Delay: ${inputDelay}ms`);
    console.log(`Processing: ${processingDuration}ms`);
    console.log(`Presentation: ${presentationDelay}ms`);
  }
});

// Kirim ke analytics service
onINP((metric) => {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating,
    delta: metric.delta,
    id: metric.id,
    page: window.location.pathname,
    attribution: metric.attribution,
  });

  // Gunakan sendBeacon agar data terkirim meski tab ditutup
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/api/web-vitals', body);
  } else {
    fetch('/api/web-vitals', {
      body,
      method: 'POST',
      keepalive: true,
    });
  }
}, { reportAllChanges: true });

Studi Kasus: Mengoptimasi INP pada Aplikasi E-Commerce

Teori itu penting, tapi mari kita lihat contoh yang lebih konkret. Berikut skenario optimasi INP pada aplikasi e-commerce yang punya masalah responsivitas — pendekatan yang bisa kamu terapkan langsung di proyek sendiri.

Masalah yang Diidentifikasi

Menggunakan LoAF dan web-vitals, kita menemukan tiga masalah utama:

  1. Filter produk memicu re-render seluruh daftar (processing time: 380ms)
  2. Tombol "Tambah ke Keranjang" tertunda karena layout thrashing (presentation delay: 250ms)
  3. Script analytics pihak ketiga memblokir main thread (input delay: 180ms)

Total INP awal: 680ms. Ouch.

Solusi 1: Virtualisasi Daftar Produk

// Sebelum: Render semua produk sekaligus
function renderProducts(products) {
  const container = document.getElementById('product-list');
  container.innerHTML = products.map(p =>
    createProductCard(p)
  ).join('');
}

// Sesudah: Virtualisasi — hanya render yang terlihat
class VirtualProductList {
  constructor(container, items, itemHeight = 300) {
    this.container = container;
    this.items = items;
    this.itemHeight = itemHeight;
    this.visibleCount = Math.ceil(
      window.innerHeight / itemHeight
    ) + 2; // Buffer

    this.container.style.height =
      `${items.length * itemHeight}px`;
    this.container.style.position = 'relative';

    this.render();
    this.setupScrollListener();
  }

  render() {
    const scrollTop = this.container.parentElement.scrollTop;
    const startIndex = Math.max(0,
      Math.floor(scrollTop / this.itemHeight) - 1
    );
    const endIndex = Math.min(
      this.items.length,
      startIndex + this.visibleCount
    );

    // Hanya render item yang terlihat
    const fragment = document.createDocumentFragment();
    for (let i = startIndex; i < endIndex; i++) {
      const card = this.createCard(this.items[i]);
      card.style.position = 'absolute';
      card.style.top = `${i * this.itemHeight}px`;
      fragment.appendChild(card);
    }

    this.container.innerHTML = '';
    this.container.appendChild(fragment);
  }

  setupScrollListener() {
    const parent = this.container.parentElement;
    parent.addEventListener('scroll',
      throttle(() => this.render(), 50)
    );
  }
}

Solusi 2: Optimasi Tombol Keranjang

// Sebelum: Layout thrashing pada update keranjang
function addToCart(product) {
  cart.push(product);

  // Baca layout
  const cartHeight = cartElement.offsetHeight;
  // Tulis — memicu forced reflow!
  cartElement.style.height = `${cartHeight + 60}px`;
  // Baca lagi — forced reflow lagi!
  const cartWidth = cartElement.offsetWidth;
  cartElement.style.minWidth = `${cartWidth}px`;

  updateCartBadge();
  showNotification('Ditambahkan ke keranjang');
}

// Sesudah: Batch DOM updates dengan requestAnimationFrame
function addToCartOptimized(product) {
  cart.push(product);

  // Gunakan CSS transitions alih-alih manipulasi manual
  cartElement.classList.add('item-added');

  // Batch visual updates di frame berikutnya
  requestAnimationFrame(() => {
    updateCartBadge();
    showNotification('Ditambahkan ke keranjang');

    // Hapus class animasi setelah selesai
    setTimeout(() => {
      cartElement.classList.remove('item-added');
    }, 300);
  });
}

Hasil Optimasi

Setelah menerapkan ketiga solusi, hasilnya cukup memuaskan (kalau nggak mau dibilang luar biasa):

  • INP turun dari 680ms ke 145ms — perbaikan 78%
  • Processing time filter produk: 380ms → 45ms
  • Presentation delay tombol keranjang: 250ms → 30ms
  • Input delay dari third-party scripts: 180ms → 20ms
  • Bounce rate menurun 15%
  • Konversi meningkat 8%

Checklist Optimasi INP

Oke, banyak yang sudah kita bahas. Berikut rangkuman langkah-langkah yang bisa kamu ikuti — anggap saja ini sebagai "cheat sheet" optimasi INP:

  1. Ukur baseline — gunakan web-vitals library dan field data dari CrUX
  2. Identifikasi bottleneck — gunakan LoAF API untuk menemukan script dan fungsi yang bermasalah
  3. Pecah long tasks — gunakan scheduler.yield(), Web Workers, atau requestIdleCallback()
  4. Hindari layout thrashing — batch read dan write DOM, gunakan requestAnimationFrame()
  5. Optimasi third-party — lazy load, isolasi di iframe, atau tunda hingga interaksi pengguna
  6. Gunakan CSS containmentcontain dan content-visibility untuk mengurangi kerja rendering
  7. Implementasi Speculation Rules — untuk navigasi instan antar halaman
  8. Aktifkan bfcache — pastikan halaman eligible untuk Back/Forward Cache
  9. Monitor di produksi — pantau skor INP secara berkelanjutan dan segera tindak lanjuti regresi
  10. Prioritaskan mobile — uji di perangkat mobile nyata, karena CPU-nya jauh lebih lemah dari laptop kamu

Tools yang Direkomendasikan

Beberapa tools yang sangat membantu untuk mengukur dan mengoptimasi INP (saya pribadi pakai hampir semuanya):

  • Chrome DevTools Performance Panel — profiling detail di lingkungan development
  • web-vitals library — pengukuran Core Web Vitals di field dengan atribusi
  • DebugBear — monitoring performa web berkelanjutan dengan alert otomatis
  • Google PageSpeed Insights — analisis performa dengan data CrUX dan Lighthouse
  • Chrome User Experience Report (CrUX) — data field dari pengguna Chrome nyata
  • Lighthouse — audit performa komprehensif dengan rekomendasi actionable
  • WebPageTest — pengujian performa mendalam dari berbagai lokasi dan perangkat

Kesimpulan

Optimasi INP bukan cuma soal mengejar angka — ini tentang memberikan pengalaman yang responsif dan menyenangkan bagi pengguna. Dengan INP yang sekarang jadi Core Web Vital resmi dan faktor ranking Google, mengabaikan responsivitas halaman bukan lagi pilihan yang bijak.

Mulailah dari yang basic: ukur INP kamu pakai field data, identifikasi bottleneck dengan LoAF API, lalu terapkan teknik-teknik yang sudah kita bahas. Pecah long tasks dengan scheduler.yield(), hindari layout thrashing, optimasi third-party scripts, dan manfaatkan Speculation Rules API untuk navigasi yang terasa instan.

Dan ingat — performa web itu maraton, bukan sprint. Bangun sistem monitoring yang berkelanjutan, tetapkan performance budget, dan jadikan optimasi INP sebagai bagian dari workflow development sehari-hari. Dengan pendekatan yang konsisten, skor INP di bawah 200ms itu sangat achievable. Selamat mengoptimasi!

Tentang Penulis Editorial Team

Our team of expert writers and editors.