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 (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:
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 INP
Gerçekçi Beklenti
Strateji
Üst Segment Desktop
< 100ms
50-80ms
Standart optimizasyon yeterli
Orta Segment Mobil (Türkiye orta)
< 200ms
150-250ms
scheduler.yield + Web Worker
Alt Segment Android (4G)
< 200ms
250-450ms
Agresif lazy loading, code splitting
React/Vue SPA
< 200ms
200-400ms
Concurrent 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();
}
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.
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.
DevTools → Performance → Record
Hedef etkileşimi tetikleyin (örn. dropdown aç, filtre uygula)
Stop → "Interactions" track'inde yavaş etkileşimi seçin
"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.
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.