Musa Yılmaz
·5 dk okuma

Core Web Vitals: Next.js Sitesinde Lighthouse Skorunu Yükseltmek

performansnextjsseocore-web-vitals

Core Web Vitals, Google'ın sayfa deneyimini ölçmek için kullandığı üç metrikten oluşur ve arama sıralamasında rol oynar. Ama asıl faydası SEO değil: bu metrikleri iyileştirmek, sitenin gerçekten daha kullanışlı olmasını sağlar.

Bu yazıda üç metriğin ne ölçtüğünü ve Next.js tarafında pratikte işe yarayan düzeltmeleri anlatıyorum.

Üç metrik

LCP (Largest Contentful Paint) — Sayfadaki en büyük içerik öğesinin görünme süresi. Genelde hero görseli veya ana başlıktır. Hedef: 2,5 saniyenin altı.

CLS (Cumulative Layout Shift) — Sayfa yüklenirken öğelerin ne kadar zıpladığı. Kullanıcı bir butona basmak üzereyken içeriğin kayması bu metriği bozar. Hedef: 0,1'in altı.

INP (Interaction to Next Paint) — Kullanıcı bir şeye tıkladığında arayüzün tepki verme süresi. Ana iş parçacığını meşgul eden ağır JavaScript bunu kötüleştirir. Hedef: 200 ms'nin altı.

Font yükleme LCP'yi neden geciktirir?

Bir sayfada LCP öğesi genelde metindir. Metin, fontu inene kadar görünmezse LCP gecikir. Lighthouse raporunda bunu "öğe oluşturma gecikmesi" (element render delay) satırında görürsünüz — LCP süresinin büyük kısmı burada geçiyorsa sorun ağ değil, font beklemesidir.

Çözüm display: "swap":

import { Geist } from "next/font/google";
 
const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
  display: "swap", // font inene kadar yedek font ile göster
});

swap ile metin hemen sistem fontuyla çizilir, gerçek font indiğinde değişir. Kullanıcı boş ekrana bakmaz.

İkinci nokta: kullanmadığınız fontu yüklemeyin. Projelerde sık görülen bir durum, bir monospace fontun tanımlanıp hiç kullanılmamasıdır. Kontrol etmek kolaydır:

grep -rn "font-mono" src/

Sonuç boşsa o font tanımını silin — her font ayrı bir ağ isteği ve gecikmedir.

Görseller CLS'e neden yol açar?

Boyutu belirtilmemiş bir görsel, yüklendiğinde altındaki içeriği aşağı iter. Next.js'in <Image> bileşeni bunu önler çünkü boyutu bilir ve yerini önceden ayırır:

import Image from "next/image";
 
<div className="relative aspect-[1200/630] w-full overflow-hidden rounded-2xl">
  <Image
    src={coverUrl}
    alt=""
    fill
    className="object-cover"
    sizes="(max-width: 768px) 100vw, 768px"
    priority
  />
</div>

Buradaki dört ayrıntı:

aspect-[1200/630] — Görsel inmeden önce alan ayrılır, kayma olmaz.

sizes — Tarayıcıya "bu görsel mobilde ekran genişliği kadar, masaüstünde en fazla 768px" der. Böylece gereksiz büyük dosya indirilmez.

priority — Sadece ekranın ilk görünen kısmındaki (above the fold) görsele verin. Her görsele vermek, hepsini öncelikli yapar ve hiçbirini öncelikli yapmamış olursunuz.

alt="" — Görsel tamamen dekoratifse boş bırakmak doğrudur; ekran okuyucu onu atlar. Bilgi taşıyorsa mutlaka açıklayıcı metin yazın.

Uzak bir kaynaktan (CDN, depolama servisi) görsel çekiyorsanız hostu tanımlamanız gerekir:

// next.config.ts
images: {
  remotePatterns: [
    {
      protocol: "https",
      hostname: "*.example-cdn.com",
      pathname: "/public/**",
    },
  ],
}

pathname kısıtını koymak önemlidir — hostu tamamen serbest bırakmak, sitenizin optimizasyon uç noktasının başkaları tarafından kullanılmasına yol açabilir.

Lighthouse erişilebilirlik puanını en çok ne düşürür?

