Tüm yazılar SEO & Pazarlama

Core Web Vitals: Hız Neden SEO İşi Oldu

Core Web Vitals aslında üç basit soru soruyor: sayfa ne zaman göründü, göründükten sonra yerinde durdu mu, dokunulduğunda cevap verdi mi. Bu üç metriği bozan tipik hataları ve teslim ettiğim sitelerde uyguladığım somut düzeltmeleri anlatıyorum.

DT
Demir Taşdemir Mobil Uygulama & Web Geliştirici
— dk okuma

Geçen sene bir müşterim “sitede her şey var ama Google’da yukarı çıkmıyoruz” dedi. İçerik fena değildi, başlıklar yerli yerindeydi. Telefondan açtım: önce boş beyaz bir ekran, sonra logo, sonra kapak görseli düştü ve altındaki bütün metin bir anda aşağı kaydı. Menüye basmaya çalıştım, buton bir süre hiçbir şey yapmadı. Sorun içerikte değildi; sayfanın kendini toparlama biçimindeydi.

Core Web Vitals tam olarak bunu ölçüyor: kullanıcının gözüyle sayfa ne zaman göründü, göründükten sonra yerinde durdu mu, dokunulduğunda cevap verdi mi. Bu yazıda üç metriği, onları bozan tipik hataları ve teslim ettiğim sitelerde ne değiştirdiğimi anlatacağım.

Üç metrik, üç ayrı soru

Core Web Vitals üç ölçümden oluşuyor ve üçü birbirinin yerine geçmiyor:

  • LCP (Largest Contentful Paint) — Görünen alandaki en büyük içerik öğesi ne zaman boyandı? “İyi” eşiği 2,5 saniye.
  • CLS (Cumulative Layout Shift) — Sayfa yüklenirken içerik ne kadar yerinden oynadı? Birimsiz bir puan; iyi kabul edilen üst sınır 0,1.
  • INP (Interaction to Next Paint) — Kullanıcı tıkladıktan veya bastıktan sonra ekranda ilk görsel cevap ne kadar sürede geldi? İyi eşiği 200 milisaniye. INP, Mart 2024’te FID’in yerine geçti.

Önemli olan ikinci kısım: bu değerler tek bir ziyaretten değil, gerçek kullanıcıların oluşturduğu saha verisinden geliyor ve 75. yüzdelik üzerinden değerlendiriliyor. Yani her dört ziyaretçiden üçünün eşiği geçmesi gerekiyor. Chrome’un topladığı bu veri 28 günlük kayan bir pencerede birikiyor; Search Console’daki Core Web Vitals raporu da onu gösteriyor.

Laboratuvar ile saha aynı şey değil

Lighthouse’ta 100 almak ile saha verisinde “iyi” görünmek aynı şey değil. Lighthouse kendi makinenizde, kendi bağlantınızda, tek seferlik ölçer. Saha verisi ise eski Android telefonlardan, zayıf mobil bağlantılardan gelen gerçek ziyaretleri de içerir. Ben her zaman ikisine birlikte bakıyorum: laboratuvar hatayı bulmak, saha ise düzelip düzelmediğini görmek için.

LCP çoğu zaman bir görsel sorunudur

LCP öğesi genellikle kapak görseli ya da ilk büyük başlık bloğudur. Süreyi dört parçaya bölünce nerede kaybettiğinizi görmek kolaylaşıyor: sunucunun ilk baytı gönderme süresi, tarayıcının o kaynağı keşfetme gecikmesi, kaynağın indirilme süresi ve indirildikten sonra ekrana çizilme gecikmesi.

Pratikte en çok gördüğüm hatalar keşfetme adımında:

  • Kapak görseline loading="lazy" konması. Tembel yükleme yalnızca ilk ekranın altındaki görseller için mantıklı; en tepedeki görsele koyduğunuzda LCP’yi kendi elinizle geciktiriyorsunuz.
  • Hero görselinin CSS’te background-image olarak verilmesi. Tarayıcının ön tarama mekanizması HTML’i okurken bu adresi göremez; önce CSS’i indirip ayrıştırması gerekir.
  • Görselin JavaScript ile sonradan sayfaya eklenmesi. Aynı sorun, bir kat daha gecikmeli.
  • Sayfanın en tepesinde duran, çizimi bloklayan büyük CSS ve senkron script dosyaları.

Kapak görselini erkenden ve öncelikli indirtmek için tarayıcıya işaret bırakmak gerekiyor:

<link rel="preload" as="image"
      href="/assets/kapak-1200.webp"
      imagesrcset="/assets/kapak-800.webp 800w, /assets/kapak-1600.webp 1600w"
      imagesizes="100vw">

<img src="/assets/kapak-1200.webp"
     srcset="/assets/kapak-800.webp 800w, /assets/kapak-1600.webp 1600w"
     sizes="100vw"
     width="1600" height="900"
     fetchpriority="high" decoding="async"
     alt="Ofis girişinde güvenlik danışma bankosu">

