Search Console'un Core Web Vitals raporunu açtınız ve mobilde kırmızı bir blok duruyor: "INP zayıf, 340 ms". Aynı sayfayı PageSpeed Insights'a soktuğunuzda 96 puan alıyor, laboratuvar bölümünde her şey yeşil. Sitede kendiniz geziyorsunuz, hızlı açılıyor. Ama Google ısrarla sayfanın yavaş olduğunu söylüyor.
Bu çelişki INP'nin en can sıkıcı tarafıdır ve nedeni basittir: INP sayfanın açılma hızını değil, açıldıktan sonra tıklamaya yanıt verme hızını ölçer. Sizin gördüğünüz 96 puan yükleme performansıdır. Kullanıcı ise menüye dokunuyor, sepete ekle butonuna basıyor, filtre değiştiriyor ve arada yarım saniye hiçbir şey olmuyor. Üstelik bu gecikme masaüstünde neredeyse hiç hissedilmez; orta seviye bir Android telefonda ise fazlasıyla belirgindir.
Bu yazıda tanımı hızlıca geçip doğrudan işe gireceğiz: önce gerçek kullanıcı verisinden hangi sayfanın kötü olduğunu bulacağız, sonra DevTools ve Long Animation Frames API ile hangi kod parçasının ana iş parçacığını tıkadığını yakalayacağız, ardından üçüncü parti scriptler, ağır event handler'lar ve şişmiş DOM için tek tek çözüm uygulayacağız. Son bölümde de WordPress kullanıyorsanız suçlu eklentiyi ölçerek bulmanın yöntemini vereceğim.
INP Tam Olarak Neyi Ölçüyor ve FID'den Farkı Ne?#
INP (Interaction to Next Paint), sayfa ömrü boyunca gerçekleşen tüm etkileşimlerin gecikmesini izler ve bunların neredeyse en kötüsünü rapor eder. Tek bir etkileşim değil, kullanıcının yaşadığı en kötü deneyimlerden biri ölçülür. Bir etkileşimin süresi ise şu üç parçanın toplamıdır:
| Aşama | Ne oluyor | Tipik suçlu |
|---|---|---|
| Girdi gecikmesi | Tıklama olayı tetiklendi ama ana iş parçacığı meşgul, handler çalışamıyor | Arka planda çalışan analitik/reklam scripti |
| İşlem süresi | Event handler kodu çalışıyor | Ağır JavaScript, senkron fetch, döngüde DOM okuma-yazma |
| Sunum gecikmesi | Tarayıcı yeni kareyi hesaplayıp boyuyor | Şişmiş DOM, pahalı CSS, büyük layout hesabı |
Eşikler net: 200 ms ve altı iyi, 200-500 ms arası iyileştirme gerekli, 500 ms üstü zayıf. Ölçüm 75. yüzdelik dilimden alınır; yani ziyaretçilerinizin dörtte birinin yaşadığı deneyim raporlanır, ortalama değil.
INP, 2024 Mart'ında FID'in yerini aldı ve bu değişim birçok sitenin skorunu tek gecede düşürdü. Nedeni şu: FID yalnızca ilk etkileşimin girdi gecikmesini ölçüyordu, handler'ın ne kadar sürdüğüne hiç bakmıyordu. Yani onclick içinde 800 ms süren bir kod yazsanız bile FID mükemmel çıkabiliyordu. INP ise handler'ın çalışma süresini ve ekrana yansıma süresini de sayar. Sonuç olarak FID'de yeşil olan siteler INP'de kırmızıya döndü — kod değişmedi, ölçüm dürüstleşti.
Bu üçlü ayrım pratikte çok işe yarar: düzeltmeye başlamadan önce hangi aşamanın uzun sürdüğünü bilirseniz, yanlış yerde vakit kaybetmezsiniz. Girdi gecikmesi yüksekse sorun sizin kodunuzda değil, arka planda çalışan başka bir şeydedir.
Hangi Sayfanın INP'si Kötü: Saha Verisinden Başlayın#
Rastgele sayfa optimize etmeyin. Google'ın kullandığı veri, gerçek Chrome kullanıcılarından toplanan CrUX verisidir ve son 28 günlük pencereyi kapsar. Bu yüzden ilk durağınız Search Console'daki Core Web Vitals raporudur.
Raporda "INP zayıf" grubuna tıkladığınızda Google size URL'leri tek tek değil, benzer sayfa grupları halinde gösterir. Örnek bir URL verir ve "bu gruptaki 1.240 sayfa aynı sorundan etkileniyor" der. Bu gruplama önemlidir: genellikle tek bir şablon (ürün sayfası, kategori sayfası, blog yazısı) sorunludur ve o şablonu düzeltmek binlerce URL'yi birden çözer. Search Console'u henüz kurmadıysanız Google Search Console kurulumu yazısındaki doğrulama adımlarıyla başlayın.
Search Console'un iki eksiği vardır: veri 28 günlük olduğu için değişikliklerin etkisini görmek haftalar alır ve size hangi öğenin yavaş olduğunu söylemez. Bu ikisini kendi RUM ölçümünüzle kapatabilirsiniz. web-vitals kütüphanesinin attribution sürümü, INP'yi ölçmekle kalmaz, hangi elementin ve hangi aşamanın suçlu olduğunu da verir:
<script type="module">
import {onINP} from 'https://unpkg.com/web-vitals@5/attribution?module';
onINP(({value, attribution}) => {
navigator.sendBeacon('/rum', JSON.stringify({
sayfa: location.pathname,
inp: Math.round(value),
hedefElement: attribution.interactionTarget,
etkilesimTuru: attribution.interactionType,
girdiGecikmesi: Math.round(attribution.inputDelay),
islemSuresi: Math.round(attribution.processingDuration),
sunumGecikmesi: Math.round(attribution.presentationDelay)
}));
});
</script>
Bu veriyi birkaç gün topladığınızda elinizde altın değerinde bir tablo olur: hangi sayfada, hangi butona basıldığında, hangi aşamada kaç milisaniye kaybedildiği. hedefElement alanı size doğrudan CSS seçicisini verir — artık tahmin yok.
Saha Verisi ile Lab Verisi Neden Uyuşmuyor?#
PageSpeed Insights'ın alt kısmındaki "Performans sorunlarını teşhis edin" bölümü laboratuvar verisidir: tek bir simüle edilmiş yükleme, hiç etkileşim yok. INP ise ancak birisi tıkladığında oluşur. Bu yüzden lab testleri INP üretemez — Lighthouse size INP puanı vermez, yerine "Toplam Engelleme Süresi" (TBT) gösterir.
TBT, INP'nin lab tarafındaki en iyi vekilidir ama mükemmel değildir. TBT sadece sayfa yüklenirken oluşan uzun görevleri sayar; oysa INP'yi bozan şey çoğu zaman yüklemeden çok sonra, kullanıcı bir sekmeye tıkladığında çalışan koddur. Yine de pratik bir kural işe yarar: TBT 200 ms'nin üzerindeyse ana iş parçacığınız zaten dolu demektir ve INP'nin de kötü olma olasılığı yüksektir.
Etkileşimi gerçekten ölçmek için kendiniz test etmelisiniz. Chrome DevTools'ta şu ayarlarla çalışın:
- Performance sekmesini açın.
- CPU kısıtlamasını 4x slowdown yapın (orta seviye telefonu taklit eder).
- Ağ kısıtlamasını Slow 4G seçin.
- Kaydı başlatın, sayfada gerçekten kullanıcı gibi davranın: menüyü açın, filtre değiştirin, sepete ekleyin.
- Kaydı durdurup Interactions şeridine bakın.
Interactions şeridindeki her blok bir etkileşimdir ve 200 ms'yi aşanlar işaretlenir. Bloğa tıkladığınızda hangi aşamanın uzadığını ve o sırada çalışan fonksiyonları görürsünüz. Masaüstünüzde kısıtlamasız test yapmak en sık yapılan hatadır; i7 işlemcili bir makinede her şey hızlıdır, kullanıcınızın telefonunda değil.
DevTools ile Uzun Görevleri Yakalama#
Performance kaydında ana iş parçacığı şeridinde kırmızı köşeli üçgenle işaretlenmiş bloklar uzun görevlerdir (50 ms üstü). Bir etkileşim tam da böyle bir bloğun ortasına denk gelirse, tarayıcı önce o bloğun bitmesini bekler — girdi gecikmeniz budur.
Kayıt almadan, doğrudan sahada çalışan bir yöntem daha var: Long Animation Frames API. Bu API yavaş kareleri ve o kareyi yavaşlatan script kaynaklarını listeler. Konsola şunu yapıştırın, sonra sayfada gezinin:
new PerformanceObserver((list) => {
for (const kare of list.getEntries()) {
if (kare.duration < 100) continue;
console.groupCollapsed(`Yavaş kare: ${Math.round(kare.duration)} ms`);
console.log('Stil/düzen süresi:', Math.round(kare.styleAndLayoutStart ? kare.duration - (kare.styleAndLayoutStart - kare.startTime) : 0), 'ms');
for (const s of kare.scripts) {
console.log(`${Math.round(s.duration)} ms ${s.invokerType} ${s.sourceURL || '(satır içi)'}`);
}
console.groupEnd();
}
}).observe({type: 'long-animation-frame', buffered: true});
Çıktı size doğrudan dosya URL'si ve süre verir. Deneyimimize göre ilk çalıştırmada listenin başında neredeyse her zaman sizin kodunuz değil, bir üçüncü parti script bulunur.
Sonuçları yorumlarken şu ayrımı yapın: invokerType alanı event-listener diyorsa gecikme sizin handler'ınızdadır. classic-script veya module-script diyorsa sorun sayfa yüklenirken çalışan bir script'tir ve çözümü ertelemektir. Render engelleyen kaynakların genel çözümü için render engelleyen kaynakları kaldırma yazısındaki defer ve async stratejileri işinizi görecektir.
Üçüncü Parti Scriptler: En Sık Suçlu#
Ölçtüğümüz sitelerin çoğunda INP'nin yarısından fazlası kendi kodumuzun dışındaki scriptlerden gelir: etiket yöneticileri, reklam ağları, ısı haritası araçları, canlı destek widget'ları, A/B test kütüphaneleri. Bunlar ana iş parçacığında çalışır ve sizin butonunuzla aynı sırayı paylaşır.
Üç kademeli bir yaklaşım işe yarar.
Birincisi: gerçekten gerekli mi? Bir ısı haritası aracını üç ay önce kurup bir daha açmadıysanız kaldırın. Aynı işi yapan iki analitik varsa birini silin. En hızlı script, sayfaya hiç yüklenmeyen scripttir.
İkincisi: ertelenebilir mi? Canlı destek widget'ının sayfa açılır açılmaz yüklenmesi gerekmez. Kullanıcı ilk kez etkileşime girene kadar bekletin:
<script>
(function () {
let yuklendi = false;
function widgetiYukle() {
if (yuklendi) return;
yuklendi = true;
const s = document.createElement('script');
s.src = 'https://ornek-destek.com/widget.js';
s.async = true;
document.head.appendChild(s);
}
['pointerdown', 'keydown', 'touchstart', 'scroll'].forEach(function (olay) {
addEventListener(olay, widgetiYukle, { once: true, passive: true });
});
setTimeout(widgetiYukle, 5000);
})();
</script>
Bu kalıp iki kapı açar: kullanıcı gerçekten bir şey yaparsa script hemen yüklenir, hiçbir şey yapmazsa 5 saniye sonra yine yüklenir. Böylece ne işlevi kaybedersiniz ne de ilk etkileşimi bloklarsınız.
Üçüncüsü: ana iş parçacığından çıkarılabilir mi? Partytown gibi çözümler etiket yöneticisini bir web worker'a taşır. Kurulumu zahmetlidir ve her script uyumlu değildir, ama Google Tag Manager altında onlarca etiket biriktiyse etkisi büyüktür.
Bu arada üçüncü parti scriptlerin yükleme performansına etkisini de gözden kaçırmayın; aynı script hem INP'yi hem de LCP'yi bozuyor olabilir.
Ağır Event Handler'ları Bölmek#
Kendi kodunuz suçluysa temel kural şudur: kullanıcıya görsel geri bildirimi hemen verin, ağır işi sonraya bırakın. Tarayıcı bir kare boyayamadığı sürece kullanıcı "hiçbir şey olmadı" hisseder.
Klasik hatalı kalıp şöyledir:
filtreButonu.addEventListener('click', () => {
const sonuc = binlerceUrunuFiltrele(urunler); // 380 ms
listeyiCiz(sonuc); // 120 ms
analitikGonder('filtre'); // 40 ms
menuyuKapat(); // görsel geri bildirim, en sonda
});
Burada kullanıcı yarım saniyeden fazla hiçbir şey görmez. Doğrusu, görsel değişikliği başa almak ve acil olmayan işi boyamadan sonraya ertelemektir:
filtreButonu.addEventListener('click', async () => {
menuyuKapat();
yukleniyorGoster(); // kullanıcı anında tepki görür
await yeniKareyiBekle(); // tarayıcı boyasın
const sonuc = await parcaliFiltrele(urunler);
listeyiCiz(sonuc);
setTimeout(() => analitikGonder('filtre'), 0); // acil değil
});
function yeniKareyiBekle() {
return new Promise(r => requestAnimationFrame(() => setTimeout(r, 0)));
}
Uzun döngüleri bölmek için ana iş parçacığına ara ara sıra vermeniz gerekir. Modern yöntem scheduler.yield(), eski tarayıcılar için setTimeout yedeğidir:
function sıraVer() {
if (typeof scheduler !== 'undefined' && typeof scheduler.yield === 'function') {
return scheduler.yield();
}
return new Promise(r => setTimeout(r, 0));
}
async function parcaliFiltrele(urunler) {
const sonuc = [];
for (let i = 0; i < urunler.length; i++) {
if (uygunMu(urunler[i])) sonuc.push(urunler[i]);
if (i % 200 === 0) await sıraVer(); // her 200 üründe nefes aldır
}
return sonuc;
}
scheduler.yield() ile setTimeout(0) arasındaki fark önemlidir: setTimeout işinizi kuyruğun sonuna atar, yani araya giren başka görevler sizden önce çalışabilir. scheduler.yield() ise kaldığınız yerden öncelikli devam etmenizi sağlar. İkisi de INP'yi düzeltir, ikincisi işinizin toplam süresini daha az uzatır.
Şişmiş DOM ve Pahalı Stil Hesapları#
Handler'ınız 10 ms sürüyor ama INP hâlâ 400 ms ise sorun sunum gecikmesindedir: tarayıcı yeni kareyi hesaplayamıyordur. En yaygın iki nedeni var.
Çok büyük DOM. 3.000-5.000 düğümü aşan sayfalarda her stil yeniden hesabı pahalıya mal olur. Konsolda hızlı ölçüm:
console.log('Toplam düğüm:', document.querySelectorAll('*').length);
Sonsuz kaydırmalı listelerde, uzun yorum bölümlerinde ve filtresiz ürün listelerinde bu sayı kolayca 10.000'i geçer. Sayfa dışındaki bölümleri tarayıcıya "şimdilik hesaplama" diye işaretleyebilirsiniz:
.yorum-karti,
.urun-satiri {
content-visibility: auto;
contain-intrinsic-size: auto 220px;
}
contain-intrinsic-size değerini vermeyi unutmayın; yoksa öğeler sıfır yükseklikle başlar, kaydırma çubuğu zıplar ve bu kez düzen kaymasını bozarsınız.
Zorlanmış senkron layout. Bir döngü içinde önce DOM okuyup sonra yazarsanız tarayıcı her turda layout'u yeniden hesaplamak zorunda kalır. Bu, "layout thrashing" denen ve profilde uzun mor bloklar olarak görünen durumdur:
// Yanlış: her turda oku-yaz-oku-yaz
elemanlar.forEach(el => {
el.style.height = el.offsetHeight + 10 + 'px';
});
// Doğru: önce hepsini oku, sonra hepsini yaz
const yukseklikler = elemanlar.map(el => el.offsetHeight);
elemanlar.forEach((el, i) => {
el.style.height = yukseklikler[i] + 10 + 'px';
});
Ayrıca animasyonlarınızı width, height, top, left yerine transform ve opacity üzerinden yapın; bu ikisi layout hesabı gerektirmez.
Hangi Eklenti Suçlu: Ölçerek Bulma Yöntemi#
WordPress kullanıyorsanız sorun genellikle tek bir eklentidedir ama hangisi olduğunu tahminle bulmak saatler alır. Long Animation Frames API çıktısını eklenti klasörüne göre gruplayarak bunu birkaç dakikaya indirebilirsiniz. Konsola şunu yapıştırın ve sayfada normal bir kullanıcı gibi gezinin:
const toplam = {};
new PerformanceObserver((list) => {
for (const kare of list.getEntries()) {
for (const s of kare.scripts) {
const m = (s.sourceURL || '').match(/\/wp-content\/(plugins|themes)\/([^/]+)\//);
const ad = m ? `${m[1]}: ${m[2]}` : (s.sourceURL ? new URL(s.sourceURL).hostname : 'satır içi');
toplam[ad] = Math.round((toplam[ad] || 0) + s.duration);
}
}
console.table(toplam);
}).observe({type: 'long-animation-frame', buffered: true});
Otuz saniye gezindikten sonra tablonun en üstündeki isim, ana iş parçacığını en çok meşgul eden eklentidir. Çıktı hostname gösteriyorsa suçlu bir üçüncü parti servistir, eklenti değil.
Şüpheliyi bulduktan sonra doğrulama adımı şart: eklentiyi geçici olarak devre dışı bırakın ve aynı ölçümü tekrarlayın. INP tahminini konsolda hızlıca görmek için web-vitals kütüphanesinin onINP fonksiyonunu reportAllChanges: true ile çağırabilirsiniz. Devre dışı bırakmanın yan etkisi olmadığından emin olmak ve daha genel bir eleme yöntemi için hangi eklenti siteyi yavaşlatıyor yazısındaki adım adım eleme sürecini takip edin.
Suçluyu bulduğunuzda üç seçeneğiniz olur: eklentiyi daha hafif bir alternatifle değiştirmek, scriptini yalnızca ihtiyaç duyulan sayfalarda yüklemek (çoğu eklenti her sayfaya kendi JS'ini basar) veya ayarlarından gereksiz özellikleri kapatmak. WordPress'e özgü Core Web Vitals stratejisinin tamamı için WordPress Core Web Vitals yazısına göz atın.
Değişiklikten Sonra Ne Zaman Sonuç Görürsünüz?#
En sık sorulan soru budur ve cevabı sabır gerektirir. Search Console'daki CrUX verisi 28 günlük kayan bir penceredir. Bugün düzelttiğiniz bir sorunun etkisi rapora yansımaya birkaç gün sonra başlar, tam olarak oturması yaklaşık dört haftayı bulur.
Bu yüzden çalışma düzeninizi şöyle kurun: değişikliği yayınlayın, kendi RUM ölçümünüzle ertesi gün doğrulayın (kendi verinizde 28 günlük gecikme yok), Search Console'daki resmi onayı ise arka planda bekleyin. RUM ölçümü kurmadıysanız her değişiklikten sonra bir ay kör uçuş yaparsınız — bu, INP çalışmasını gereksiz yere aylara yayan en büyük sebeptir.
Bir de sıralama beklentisini yerine oturtalım: Core Web Vitals bir sıralama sinyalidir ama içerik kalitesi ve alaka düzeyi kadar ağır basmaz. INP'yi 400 ms'den 150 ms'ye indirmek sizi ilk sıraya taşımaz; ancak dönüşüm oranınızı ölçülebilir biçimde artırır, çünkü tıkladığında tepki vermeyen bir butonu kullanıcılar iki kez tıklar, sonra da sayfayı terk eder.
Sıkça Sorulan Sorular#
INP kaç ms olmalı, hangi değer iyi sayılır?#
200 ms ve altı iyi, 200-500 ms arası iyileştirme gerektiren, 500 ms üstü zayıf kabul edilir. Google bu değeri 75. yüzdelik dilimden alır; yani ziyaretçilerinizin en kötü deneyim yaşayan çeyreği baz alınır, ortalama değil. Bu yüzden ziyaretçilerinizin çoğu iyi bir deneyim yaşasa bile eski telefon kullanan bir azınlık skorunuzu zayıf gösterebilir.
PageSpeed Insights 95 puan veriyor ama INP zayıf, bu nasıl olur?#
PageSpeed'in üstteki puanı laboratuvar testidir ve yalnızca sayfa yükleme performansını ölçer; hiçbir tıklama simüle edilmez. INP ise sayfa açıldıktan sonra gerçek kullanıcıların yaptığı etkileşimlerden hesaplanır. Sayfanız çok hızlı açılıp, açıldıktan sonra ağır bir JavaScript ile etkileşime geç yanıt veriyorsa bu tablo tamamen normaldir. Sayfanın üst kısmındaki gerçek kullanıcı verisi bölümüne bakın.
INP'yi Lighthouse ile ölçebilir miyim?#
Hayır. Lighthouse etkileşim simüle etmediği için INP değeri üretemez. Onun yerine Toplam Engelleme Süresi (TBT) metriğine bakın; ana iş parçacığının ne kadar meşgul olduğunu gösterdiği için INP'nin iyi bir vekilidir. Gerçek ölçüm için ya Search Console'un saha verisini ya da kendi sitenize kuracağınız bir RUM ölçümünü kullanmanız gerekir.
FID'de yeşildim, INP'de kırmızıya düştüm, kodumda ne değişti?#
Kodunuzda hiçbir şey değişmedi, ölçüm değişti. FID yalnızca ilk etkileşimin başlama gecikmesini ölçüyordu ve event handler'ınızın ne kadar sürdüğünü hiç dikkate almıyordu. INP ise handler'ın çalışma süresini ve sonucun ekrana yansımasını da sayar, üstelik sayfadaki tüm etkileşimlere bakar. Yani sorun zaten vardı, FID onu görmüyordu.
Sunucumu güçlendirsem INP düzelir mi?#
Genellikle hayır. INP büyük ölçüde tarayıcı tarafında, kullanıcının cihazındaki ana iş parçacığında oluşan bir gecikmedir; sunucunuzun CPU'su bunu doğrudan etkilemez. Sunucu yükseltmesi TTFB ve LCP gibi yükleme metriklerini iyileştirir. INP'nin sunucuyla ilgili tek noktası, etkileşim sonrası yapılan API isteğinin yavaş dönmesidir; o durumda da çözüm sunucu değil, isteği beklerken kullanıcıya anında görsel geri bildirim vermektir.