Next.js Performans Optimizasyonu Nereden Başlamalı?
Next.js bir projeye eklendikten sonra "artık optimize" hissi yerleşir. Framework, image optimizasyonunu, font yüklemeyi, statik ve sunucu taraflı render'ı pek çok projede varsayılan olarak devreye alır. Oysa bir Lighthouse raporu açıldığında LCP'nin 3 saniyenin üzerinde olduğu, client JavaScript'in 800 KB'ı aştığı ya da aynı veri kaynağına hem sunucu hem istemci tarafından istek gönderildiği görülebilir. Framework suçlu değildir; kullanılmayan veya yanlış kurulmuş araçlar suçludur.
Next.js'in sunduğu optimizasyon katmanlarının bir bölümü hiçbir yapılandırma gerektirmez, bir bölümü açıkça etkinleştirilmesi gereken seçenekler içerir, bir bölümü ise yanlış kullanıldığında tam tersine maliyet üretir. next/image bileşenine priority prop'u verilmemesi LCP görselini geç yükletir. 'use client' direktifinin geniş bileşen ağaçlarına yayılması Server Components'ın sağladığı bundle küçültme avantajını sıfırlar. Google Fonts'un <link> etiketi aracılığıyla <head>'e eklenmesi, next/font'un çözdüğü render blocking ve layout kayması sorunlarını geri getirir.
Her araç için kapsamlı bir referans sunmak yerine hangi performans sorununun hangi araçla ele alınacağını ve araçların nerede tersine çalışabileceğini göstermek, daha verimli bir başlangıç noktası olur.
next/image'ın LCP üzerindeki etkisi ve doğru yapılandırma
Sayfanın görünür alanındaki en büyük görsel, yani LCP elemanı, priority prop'u olmadan loading="lazy" davranışına girer. Tarayıcı bu görseli viewport'a yaklaşana kadar yüklemez; oysa sayfa açıldığında zaten görünür konumdadır. priority eklendiğinde Next.js bu görseli <link rel="preload"> ile öne çeker ve loading="eager" olarak işaretler:
<Image
src="/img/hero.webp"
width={1536}
height={1024}
priority
alt="Açıklayıcı metin"
/>
sizes prop'u atlandığında oluşan sorun daha sessizdir. Next.js bir srcset üretir ama sizes belirtilmezse tarayıcı görselin viewport'un tamamını kaplayacağını varsayar ve gereksiz yüksek çözünürlüklü versiyonu indirir. Bir görsel yalnızca 640 piksel genişliğinde gösterilecekse sizes="(max-width: 640px) 100vw, 640px" gibi bir değer hem indirme boyutunu hem de LCP süresini düşürür — mobil cihazlarda bu fark 100–300 KB arasında olabilir.
width ve height prop'larının unutulması ise CLS kaynağına dönüşür. Bu iki değer olmadan Next.js görsel için alan ayıramaz; görsel yüklendiğinde sayfa kayar. Dinamik görseller için fill layout'u kullanılıyorsa sarmalayan konteynerin position: relative ve belirli bir yüksekliği olması şarttır — aksi hâlde görsel sıfır yükseklikte render edilir ve sayfada görünmez.
Server Components ile client bundle'ı inceltmek
App Router'da bileşenler varsayılan olarak Server Component'tır. Sunucu tarafında render edilen bileşenler client bundle'a dahil olmaz; içe aktardıkları kütüphaneler, yaptıkları veri sorguları ve ürettikleri HTML tarayıcıya JavaScript olarak gelmez. Doğru kullanıldığında bu yaklaşım, client JS yükünü önemli ölçüde azaltır.
Sorun 'use client' direktifinin yanlış konumlandırılmasından kaynaklanır. Bir bileşen ağacının tepesine 'use client' eklemek, tüm alt ağacı client bundle'a çeker. Çoğu durumda yalnızca bir butona click handler ya da bir forma onChange gereklidir. Bu küçük etkileşimli parça ayrı bir bileşene taşınarak 'use client' yalnızca orada kullanılırsa, geri kalan ağaç sunucu tarafında kalır.
Ağır kütüphaneler de zaman zaman istemci bağlamına sürüklenir. Bir grafik kütüphanesi ya da PDF oluşturucu yalnızca belirli sayfalarda kullanılıyorsa ve bu sayfalar dinamik route'larsa, Next.js'in dynamic() fonksiyonu { ssr: false } seçeneğiyle birlikte kullanılarak kütüphane yalnızca istemcide ve yalnızca ihtiyaç duyulduğunda yüklenir. Bu sızıntıları tespit etmenin en pratik yolu bundle analiz haritasını açıp hangi modülün hangi chunk'a girdiğine bakmaktır.
next/font ile font yüklemesini framework içine almak
Google Fonts'u <link> etiketi aracılığıyla yüklemek iki sorun üretir: tarayıcı fonts.googleapis.com'a bağlanmadan önce DNS sorgusu ve TLS el sıkışması yapar; font tanımı gelene kadar ise metin render edilemez. next/font bu bağlantı maliyetini ortadan kaldırır. Fontları build zamanında indirir, projenin kendi sunucusundan sunar ve font-display: swap otomatik olarak uygulanır:
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
adjustFontFallback seçeneği — Google Fonts için varsayılan olarak açıktır — web fontunun metriklerine yakın bir fallback tanımı üretir. Font yüklendiğinde oluşan metin kayması bu şekilde belirgin biçimde azalır. Farklı ağırlıklar ve stiller için ayrı import yerine tek bir font instance kullanmak yeterlidir; her ek varyant ayrı bir ağ isteği anlamına gelir.
next/font ile manuel Google Fonts entegrasyonu arasındaki fark genellikle 50–150 ms TTFB ve 0.05–0.15 CLS puanı civarındadır. Rakamlar küçük görünse de toplam skoru eşik değerin üstüne ya da altına taşıyabilir. Font yükleme optimizasyonunun render blocking ile doğrudan bağlantısı, bu konfigürasyonu erken yapılması gereken bir adım hâline getirir.
App Router'da veri çekme ve istemci isteği yükü
Pages Router'dan App Router'a geçişte sık karşılaşılan bir örüntü, veri çekmeyi hâlâ istemci tarafına bırakmaktır. useEffect + fetch kombinasyonu tarayıcı HTML'yi aldıktan sonra çalışır; veri gelene kadar içerik boş görünür. Bu durum hem algılanan yükleme süresini hem de Core Web Vitals değerlerini olumsuz etkiler.
Server Components'ta veri doğrudan bileşen içinde fetch edilir; bu işlem sunucuda çalışır ve tarayıcıya zaten doldurulmuş HTML gider:
async function ProductList() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 },
}).then(res => res.json());
return (
<ul>
{products.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}
next: { revalidate } değeri ISR (Incremental Static Regeneration) mantığıyla çalışır: ilk istek cache'den sunulur, arka planda yeniden üretilir. cache: 'no-store' ile tamamen dinamik, cache: 'force-cache' ile tamamen statik davranış sağlanır. Hangi route'un hangi stratejiyi gerektirdiği veri tazeliği ve trafik hacmiyle belirlenir.
Şelale (waterfall) sorunu App Router'da da görülür: bir parent bileşen veriyi beklerken alt bileşenler kendi isteklerini başlatamaz. Bağımsız veri ihtiyaçları olan bileşenleri Promise.all ile paralel çekmek ya da Suspense ile akış modeline taşımak bu engeli aşar. Suspense sayesinde hazır olan bileşenler tarayıcıya hemen gönderilir, veri bekleyen kısımlar fallback gösterir ve hazır olduğunda akar — tüm sayfanın tamamlanmasını beklemek gerekmez.
Ölçüm: @next/bundle-analyzer, Speed Insights ve Lighthouse
Optimizasyonun etkisini doğrulamak için birkaç araç paralel kullanılır. @next/bundle-analyzer, npm run build çıktısını görsel bir haritaya dönüştürür; hangi chunk'ın ne kadar yer kapladığı, hangi kütüphanenin beklenenin üzerinde ağırlık taşıdığı açıkça görülür. Analiz her zaman production build üzerinde yapılmalıdır — geliştirme modunda tree shaking ve minification devrede olmadığından rakamlar yanıltıcı olur.
next build çıktısı da doğrudan bir ölçüm noktasıdır. Her route'un sayfa boyutu ve ilk yük JS değeri terminalde görünür. Next.js kendi eşiğini uygular: 130 KB'ı aşan ilk yük JS değerleri uyarı üretir. Bu rakam route bazında incelenir; ortak chunk'lar ile route'a özgü chunk'lar ayrı değerlendirilmelidir çünkü her sayfada tekrar eden vendor kodu ortak chunk'ın sorumluluğundadır.
Vercel platformunda barındırılan projeler için @vercel/speed-insights paketi, gerçek kullanıcı ölçümlerini doğrudan dashboard'a aktarır. LCP, CLS ve INP değerleri cihaz ve coğrafya kırılımında izlenebilir. Bu saha verisi, lab ölçümünden farklı davranış gösteren durumları ortaya çıkarır; özellikle mobil kullanıcıların LCP'si masaüstüne kıyasla belirgin biçimde yüksekse, optimizasyon önceliği netleşir. PageSpeed Insights gerçek URL'leri CrUX verileriyle birlikte gösterir ve saha verisinin mevcut olduğu projeler için doğrudan karşılaştırma imkânı sunar.
Next.js'in sunduğu araçlar framework kullanıcısına ciddi bir avantaj sağlar — ancak yalnızca doğru konuma yerleştirildiğinde. priority prop'u eklenmemiş bir görsel, 'use client' ile istemciye sürüklenmiş bir bileşen ağacı ya da next/font yerine HTML'e eklenen bir font bağlantısı, framework'ün vaat ettiği kazanımları teker teker geri alır.
Önceliklendirme açısından bakıldığında, LCP'yi etkileyen image yapılandırması ile client bundle'ı şişiren 'use client' sınırları genellikle en kısa sürede en büyük kazanımı sağlar. Font yapılandırması ve veri çekme mimarisi orta vadeli iyileştirmelerdir. Ölçüm altyapısı ise baştan kurulmalıdır — değişiklikler sayısal karşılık bulmadığında ne ilerleme görülür ne de gerileme fark edilir.