Görselleri doğru boyutlandırmak

Yukarıdaki örnekte iki işi aynı anda yapan bir ayrıntı var: width ve height öznitelikleri. Bunlar görselin ekrandaki boyutunu belirlemiyor; onu CSS yine max-width: 100%; height: auto ile yönetiyor. Yaptıkları iş, tarayıcıya en-boy oranını söylemek. Tarayıcı görsel daha inmeden doğru yükseklikte boş bir kutu ayırıyor ve görsel geldiğinde metin aşağı kaymıyor. Tek satırlık bu ekleme, CLS sorunlarının büyük bölümünü çözüyor.

Boyut tarafında dikkat ettiğim diğer noktalar:

  • Gerçek boyutta servis etmek. 4000 piksel genişliğindeki bir fotoğrafı CSS ile 600 piksele küçültmek dosyayı küçültmüyor; telefon o baytların tamamını indiriyor.
  • Modern format. WebP çoğu senaryoda JPEG’e göre belirgin şekilde küçük dosya veriyor; AVIF daha da iyi sıkıştırıyor ama kodlaması yavaş. Fotoğraflarda WebP’yi varsayılan yapıyorum, logo ve ikonlarda SVG kullanıyorum.
  • Gömülü içeriklere yer ayırmak. Harita, video, reklam alanı gibi geç gelen kutulara CSS’te aspect-ratio veya min-height vermek şart.

CLS: ayağınızın altından kayan zemin

Düzen kayması kullanıcının en çok sinirlendiği şey ama geliştiricinin en az fark ettiği şey. Sebebi basit: biz siteyi hızlı bağlantıda ve önbellek doluyken açıyoruz, her şey anında geliyor. Kayma, yavaş bağlantıda ortaya çıkıyor.

Kaynakları neredeyse hep aynı:

  • Boyutu bildirilmemiş görseller ve iframe’ler.
  • Sayfanın en üstüne sonradan giren duyuru çubuğu veya çerez bandı. Bu bloklar akışın içine girdiğinde altındaki her şeyi aşağı itiyor. Ben bunları position: fixed ile ayrı bir katman olarak veriyorum; sayfa akışına hiç dokunmuyorlar.
  • Yazı tipi değişimi. Yedek fontla çizilen metin, asıl font gelince farklı genişlikte olduğu için satırlar yeniden diziliyor.
  • top, left, height gibi özellikleri animasyonla değiştirmek. Bunun yerine transform ve opacity kullanmak hem kaymayı önlüyor hem daha akıcı çalışıyor.

Yazı tipleri: en sinsi kayma kaynağı

Yazı tipi yükleme tek başına hem LCP’yi hem CLS’yi etkileyebiliyor. Üçüncü taraf bir font servisinden çekmek, tarayıcıya yeni bir alan adı için DNS çözümlemesi ve TLS el sıkışması maliyeti bindiriyor. Ben teslim ettiğim sitelerde fontları kendi sunucumdan, woff2 olarak veriyorum.

@font-face {
  font-family: "Inter";
  src: url("/assets/inter-var.woff2") format("woff2");
  font-weight: 400 700;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0100-024F, U+0130-0131, U+015E-015F, U+011E-011F;
}

/* Yedek fontun ölçüleri asıl fonta yaklaşsın */
@font-face {
  font-family: "Inter Yedek";
  src: local("Arial");
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
}

Buradaki üç ayrıntı önemli. font-display: swap metnin yedek fontla hemen görünmesini sağlıyor, yani kullanıcı boş ekrana bakmıyor. unicode-range ile Türkçe için gereken karakterleri kapsayan aralığı bildirip dosyayı küçültüyorum — ı, ş, ğ ve büyük İ, Latin Extended-A bloğunda olduğu için sadece temel Latin aralığıyla yetinmek olmuyor. Üçüncüsü, yedek fontun ölçülerini size-adjust ve ascent-override ile asıl fonta yaklaştırmak: font geldiğinde satır genişlikleri neredeyse aynı kaldığı için gözle görülür bir sıçrama olmuyor.

Kritik CSS: her şeyi baştan yüklememek

CSS varsayılan olarak çizimi bloklar. Tarayıcı stil dosyasının tamamını indirip ayrıştırmadan tek bir piksel çizmez. Tek bir büyük stil.css dosyanız varsa, sayfanın ilk ekranı, alt bilginin ve iletişim formunun stillerini de bekliyor demektir.

Çözüm, ilk ekranı çizen kuralları HTML’in içine gömmek, gerisini bloklamadan yüklemek:

