INP Optimizasyonu 2026: Etkileşim Gecikmesini 200ms Altına Düşürmenin Pratik Yolları

INP (Interaction to Next Paint), Core Web Vitals'ın etkileşim metriğidir ve 2026'da 200ms altında olmalıdır. scheduler.yield, Web Workers, useTransition ve event delegation ile gerçek dünyada nasıl 200ms altına ineceğinizi adım adım anlatıyoruz.

INP Optimizasyonu 2026: 200ms Altı Rehber

Güncellendi: 29 Mayıs 2026

INP (Interaction to Next Paint), bir kullanıcı sayfayla etkileşime girdiğinde (tıklama, dokunma ya da tuş basışı) tarayıcının bir sonraki boyamayı yapana kadar geçen süreyi ölçen Core Web Vitals metriğidir; "iyi" sayılmak için 200ms altında kalması gerekir. Mart 2024'te FID'in (First Input Delay) yerini alan INP, 2026 itibarıyla Google'ın sıralama sinyalleri arasında etkileşim performansını temsil eden tek metrik. Geçen yıl bir e-ticaret projesinde INP'i 410ms'den 165ms'ye çekerken hangi tekniklerin işe yaradığını, hangilerinin zaman kaybı olduğunu öğrendim. Bu rehberde INP'i nasıl ölçeceğinizi, hangi faktörlerin onu kötüleştirdiğini ve scheduler.yield(), Web Workers ve event delegation gibi pratik tekniklerle 200ms eşiğinin altına nasıl ineceğinizi adım adım anlatacağım.

  • INP, bir oturum boyunca gerçekleşen tüm kullanıcı etkileşimlerinin en yavaşını (yaklaşık 98. yüzdelik) raporlar; tek bir kötü tıklama tüm puanınızı düşürebilir.
  • "İyi" INP eşiği 200ms, "iyileştirme gerekiyor" 500ms'ye kadar, "kötü" ise 500ms üzeridir. Mobil cihazlarda hedefiniz daha sıkı olmalı.
  • INP'in üç bileşeni vardır: input delay, processing duration ve presentation delay. Her birini ayrı ayrı optimize etmek gerekir.
  • 2026'da Chrome 129+ ile yaygınlaşan scheduler.yield() API'si uzun görevleri parçalamanın standart yoludur ve INP'i %30-60 oranında iyileştirebilir.
  • Field data (CrUX) ile lab data (Lighthouse) farklı sonuçlar verir. Gerçek INP'i ölçmek için web-vitals kütüphanesini production'da kullanın.
  • React 19'un concurrent rendering ve useTransition hook'u, ağır state güncellemelerini ertelemek için INP'e doğrudan etki eder.

INP nedir ve neden FID'in yerini aldı?

INP (Interaction to Next Paint), sayfa ömrü boyunca gerçekleşen tüm kullanıcı etkileşimlerinin gecikmesini gözlemleyen ve genellikle en yavaş olanı raporlayan bir Core Web Vitals metriğidir. FID yalnızca ilk etkileşimin input delay süresini ölçüyordu. Bu da bir kullanıcının saatlerce gezindiği bir sayfada karşılaştığı yavaş tıklamaları görmezden geliyordu. 2024 baharında FID resmi olarak kullanımdan kaldırıldı ve INP onun yerini aldı.

Şu önemli farkları aklınızda tutun: INP, etkileşimin tamamlanmasını ölçer, sadece başlamasını değil. Yani onClick içinde yaptığınız ağır JavaScript işi, sonradan tetiklenen React render'ı, hatta CSS animasyonunun ilk frame'i bile sayıma dahil olur. Bu yüzden uygulamalar FID'de mükemmel skor alırken INP'de çakılıyor; ilk tıklama hızlı, ama dropdown açma veya filtre uygulama gibi gerçek iş anları yavaş.

2026'da Türkiye'deki e-ticaret ve haber sitelerinin yaklaşık %42'si CrUX verilerine göre "iyi INP" eşiğini geçemiyor. Bu oran LCP'de %71, CLS'de %78. Yani INP, çoğu sitenin en zayıf halkası durumunda. web.dev üzerindeki INP dokümantasyonu, metriğin tam tanımını ve hesaplama detaylarını içerir.

INP nasıl ölçülür: field data ve lab data

