Core Web Vitals performans denetimi: adım adım
Core Web Vitals denetimi adım adım: LCP, INP ve CLS neyi ölçer, saha ile laboratuvar verisi neden ayrışır, statik sitede görsel, font, CSS ve betik düzeltmeleri.
Core Web Vitals denetimi, bir siteyi "hızlı" ya da "yavaş" diye etiketlemekten çok, ziyaretçinin gerçekten ne yaşadığını üç sayıya indirgeyen bir ölçüm disiplini. Kendi sitemi ve iş için kurduğum statik siteleri denetlerken gördüğüm kadarıyla asıl zorluk metrikleri ezberlemek değil; hangi sayının nereden geldiğini, neden bir araçta yeşil bir diğerinde kırmızı göründüğünü ayırt edebilmek. Bu yazıda LCP, INP ve CLS'nin tam olarak neyi ölçtüğünü, saha verisi ile laboratuvar verisinin neden birbirini tutmadığını ve statik bir sitede elle yapılabilecek düzeltmeleri kendi denetim sıramla anlatacağım.
Denetim derken kastettiğim, PageSpeed Insights'a adresi yapıştırıp skoru okumaktan fazlası. Skor tek bir sayı; oysa Google'ın arama tarafında baktığı şey skor değil, üç metriğin gerçek kullanıcılarda 75. yüzdelikte hangi eşiği geçtiği. Bu ayrımı anlamadan yapılan iyileştirmeler çoğu zaman laboratuvar raporunu güzelleştirip sahadaki deneyimi olduğu yerde bırakıyor.
Yazının içinde hem ölçümü otomatikleştiren küçük bir Python betiği hem de görüntü, font, kritik CSS ve betik yükü için doğrudan kopyalanabilir örnekler var. Bunları bir kere kurunca denetim, tek seferlik bir tarama olmaktan çıkıp her yayın öncesi çalışan bir kontrol adımına dönüşüyor.
Üç metrik tam olarak neyi ölçüyor
Eşik değerleri Google'ın web.dev belgelerinde yayımlanıyor; aşağıdaki sayılar oradan. Kendi yorumum, her metriğin arkasında hangi teknik sebebin durduğuyla ilgili.
- LCP (Largest Contentful Paint): ilk ekrandaki en büyük içerik parçasının, çoğu sitede kapak görselinin ya da başlık bloğunun, ekrana çizildiği an. 2,5 saniyenin altı iyi, 4 saniyenin üstü zayıf sayılıyor. Sunucu yanıt süresi, görselin ağırlığı ve tarayıcının o görsele verdiği öncelik bu değeri belirliyor.
- INP (Interaction to Next Paint): Mart 2024'te FID'in yerini aldı. Sayfa açık kaldığı sürece her tıklama, dokunma ve tuş basımı ölçülüyor; raporlanan değer bunların en kötülerinden biri. 200 milisaniyenin altı iyi, 500'ün üstü zayıf. Ana iş parçacığını meşgul eden betikler, özellikle üçüncü taraf etiket yöneticileri, burada ortaya çıkıyor.
- CLS (Cumulative Layout Shift): kullanıcı bir şey yapmamışken içeriğin ne kadar kaydığının birimsiz skoru. 0,1'in altı iyi, 0,25'in üstü zayıf. Boyutu bildirilmemiş görsel, geç gelen font ve sonradan araya giren duyuru şeridi en sık gördüğüm üç sebep.
Üçünde de değerlendirme 75. yüzdelik üzerinden yapılıyor: ziyaretlerin en az dörtte üçü eşiğin altındaysa metrik "iyi" kabul ediliyor. Ortalama değil, yüzdelik. Bu yüzden ortalamayı bozmayan bir avuç yavaş cihaz ziyareti p75'i eşiğin üstüne itebiliyor ve sayfa, çoğu ziyaretçi için gayet hızlı olsa da kırmızıya düşüyor.
Saha verisi ile laboratuvar verisi neden ayrışır
Saha verisi Chrome User Experience Report'tan (CrUX) geliyor: veri paylaşımına izin vermiş gerçek Chrome kullanıcılarının ziyaretleri, 28 günlük kayan bir pencerede toplanıyor. Laboratuvar verisi ise Lighthouse'un tek bir koşumu: sabit bir cihaz emülasyonu, simüle edilmiş yavaş mobil ağ, boş önbellek. İkisi aynı sayfayı ölçse de aynı şeyi ölçmüyor; kurarken en çok kafa karıştıran nokta bu.
- Cihaz ve ağ dağılımı: laboratuvar tek bir profil kullanıyor, saha ise ziyaretçi kitleniz neyse onu yansıtıyor. Mobil trafiği ağır bir sitede saha LCP'si laboratuvarın üstüne çıkabiliyor; masaüstü ağırlıklı bir kurumsal sitede tam tersi oluyor.
- Önbellek: laboratuvar her seferinde soğuk başlıyor; saha, tekrar ziyaretleri de kapsıyor. Bu yüzden saha bazen laboratuvardan daha iyi görünüyor ve insan boşuna "neden ilk ziyaret bu kadar kötü" diye uğraşıyor.
- Sayfa ömrü: laboratuvar yalnızca yükleme anına bakıyor. CLS'de kaydırma sonrası oluşan kaymalar, INP'de bütün etkileşimler yalnızca sahada sayılıyor. INP laboratuvarda hiç ölçülmüyor; Lighthouse onun yerine Total Blocking Time veriyor, o da bir tahmin.
- Zaman penceresi: saha 28 günlük ortalama bir resim. Bugün yaptığınız düzeltme laboratuvarda anında, sahada haftalar sonra tam olarak görünüyor. Bir düzeltmeyi ertesi gün sahada aramak boşa umut.
- Veri yokluğu: düşük trafikli sitelerde CrUX verisi hiç olmayabiliyor; PageSpeed Insights o zaman yalnızca laboratuvar sonucu gösteriyor. Bu durumda "sahada yeşil" denemez; dürüst olan, saha için "doğrulanamadı" yazmaktır.
Benim çalışma sıram şu: sahada hangi metrik kırmızıysa laboratuvarda o metriğin sebebini bul, düzelt, laboratuvarda doğrula, sonra 28 gün sahayı izle. Laboratuvar teşhis aracı, saha ise karne. İkisini karıştırınca ya var olmayan bir sorunu çözmeye ya da gerçek sorunu görmezden gelmeye başlıyorsunuz.
Ölçümü otomatikleştirmek: saha ve laboratuvar yan yana
PageSpeed Insights'ın API'si tek istekte hem CrUX saha verisini hem Lighthouse sonucunu döndürüyor. Aşağıdaki betik iki kaynağı yan yana basıyor; saha verisi yoksa bunu açıkça söylüyor, sessizce boş geçmiyor. API anahtarı isteğe bağlı ama düzenli kullanımda kota için gerekli; anahtarı ortam değişkeninden alıyorum, koda hiçbir zaman gömmüyorum.
import json, os, sys, urllib.parse, urllib.request
PSI = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed"
SAHA = {
"LARGEST_CONTENTFUL_PAINT_MS": "LCP",
"INTERACTION_TO_NEXT_PAINT": "INP",
"CUMULATIVE_LAYOUT_SHIFT_SCORE": "CLS",
}
LAB = {
"largest-contentful-paint": "LCP",
"cumulative-layout-shift": "CLS",
"total-blocking-time": "TBT", # INP lab'da olculmez, TBT yaklasik vekildir
}
def sorgula(url, strateji="mobile"):
params = {"url": url, "strategy": strateji, "category": "performance"}
anahtar = os.environ.get("PSI_API_KEY") # ortamdan gelir, koda gomulmez
if anahtar:
params["key"] = anahtar
istek = PSI + "?" + urllib.parse.urlencode(params)
with urllib.request.urlopen(istek, timeout=120) as yanit:
return json.load(yanit)
def raporla(veri):
saha = veri.get("loadingExperience", {}).get("metrics", {})
if not saha:
print("SAHA : veri yok — CrUX bu URL icin yeterli ziyaret gormemis")
for anahtar, ad in SAHA.items():
m = saha.get(anahtar)
if not m:
continue
p75 = m["percentile"]
deger = f"{p75 / 100:.2f}" if ad == "CLS" else f"{p75} ms" # CLS 100 ile carpilmis gelir
print(f"SAHA {ad:<4} p75={deger:<9} {m['category']}")
denetim = veri["lighthouseResult"]["audits"]
for anahtar, ad in LAB.items():
print(f"LAB {ad:<4} {denetim[anahtar]['displayValue']}")
if __name__ == "__main__":
for strateji in ("mobile", "desktop"):
print(f"== {strateji} ==")
raporla(sorgula(sys.argv[1], strateji))
Bu betiği bir zamanlayıcıyla her sabah koşturup çıktıyı tarihli bir dosyaya yazdırıyorum. Mantık, PowerShell ile sistem izleme betiğinde kurduğum düzenin aynısı: tek komut, dosyaya düşen rapor, yan yana konunca trendi gösteren satırlar. Böylece 28 günlük saha penceresinin bir düzeltmeden sonra nasıl kaydığını izleyebiliyorum. Mobil sonuca önce bakın; Google mobil aramada mobil veriyi esas alıyor ve sorunlar genellikle orada daha görünür.
Statik sitede pratik düzeltmeler
Statik sitenin avantajı, sunucu tarafında bir darboğaz olmaması; sorun neredeyse her zaman tarayıcıya gönderdiğiniz şeylerde. Aşağıdaki dört alan, denetlediğim sitelerde en çok puan kazandıran yerler. Önce kalıbı gösteren tek bir HTML örneği, sonra her alanın gerekçesi.
<head>
<!-- 1) Kritik CSS: yalnizca ilk ekranin ihtiyaci, satir ici -->
<style>
body{margin:0;font-family:"Inter",system-ui,sans-serif}
.hero{min-height:60vh}
.hero img{width:100%;height:auto;display:block}
</style>
<!-- Kalan CSS: render'i bloklamadan, yuklenince devreye gir -->
<link rel="stylesheet" href="/css/site.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/css/site.css"></noscript>
<!-- 2) Font: kendi sunucundan, tek dosya, on yukleme, swap -->
<link rel="preload" href="/font/inter-latin-ext.woff2" as="font" type="font/woff2" crossorigin>
<style>
@font-face{
font-family:"Inter";
src:url(/font/inter-latin-ext.woff2) format("woff2");
font-weight:400;font-display:swap;
unicode-range:U+0000-00FF,U+0100-017F; /* Latin + Turkce harfler */
}
</style>
<!-- 3) Betik: ertele, ana is parcacigini ilk ekranda bos birak -->
<script src="/js/site.js" defer></script>
</head>
<body>
<section class="hero">
<!-- 4) LCP gorseli: boyut bildir, srcset ver, oncelik yuksek, lazy DEGIL -->
<img src="/img/kapak-1200.webp"
srcset="/img/kapak-640.webp 640w, /img/kapak-1200.webp 1200w, /img/kapak-1800.webp 1800w"
sizes="(max-width: 700px) 100vw, 1200px"
width="1200" height="675" alt="Kapak gorseli"
fetchpriority="high" decoding="async">
</section>
<!-- Ekranin altindaki gorseller: lazy -->
<img src="/img/ekran-800.webp" width="800" height="500"
alt="Ekran goruntusu" loading="lazy" decoding="async">
</body>
- Görüntü boyutlandırma: her görsele
widthveheightyazmak CLS'nin yarısını tek başına çözüyor; tarayıcı görsel gelmeden yer ayırıyor. LCP içinsrcsetile birkaç genişlik sunun, modern format (WebP ya da AVIF) kullanın ve kapak görselinefetchpriority="high"verin. En sık gördüğüm hata, kapak görseline deloading="lazy"yazılması: bu, LCP'yi doğrudan geciktiriyor. Lazy yalnızca ilk ekranın altındakiler için. - Font yükleme: fontu kendi alan adınızdan sunun; harici font servisi ek bir DNS ve TLS el sıkışması demek. Tek ağırlık, WOFF2,
preloadvefont-display: swap. Türkçe karakterler Latin Extended-A bloğunda olduğu için alt kümeyi keserken o aralığı dışarıda bırakmayın; yoksa ğ, ş ve ı sistem fontuna düşer ve bu da başlı başına bir kayma üretir. - Kritik CSS: ilk ekranın ihtiyacı olan birkaç kuralı satır içine alın, kalanını render'i bloklamadan yükleyin. Statik sitede CSS zaten küçükse bunu abartmaya gerek yok; ama tek bir büyük framework dosyası varsa fark hissediliyor.
- Gereksiz betikleri atmak: INP'de en büyük kazanç genelde bir şey eklemekten değil çıkarmaktan geliyor. Sohbet balonu, sosyal medya gömme kodu, kullanılmayan etiket yöneticisi tetikleyicileri ana iş parçacığını meşgul ediyor. Kalması gerekenleri
deferile ertelein, üçüncü taraf olanları kullanıcı etkileşimine bağlayın ve Lighthouse'un "kullanılmayan JavaScript" listesini her denetimde tekrar okuyun.
Görselleri elle boyutlandırmak sürdürülebilir değil; ben bunu bir betiğe bağlıyorum. Kaynak klasördeki her JPEG'den üç genişlikte WebP üretiyor, kaynaktan büyük boyut istemiyor ve çıktı ağırlığını basıyor. Yayın öncesi bir kez koşuyor, srcset bu dosyalara işaret ediyor.
from pathlib import Path
from PIL import Image
GENISLIKLER = (640, 1200, 1800)
KAYNAK = Path("kaynak")
HEDEF = Path("site/img")
HEDEF.mkdir(parents=True, exist_ok=True)
for dosya in sorted(KAYNAK.glob("*.jpg")):
with Image.open(dosya) as im:
im = im.convert("RGB")
for g in GENISLIKLER:
if g > im.width: # kucuk kaynagi buyutme; bulanik ve gereksiz agir olur
continue
yukseklik = round(im.height * g / im.width)
kopya = im.resize((g, yukseklik), Image.Resampling.LANCZOS)
cikti = HEDEF / f"{dosya.stem}-{g}.webp"
kopya.save(cikti, "WEBP", quality=80, method=6)
print(f"{cikti} {cikti.stat().st_size // 1024} KB")
Denetim sırası ve yayın öncesi kapı
Bütün bunları tek bir sıraya oturtunca denetim öngörülebilir hale geliyor. Benim uyguladığım sıra şu:
- Önce saha: hangi metrik, hangi cihaz türünde kırmızı? Saha verisi yoksa bunu not düşün ve laboratuvarla yetinin.
- Sonra laboratuvar: kırmızı metriğin sebebini Lighthouse'un teşhis listesinden bulun; LCP için hangi öğenin sayıldığını rapor açıkça gösteriyor.
- Tek seferde tek düzeltme yapın ve laboratuvarda yeniden ölçün; aynı anda beş şeyi değiştirince hangisinin işe yaradığını bilemezsiniz.
- Düzeltmeyi yayınladıktan sonra betiğin günlük çıktısını 28 gün izleyin; saha karnesi o zaman güncellenmiş olur.
- Eşikleri yayın öncesi kapı yapın: laboratuvar LCP'si ya da CLS'si eşiği aşan sayfa yayına çıkmasın.
Son madde, Excel raporlarında kurduğum kalite kontrol katmanıyla aynı düşünce: kuralı bir kere yazın, her koşumda otomatik uygulansın, insan yalnızca istisnalara baksın. Performans da bir kalite boyutu ve aynı disiplinle yönetilince "site yavaşlamış" cümlesi bir sürpriz olmaktan çıkıyor.
Özetle: LCP, INP ve CLS ziyaretçinin üç farklı anını ölçüyor; saha karne, laboratuvar teşhis; statik sitede kazanç görselin boyutunda, fontun sunumunda, CSS'in sırasında ve atılabilen betiklerde. Kendi siteniz için böyle bir denetim düzeni kurmak ya da mevcut sonuçları birlikte yorumlamak isterseniz veri ve otomasyon danışmanlığı işimi anlattığım ana sayfadaki /#contact bölümünden bana yazabilirsiniz.