<style>
  /* yalnızca ilk ekran: tipografi, üst menü, hero */
  :root{--metin:#111}
  body{margin:0;font:16px/1.6 "Inter","Inter Yedek",sans-serif;color:var(--metin)}
  .ust-menu{display:flex;align-items:center;height:64px}
</style>

<link rel="stylesheet" href="/css/stil.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/css/stil.css"></noscript>

media="print" hilesi tarayıcıya “bu dosya şu an ekran için gerekli değil” dedirtiyor; dosya bloklamadan iniyor ve onload ile devreye giriyor. Ama abartmamak lazım: satır içine ne kadar çok CSS koyarsanız HTML o kadar şişer ve o kısım önbelleğe girmez. Ben genelde birkaç kilobaytı geçmemeye çalışıyorum.

Kritik CSS’i elle yazarsanız bakımı unutmayın

Tasarımı değiştirip satır içi bloğu güncellemeyi unutunca ilk ekran bir an yanlış görünüyor, sonra düzeliyor — yani kendi elinizle düzen kayması üretiyorsunuz. Bu yüzden satır içine yalnızca çok nadir değişen kuralları koyuyorum.

INP: tıkladıktan sonraki sessizlik

LCP ve CLS yüklemeyle ilgili; INP ise sayfa açıldıktan sonrasıyla. Tarayıcıda JavaScript tek bir ana iş parçacığında çalışır. O iş parçacığı meşgulken tıklamanız kuyrukta bekler, ekran güncellenemez. 50 milisaniyeyi geçen her görev bu yüzden “uzun görev” sayılıyor.

INP’yi bozan tipik durumlar: kaydırma veya yazma olaylarında her tetiklenişte ağır iş yapmak, tek seferde çok büyük bir DOM ağacı kurmak, sayfaya işlevle ilgisi olmayan üçüncü taraf script’leri doldurmak. Yaklaşımım şu: kullanıcıya görsel geri bildirimi önce ver, ağır işi sonraya bırak. Butona basıldığında önce sınıfı ekleyip arayüzü güncelliyorum, hesabı ya da ağ isteğini ondan sonraki karede başlatıyorum. Uzun listelerde content-visibility: auto ile görünmeyen bölümlerin çizim maliyetini erteliyorum.

Teslim ettiğim sitelerde ne değiştirdim

allianceguvenlik.com, drdoganuysal.com ve kendi sitem demirtasdemir.com üzerinde uyguladığım düzeltmeler büyük ölçüde aynı listeydi:

SorunYaptığımEtkilediği metrik
Kapak fotoğrafı ham JPEG, tek boyutWebP’ye çevirdim, iki boyutlu srcset verdimLCP
Tüm görsellerde loading="lazy"İlk ekrandaki görselden kaldırdım, fetchpriority="high" ekledimLCP
Görsellerde boyut özniteliği yokHepsine width ve height ekledimCLS
Font üçüncü taraf servisten geliyorKendi sunucuma taşıdım, woff2 ve preloadLCP, CLS
Çerez bandı sayfa akışının içindeSabit konumlu katmana aldımCLS
İkon fontuSatır içi SVG’ye geçtimLCP, CLS

WordPress ile çalıştığım işlerde ayrıca her sayfaya yüklenen ama o sayfada hiç kullanılmayan eklenti CSS’leri ve slider dosyalarını ayıklamak tek başına ciddi bir yük alıyor. Bir de klasik hata var: aynı işi yapan iki eklentinin ikisinin birden aktif kalması.

Buraya “şu kadar iyileşti” diye bir yüzde yazmıyorum. Saha verisi 28 günlük pencerede hareket ediyor ve aynı dönemde içerik de bağlantılar da değişiyordu; tek bir sayıyı bu düzeltmelerin hanesine yazmak dürüst olmaz. Söyleyebileceğim şu: Search Console’daki “iyileştirme gerekli” uyarıları kayboldu ve mobilde sayfalar artık zıplamadan açılıyor.

Son olarak dengeyi kurmak lazım: hız tek başına kötü bir içeriği yukarı taşımaz. Google da sayfa deneyimini içeriğin yerine geçen bir şey olarak sunmuyor. Ama benzer güçteki iki sonuç arasında fark yaratan, daha önemlisi kullanıcının siteyi terk etmesini engelleyen bir katman. Bu yüzden ben Core Web Vitals’ı bir SEO puanı olarak değil, kullanıcının sabrının ölçüsü olarak okuyorum.

  • SEO
  • Core Web Vitals
  • Performans
  • Web Geliştirme
  • CSS
Paylaş: LinkedIn X WhatsApp
DT

Demir Taşdemir

Mobil Uygulama & Web Geliştirici

2018'den beri yazılım geliştiriyorum. App Store ve Google Play'de 11 uygulama yayınladım; şu an 6 mobil uygulama, 1 e-ticaret platformu ve 1 masaüstü oyun üzerinde çalışıyorum.

Sitenizin hızı sıralamanızı geride tutuyor olabilir

Mevcut sitenizi Core Web Vitals açısından inceleyip önce hangi düzeltmelerin yapılması gerektiğini çıkarabilirim. Yeni bir site kuruyorsanız da bu ölçütleri en baştan gözeterek geliştiriyorum. İletişim sayfasından yazmanız yeterli.