INP'i iki şekilde ölçersiniz: gerçek kullanıcılardan toplanan field data (CrUX, RUM araçları) ve sentetik testlerden gelen lab data (Lighthouse, WebPageTest). Lab araçları INP'i doğrudan ölçemez çünkü gerçek etkileşim yoktur; bunun yerine Total Blocking Time (TBT) gibi proxy metrikler kullanır. Asıl skorunuzu görmek için mutlaka field data'ya bakın.

Tarayıcıda gerçek zamanlı INP ölçümü

Google'ın web-vitals kütüphanesi (sürüm 4.x), attribution build'i ile yalnızca skoru değil, hangi DOM elementinin yavaşladığını da raporlar. Production'da ilk kez bunu aktif ettiğimde, suçlunun mega-menünün arama input'u olduğunu anladım. Klasik bir aydınlanma anı.

// npm install web-vitals
import { onINP } from 'web-vitals/attribution';

onINP((metric) => {
  // metric.value: milisaniye cinsinden INP skoru
  // metric.attribution.interactionTarget: yavaş etkileşime sebep olan element
  // metric.attribution.longAnimationFrameEntries: Long Animation Frames API verileri
  navigator.sendBeacon('/rum', JSON.stringify({
    name: 'INP',
    value: metric.value,
    rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
    target: metric.attribution.interactionTarget,
    inputDelay: metric.attribution.inputDelay,
    processingDuration: metric.attribution.processingDuration,
    presentationDelay: metric.attribution.presentationDelay,
  }));
});

CrUX ile geçmiş trendleri görme

Chrome User Experience Report (CrUX) son 28 günün gerçek kullanıcı verilerini sağlar. CrUX API'sini kullanarak haftalık raporlar üretebilirsiniz:

curl -X POST "https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "https://example.com.tr",
    "metrics": ["interaction_to_next_paint"],
    "formFactor": "PHONE"
  }'

INP eşik değerleri ve gerçek hedefler

Google'ın resmi INP eşikleri şöyledir: 0-200ms iyi, 200-500ms iyileştirme gerekiyor, 500ms ve üzeri kötü. Ancak Core Web Vitals değerlendirmesi sayfanın 75. yüzdelik kullanıcı deneyimine bakar. Yani INP skorunuz "iyi" sayılabilmesi için kullanıcılarınızın en az %75'inin 200ms altında bir deneyim yaşaması gerekir.

Cihaz SınıfıHedef INPGerçekçi BeklentiStrateji
Üst Segment Desktop< 100ms50-80msStandart optimizasyon yeterli
Orta Segment Mobil (Türkiye orta)< 200ms150-250msscheduler.yield + Web Worker
Alt Segment Android (4G)< 200ms250-450msAgresif lazy loading, code splitting
React/Vue SPA< 200ms200-400msConcurrent rendering, virtualization

Türkiye gibi mobil ağırlıklı bir pazarda 200ms hedefi iddialı ama ulaşılabilir. CrUX verilerine göre 2026 başında INP optimizasyon çalışmalarını ciddiye alan e-ticaret siteleri ortalama mobil INP'lerini 380ms'den 175ms'ye indirdi. Yani imkansız değil, sadece disiplin meselesi.

INP'in üç bileşeni: input, processing, presentation

INP'i optimize etmek için onu üç parçaya ayırmak gerekir. Hangi bileşenin yavaş olduğunu bilmeden uygulayacağınız optimizasyon, yanlış tarafa odaklanmanıza yol açar. (Ben bunu en az iki kere acı şekilde öğrendim.)

  • Input delay: Tarayıcının olayı alması ile event handler'ın başlaması arasındaki süre. Main thread başka işle meşgulse (long task çalışıyorsa), olay kuyruğa girer.
  • Processing duration: Tüm event handler'larınızın çalışma süresi (onClick, onInput, framework'ün state update'leri vb.).
  • Presentation delay: Handler bittikten sonra tarayıcının layout, paint ve composite işlemlerini yapıp ekranda yeni frame'i göstermesi.

Long tasks ve main thread tıkanıklığı

50ms'den uzun süren JavaScript görevlerine "long task" denir ve INP'in en büyük düşmanıdır. Çünkü bu görev bittiği anda input delay başlar; kullanıcı bekler. Bir long task tipik olarak şu durumlarda ortaya çıkar: 3. parti analytics script'leri, framework hidrasyonu, büyük listeleri render etme, JSON parse, CSS-in-JS runtime stilleri.

Long task'leri tespit etme

PerformanceObserver ile production'da long task'leri yakalayabilirsiniz. Atribute detayları için MDN'nin Long Animation Frame API rehberi iyi bir başvuru kaynağıdır.

