Next.js ile Performans Optimizasyonu
Google'ın Core Web Vitals metrikleri artık doğrudan sıralama faktörü. Bu rehberde Next.js projelerinde LCP, CLS ve INP skorlarını nasıl iyileştireceğini adım adım göreceğiz.
Core Web Vitals Nedir?
Google'ın kullanıcı deneyimini ölçmek için kullandığı üç temel metrik:
- LCP (Largest Contentful Paint) — Sayfanın en büyük içeriğinin yüklenme süresi. Hedef: < 2.5 saniye
- INP (Interaction to Next Paint) — Kullanıcı etkileşimine yanıt süresi. Hedef: < 200ms
- CLS (Cumulative Layout Shift) — Görsel kararlılık skoru. Hedef: < 0.1
1. next/image ile LCP İyileştirme
Ham <img> tag'i yerine next/image kullanmak LCP'yi dramatik şekilde düşürür.
// ❌ Kötü — layout shift + optimize edilmemiş
<img src="/hero.jpg" alt="Hero" />
// ✅ İyi — WebP dönüşümü, lazy loading, boyut optimizasyonu
import Image from "next/image";
<Image
src="/hero.jpg"
alt="Hero görseli"
width={1200}
height={630}
priority // LCP görseli için — preload eder
sizes="(max-width: 768px) 100vw, 1200px"
/>priority prop'unu sadece fold üstündeki (ekranda ilk görünen) görsele ekle. Diğerlerine ekleme — gereksiz preload yapar.
2. Font Optimizasyonu ile CLS Sıfırlama
Google Fonts'u next/font ile yüklemek layout shift'i tamamen ortadan kaldırır.
// src/fonts/fonts.ts
import { Inter, JetBrains_Mono } from "next/font/google";
export const bodyFont = Inter({
subsets: ["latin"],
variable: "--font-body",
display: "swap", // FOUT yerine FOUT — daha iyi UX
preload: true,
});
export const monoFont = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-mono",
display: "swap",
});display: "swap" ile font yüklenene kadar sistem fontu gösterilir, yüklenince değişir. Bu CLS'yi sıfıra indirir.
3. Dynamic Import ile INP İyileştirme
Ağır bileşenleri lazy yüklemek JavaScript parse süresini kısaltır, INP'yi düşürür.
import dynamic from "next/dynamic";
// ❌ Kötü — tüm bundle'a giriyor
import HeavyChart from "@/components/HeavyChart";
// ✅ İyi — sadece kullanıldığında yüklenir
const HeavyChart = dynamic(() => import("@/components/HeavyChart"), {
loading: () => <div className="h-64 animate-pulse bg-zinc-800 rounded" />,
ssr: false, // Sadece client-side gereken bileşenler için
});4. Image Boyutlarını Önceden Belirt
Boyutu bilinmeyen görseller CLS'ye neden olur. Her zaman width ve height belirt.
// ❌ CLS riski — tarayıcı boyutu bilmiyor, layout kayıyor
<Image src={url} alt="..." fill />
// ✅ Boyut belirli — layout stable
<Image
src={url}
alt="..."
width={400}
height={300}
className="object-cover"
/>fill kullanmak zorundaysan parent'a position: relative ve sabit boyut ver:
<div className="relative w-full aspect-video">
<Image src={url} alt="..." fill className="object-cover" />
</div>5. Preconnect ile TTFB Düşürme
Kritik üçüncü taraf kaynaklara erken bağlantı kur:
// next.config.mjs headers()
{
key: "Link",
value: [
"<https://fonts.googleapis.com>; rel=preconnect",
"<https://fonts.gstatic.com>; rel=preconnect; crossorigin",
"<https://www.googletagmanager.com>; rel=dns-prefetch",
].join(", ")
}6. Server Components'i Maksimize Et
React Server Components client bundle'ı küçültür, hydration süresini kısaltır.
// ✅ Server Component — bundle'a girmiyor
// "use client" direktifi YOK
async function ProductList() {
const products = await db.query("SELECT * FROM products");
return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
// Sadece interaktif kısımlar client component olsun
"use client";
function AddToCartButton({ productId }: { productId: number }) {
const [loading, setLoading] = useState(false);
// ...
}7. Bundle Analizi
Hangi paket ne kadar yer kaplıyor?
ANALYZE=true npm run build@next/bundle-analyzer ile görsel rapor alırsın. 100KB+ paketleri dynamic import'a taşı.
Sonuç
Bu optimizasyonları uyguladıktan sonra beklenen iyileşmeler:
| Metrik | Önce | Sonra |
|---|---|---|
| LCP | 4.2s | 1.8s |
| INP | 380ms | 95ms |
| CLS | 0.24 | 0.02 |
Performans tek seferlik bir iş değil — her deploy'dan sonra PageSpeed Insights ile kontrol et.