Lighthouse'un erişilebilirlik denetiminde en sık düşülen kalem renk kontrastıdır. Marka renginiz beyaz üzerinde güzel görünebilir ama WCAG AA eşiği olan 4,5:1 oranını geçmiyor olabilir.

Kontrastı ölçmek basittir:

function luminance(hex) {
  const c = hex.replace("#", "").match(/../g).map((x) => {
    const v = parseInt(x, 16) / 255;
    return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
  });
  return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
}
 
function contrastRatio(a, b) {
  const [l1, l2] = [luminance(a), luminance(b)].sort((x, y) => y - x);
  return (l1 + 0.05) / (l2 + 0.05);
}
 
console.log(contrastRatio("#0d9488", "#ffffff")); // 3.74 → yetersiz
console.log(contrastRatio("#0d7d72", "#ffffff")); // 5.01 → yeterli

Rengi biraz koyulaştırmak genelde marka kimliğini bozmadan eşiği geçmeye yeter. Renkleri CSS değişkeni olarak tanımlıyorsanız tek satır değişiklik tüm siteyi düzeltir:

:root {
  --accent: #0d7d72; /* beyaz üzerinde 5.01:1 */
}

Koyu temayı ayrı kontrol edin — açık temada yeterli olan bir renk, koyu zeminde farklı davranır.

Lab skoru ile saha verisi (CrUX) arasındaki fark nedir?

PageSpeed Insights iki farklı veri gösterir ve karıştırılması yaygındır:

Lab verisi (Lighthouse) — Kontrollü bir ortamda, yapay olarak yavaşlatılmış CPU ve ağ ile yapılan tek seferlik ölçüm. Tekrarlanabilir ve karşılaştırılabilirdir, ama gerçek kullanıcıyı temsil etmez.

Saha verisi (CrUX) — Gerçek Chrome kullanıcılarından toplanan 28 günlük veri. Google'ın sıralamada kullandığı budur.

Yeni bir sitede saha verisi henüz yoktur; "Veri yok" görmek normaldir. Lab skorunda 100 kovalamak yerine, gerçek kullanıcı verisi birikmeye başladığında ona bakın.

Lab skorunda takılınan tipik bir kalem "Speed Index" olur. Bu metrik framework'ün hidrasyon maliyetini de içerir ve React tabanlı bir sitede belirli bir noktadan sonra düşmez. Son birkaç puan için sitenin işlevselliğini kısmak genelde kötü bir takastır.

Yanıltıcı uyarılar

Lighthouse raporunda görüp panik yapmamanız gereken iki şey:

Prefetch zaman aşımları. Next.js, görünen linklerin içeriğini önceden indirir. Lighthouse'un yapay olarak kısıtladığı ağda bu istekler zaman aşımına uğrayıp konsol hatası olarak raporlanır. Gerçek kullanımda oluşmaz ve sayfa bunlar başarısız olsa da çalışır.

Geçici kaynak hataları. "robots.txt alınamadı — zaman aşımı" gibi mesajlar bazen Google'ın test altyapısındaki geçici gecikmeden kaynaklanır. Doğrulamak için doğrudan isteyin:

curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" https://example.com/robots.txt

200 ve makul bir süre dönüyorsa sorun sizde değildir; testi tekrarlayın.

Ölçüm sırası

Optimizasyona başlamadan önce mevcut durumu kaydedin:

npx lighthouse https://example.com \
  --only-categories=performance,accessibility,best-practices,seo \
  --form-factor=mobile --screenEmulation.mobile \
  --output=json --output-path=./before.json

Bir değişiklik yapın, tekrar ölçün, karşılaştırın. Aynı anda beş şey değiştirirseniz hangisinin işe yaradığını bilemezsiniz — hatta bazıları birbirini götürebilir.

Özet

Pratikte en çok fark yaratan üç müdahale şunlardır: fontlara display: swap eklemek, görsellere sabit en-boy oranı vermek ve kullanılmayan kaynakları kaldırmak. Bunlar birkaç satırlık değişikliklerdir ve LCP ile CLS üzerinde doğrudan etki gösterir.

Geri kalanı için ölçün, tek seferde bir şey değiştirin ve lab skorunu bir hedef değil, bir gösterge olarak kullanın.