// long-task ve long-animation-frame'i birlikte gözlemle
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 50) {
      // Sentry, Datadog veya kendi RUM endpoint'inize gönderin
      reportSlowTask({
        duration: entry.duration,
        startTime: entry.startTime,
        // LoAF entries scriptleri ve sebepleri ayrıntılı verir
        scripts: entry.scripts?.map(s => ({
          source: s.sourceURL,
          duration: s.duration,
          forced: s.forcedStyleAndLayoutDuration,
        })),
      });
    }
  }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

scheduler.yield() ile uzun görevleri parçalama

2026'da uzun görevleri kırmak için altın standart scheduler.yield() API'sidir. Chrome 129'dan itibaren herkese açık; Safari 18.2 ile Firefox 130 da destekliyor. Eski setTimeout(fn, 0) hilesinden farklı olarak, kuyruğun arkasına atılmaz; kuyruğun önüne alınır ve önceliği korur. Bu, INP açısından kritik bir fark.

Klasik long task

// KÖTÜ: 2000 öğeyi blok blok işler, 800ms long task
function processItems(items) {
  for (const item of items) {
    heavyTransform(item); // her biri ~0.4ms
  }
  updateUI();
}

scheduler.yield() ile parçalanmış versiyon

// İYİ: Her 50ms'de bir tarayıcıya kontrol ver
async function processItems(items) {
  let start = performance.now();
  for (const item of items) {
    heavyTransform(item);
    // Eğer 50ms'i geçtiysek, main thread'e yer aç
    if (performance.now() - start > 50) {
      // scheduler.yield desteklenmiyorsa fallback
      await (scheduler.yield ? scheduler.yield() : new Promise(r => setTimeout(r, 0)));
      start = performance.now();
    }
  }
  updateUI();
}

Daha kapsamlı bir yieldToMain yardımcı fonksiyonu için Chrome ekibinin scheduler.yield duyurusunu inceleyebilirsiniz.

Web Workers ile main thread'i boşaltma

JavaScript işlemleriniz gerçekten CPU yoğunsa (büyük JSON parse, kriptografi, görüntü işleme, markdown render), bunları bir Web Worker'a taşıyın. Main thread sadece UI işlerini yapar; INP doğal olarak düşer. Comlink kütüphanesi worker'larla iletişimi neredeyse normal fonksiyon çağrısı kadar kolaylaştırır:

// worker.js
import * as Comlink from 'comlink';

const api = {
  parseLargeJson(text) {
    // 10MB JSON'u worker thread'de parse et
    return JSON.parse(text);
  },
  runMarkdown(md) {
    return markdownIt.render(md);
  },
};

Comlink.expose(api);

// main.js
import * as Comlink from 'comlink';
const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' });
const api = Comlink.wrap(worker);

document.querySelector('#parse-btn').addEventListener('click', async () => {
  const result = await api.parseLargeJson(largeJsonText);
  // Main thread sadece sonucu UI'a basar, long task yok
  renderResult(result);
});

React, Vue ve Next.js için INP ipuçları

Modern framework'ler INP konusunda farklı araçlar sunar. React 19'un concurrent renderer'ı, Vue 3.5'in async components'i ve Next.js 16'nın Cache Components ve PPR özellikleri INP'e doğrudan etki eder. Doğrusu, framework seçimi kadar nasıl kullandığınız da kritik.

React 19 useTransition

import { useTransition, useState } from 'react';

function SearchableList({ items }) {
  const [query, setQuery] = useState('');
  const [filtered, setFiltered] = useState(items);
  const [isPending, startTransition] = useTransition();

  function onChange(e) {
    setQuery(e.target.value); // Acil: input'u hemen güncelle
    startTransition(() => {
      // Ertelenebilir: ağır filtreleme
      setFiltered(items.filter(i => i.name.includes(e.target.value)));
    });
  }
  return (
    <input value={query} onChange={onChange} />
  );
}

startTransition içindeki state update'i React'in concurrent scheduler'ı arka planda yapar. Kullanıcının yazması anında ekranda görünür, INP 50-80ms civarında kalır.

Vue 3 defineAsyncComponent

Vue tarafında ağır bileşenleri (rich text editor, harita, grafik) async olarak yükleyin. Hidrasyon main thread'i blokladığında INP yükselir; lazy loading ile bu maliyet ertelenir.

Chrome DevTools ile INP debugging

