Next.js Performance: Core Web Vitals Optimizasyonu

Next.js Performance: Core Web Vitals Optimizasyonu
Web performansı, kullanıcı deneyimi ve SEO sıralaması için kritik bir faktördür. Google'ın Core Web Vitals metrikleri (LCP, INP, CLS), sayfa deneyimini ölçen temel göstergelerdir. Next.js, bu metrikleri optimize etmek için güçlü yerleşik araçlar sunar. Bu yazıda Next.js uygulamalarında Core Web Vitals optimizasyonunu kapsamlı olarak inceleyeceğiz.
Core Web Vitals Nedir?
| Metrik | Açıklama | İyi | Kötü |
|---|---|---|---|
| LCP (Largest Contentful Paint) | En büyük içerik elemanının yüklenme süresi | < 2.5s | > 4.0s |
| INP (Interaction to Next Paint) | Kullanıcı etkileşimlerine yanıt süresi | < 200ms | > 500ms |
| CLS (Cumulative Layout Shift) | Sayfadaki görsel kayma miktarı | < 0.1 | > 0.25 |
LCP Optimizasyonu
Image Optimization
import Image from 'next/image';
// LCP elemanı olan hero image'i optimize edin
export default function HeroSection() {
return (
<section className="hero">
<Image
src="/hero-banner.jpg"
alt="Hero Banner"
width={1200}
height={600}
priority // LCP elemanı için priority ekleyin
sizes="100vw" // Responsive boyut bilgisi
quality={85} // Kalite ayarı
placeholder="blur" // Blur placeholder
blurDataURL="data:image/jpeg;base64,/9j/4AAQ..."
/>
<h1>Ana Başlık</h1>
</section>
);
}
priority prop'u, Next.js'e bu görselin LCP elemanı olduğunu bildirir ve <link rel="preload"> ekler. Sadece above-the-fold (ilk görünen) görsellerde kullanın. Her sayfada en fazla 1-2 görselde priority kullanmalısınız.
Font Optimization
// app/layout.js
import { Inter, Fira_Code } from 'next/font/google';
const inter = Inter({
subsets: ['latin', 'latin-ext'],
display: 'swap', // FOUT yerine FOIT önleme
preload: true,
variable: '--font-inter',
});
const firaCode = Fira_Code({
subsets: ['latin'],
display: 'swap',
variable: '--font-fira-code',
});
export default function RootLayout({ children }) {
return (
<html lang="tr" className={`${inter.variable} ${firaCode.variable}`}>
<body className={inter.className}>
{children}
</body>
</html>
);
}
Critical CSS ve Bundle Optimization
// next.config.js
const nextConfig = {
// Experimental CSS optimization
experimental: {
optimizeCss: true,
},
// Bundle analyzer
webpack: (config, { isServer }) => {
if (process.env.ANALYZE === 'true') {
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
config.plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: isServer ? '../analyze/server.html' : './analyze/client.html',
})
);
}
return config;
},
};
INP Optimizasyonu
React.startTransition ile Önceliklendirme
'use client';
import { useState, useTransition } from 'react';
function SearchWithResults({ items }) {
const [query, setQuery] = useState('');
const [filteredItems, setFilteredItems] = useState(items);
const [isPending, startTransition] = useTransition();
const handleSearch = (e) => {
const value = e.target.value;
setQuery(value); // Yüksek öncelik - input hemen güncellenir
startTransition(() => {
// Düşük öncelik - filtreleme arka planda yapılır
const filtered = items.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase())
);
setFilteredItems(filtered);
});
};
return (
<div>
<input value={query} onChange={handleSearch} placeholder="Ara..." />
{isPending && <p>Filtreleniyor...</p>}
<ul>
{filteredItems.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
Dynamic Import ile Code Splitting
import dynamic from 'next/dynamic';
// Ağır bileşenleri lazy load edin
const HeavyChart = dynamic(() => import('@/components/Chart'), {
loading: () => <div className="chart-skeleton" />,
ssr: false, // Sadece client'ta yükle
});
const MarkdownEditor = dynamic(
() => import('@/components/MarkdownEditor'),
{ ssr: false }
);
// Koşullu lazy load
function ProductPage({ product }) {
const [showReviews, setShowReviews] = useState(false);
return (
<div>
<ProductDetails product={product} />
<button onClick={() => setShowReviews(true)}>
Yorumları Göster
</button>
{showReviews && <HeavyChart data={product.reviews} />}
</div>
);
}
Third-Party Script Yönetimi
import Script from 'next/script';
export default function Layout({ children }) {
return (
<html>
<body>
{children}
{/* Analytics - sayfa yüklendikten sonra */}
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX"
strategy="afterInteractive"
/>
{/* Chat widget - idle durumda */}
<Script
src="https://widget.intercom.io/widget/xxx"
strategy="lazyOnload"
/>
{/* Kritik script - head'de */}
<Script
src="/critical-polyfill.js"
strategy="beforeInteractive"
/>
</body>
</html>
);
}
CLS Optimizasyonu
Image Boyut Belirtme
// Her zaman width ve height belirtin
<Image src="/photo.jpg" width={800} height={600} alt="Foto" />
// Responsive görsellerde aspect-ratio kullanın
<div style={{ position: 'relative', aspectRatio: '16/9' }}>
<Image src="/banner.jpg" fill alt="Banner"
style={{ objectFit: 'cover' }} />
</div>
Font Display Swap
// next/font otomatik olarak font-display: swap ekler
// Manuel font yükleme yapıyorsanız:
const myFont = localFont({
src: './fonts/MyFont.woff2',
display: 'swap',
fallback: ['system-ui', 'arial'],
adjustFontFallback: 'Arial', // Fallback font metrikleri ayarlama
});
Skeleton ve Placeholder
// loading.js - Layout shift'i önleyen skeleton
export default function Loading() {
return (
<div className="page-skeleton">
{/* Gerçek içerikle aynı boyutlarda skeleton */}
<div className="skeleton-header" style={{ height: 60 }} />
<div className="skeleton-hero" style={{ aspectRatio: '16/9' }} />
<div className="skeleton-content">
<div className="skeleton-line" style={{ width: '80%', height: 24 }} />
<div className="skeleton-line" style={{ width: '60%', height: 16 }} />
<div className="skeleton-line" style={{ width: '70%', height: 16 }} />
</div>
</div>
);
}
Caching Stratejileri
// Server Component'lerde fetch caching
async function ProductList() {
// 1 saat boyunca cache'le
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 },
}).then((res) => res.json());
return products.map((p) => <ProductCard key={p.id} product={p} />);
}
// Statik sayfalar için generateStaticParams
export async function generateStaticParams() {
const products = await getProducts();
return products.map((p) => ({ slug: p.slug }));
}
// Route segment config
export const revalidate = 3600; // 1 saat
export const dynamic = 'force-static'; // Statik olarak oluştur
Performans Ölçüm Araçları
// Web Vitals raporlama
// app/layout.js
import { SpeedInsights } from '@vercel/speed-insights/next';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<SpeedInsights />
</body>
</html>
);
}
// Custom Web Vitals raporlama
// app/web-vitals.js
'use client';
import { useReportWebVitals } from 'next/web-vitals';
export function WebVitalsReporter() {
useReportWebVitals((metric) => {
// Analytics servisine gönder
console.log(metric.name, metric.value);
// Google Analytics 4'e gönder
window.gtag?.('event', metric.name, {
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
event_label: metric.id,
non_interaction: true,
});
});
return null;
}
Performance Checklist
- LCP: Hero image'de
prioritykullanın, font'lardadisplay: swapekleyin - INP: Ağır hesaplamaları
startTransitionile sarın, code splitting uygulayın - CLS: Tüm görsellere boyut belirtin, skeleton'ları gerçek boyutlarda tasarlayın
- Bundle:
@next/bundle-analyzerile analiz edin, gereksiz paketleri kaldırın - Caching: Fetch caching, ISR ve static generation kullanın
- Scripts: 3. parti scriptleri
afterInteractiveveyalazyOnloadile yükleyin - Fonts:
next/fontkullanın, subset ve preload ayarlayın - Images:
next/imageile otomatik optimizasyon, WebP/AVIF format
Sonuç
Next.js, Core Web Vitals optimizasyonu için güçlü yerleşik araçlar sunar. next/image ile otomatik görsel optimizasyonu, next/font ile font performansı, next/script ile third-party script yönetimi ve Streaming SSR ile aşamalı render bu araçların başlıcalarıdır. LCP için priority image ve font swap, INP için code splitting ve useTransition, CLS için boyut belirtme ve skeleton tasarımı temel stratejilerdir. Vercel Speed Insights ve web-vitals kütüphanesi ile metrikleri sürekli takip ederek performansı koruyun.