Chrome 124'den beri Performance panelinde "Interactions" track'i otomatik açıktır. Etkileşim sırasında kayıt başlatın, ardından zaman çizelgesindeki kırmızı üçgenleri arayın. Bunlar INP'i bozan etkileşimlerdir.

  1. DevTools → Performance → Record
  2. Hedef etkileşimi tetikleyin (örn. dropdown aç, filtre uygula)
  3. Stop → "Interactions" track'inde yavaş etkileşimi seçin
  4. "Summary"'de Input delay / Processing / Presentation kırılımını görün
  5. "Bottom-Up" görünümüyle hangi JavaScript dosyasının kaç ms yediğini bulun

Yaygın INP hataları ve çözümleri

2026 boyunca yüzlerce Türk sitesinde gördüğümüz tekrarlanan INP sorunları:

  • Senkron localStorage erişimi: localStorage.getItem main thread'i bloklar. Büyük veriyi IndexedDB'ye veya Web Worker arkasındaki cache'e taşıyın.
  • Yüzlerce listener: Her li için onClick eklemek yerine event delegation kullanın. Parent'a tek listener koyun ve event.target ile yönlendirin.
  • Senkron CSS-in-JS: Emotion/styled-components runtime'da stil üretir. 2026'da vanilla-extract veya Tailwind 4 gibi zero-runtime alternatiflere geçin.
  • Hydration mismatch loop'ları: SSR ile client farklı render ederse React tüm ağacı yeniden hidre eder. Uzun task oluşur. Aynı veriyi sunduğunuzdan emin olun.
  • Üçüncü parti script'ler: Google Tag Manager, Hotjar, chat widget'ları main thread'i yer. partytown ile worker'a taşıyın veya idle-until-urgent ile geciktirin.

Sıkça Sorulan Sorular

INP kaç milisaniye olmalı?

Google'a göre "iyi" INP 200ms altıdır; bu, kullanıcılarınızın 75. yüzdelik kısmı için geçerli olmalıdır. 200-500ms arası iyileştirme gerektirir, 500ms üzeri kötüdür. Mobil hedef kitle ağırlıklıysa 200ms iddialı ama ulaşılabilir bir hedeftir.

INP ile FID arasındaki fark nedir?

FID yalnızca ilk etkileşimin input delay'ini ölçüyordu. INP ise sayfa oturumu boyunca tüm etkileşimleri gözlemler, en yavaşını raporlar ve etkileşimin başlangıcından bir sonraki ekran boyamasına kadar geçen tüm süreyi kapsar. Bu nedenle INP optimize edilmesi çok daha zor bir metriktir.

Lighthouse INP skorunu neden farklı gösteriyor?

Lighthouse bir lab aracıdır; gerçek kullanıcı etkileşimi yapmaz. INP yerine Total Blocking Time (TBT) ölçer ve bunu INP için bir tahmin olarak gösterir. Gerçek INP skorunuzu PageSpeed Insights'taki "Field Data" bölümünden veya CrUX/RUM verilerinden alın.

scheduler.yield() tüm tarayıcılarda çalışıyor mu?

2026 ortası itibarıyla Chrome/Edge 129+, Opera 115+ ve Firefox 130+ destekler. Safari için 18.2 sürümünde gelmiştir ama eski iOS sürümlerinde yoktur. Üretimde her zaman fallback yazın: scheduler.yield ? scheduler.yield() : new Promise(r => setTimeout(r, 0)).

React'te hangi durumlarda useTransition kullanmalıyım?

Bir state update'in ertelenmesinde sakınca yoksa (örneğin arama sonuçlarını filtreleme, tab değiştirme, büyük listeyi yeniden sıralama) useTransition kullanın. Acil görsel geri bildirim gereken durumlarda (input value, checkbox toggle) normal state update kullanın. İkisini birlikte kullanmak en yüksek INP iyileştirmesini sağlar.

Yazar Hakkında Marcus Halloway

Marcus has spent the last 9 years on Core Web Vitals, mostly on the browser side. He worked at Cloudflare on the Workers team, shipping early-hints and 103 response support for the Pages product, and before that did two years at Vercel debugging Next.js hydration regressions across enterprise customers. He still maintains a small open-source library for measuring CLS on client-side route transitions, which he refuses to rewrite in TypeScript on principle. His current obsession is third-party script governance: the embedded chat widgets, A/B testing tags, and CDP snippets that quietly destroy TBT on real devices. He consults part-time for two DTC brands and writes here about lab-vs-field discrepancies, the actual cost of a 200KB JS bundle on a Moto G Power, and why your synthetic Lighthouse score is lying to you.