Önkoşullar ve Kapsam
Bu rehber, Next.js App Router (13 ve sonrası) kullanan orta seviye geliştiriciler için hazırlanmıştır. React ve temel HTTP önbellekleme (cache) kavramlarına aşina olduğunuz varsayılır. Next.js sürümler arasında caching davranışını değiştirdiği için, burada anlatılan API'lerin sürüme bağlı olduğunu unutmayın; uygulamadan önce kullandığınız sürümün resmi dokümantasyonunu doğrulayın.
Next.js'in caching modeli, sürümler arasında en çok değişen alanlardan biridir. Bu yüzden "şu sürümde şöyle davranır" gibi kesin ifadelerden kaçınıp, hangi API'nin ne işe yaradığını ve nasıl doğrulanacağını anlatacağız.
SEO: Metadata ve Render Stratejisi
Metadata API
App Router'da sayfa bazlı meta etiketleri metadata export'u ile tanımlarsınız. Bu, <head> içeriğini manuel yönetmekten daha güvenli ve tutarlıdır.
// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
export async function generateMetadata(
{ params }: { params: { slug: string } }
): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
alternates: { canonical: `/blog/${post.slug}` },
openGraph: {
title: post.title,
description: post.excerpt,
type: "article",
},
};
}
generateMetadata asenkron çalışır ve sayfa render'ından önce çözülür. Bir sayfada hem statik metadata hem generateMetadata tanımlamayın; ikisi çakışır.
Canonical ve Dinamik Segmentler
Dinamik rotalarda canonical URL'yi mutlaka belirtin. Aksi halde aynı içerik farklı sorgu parametreleriyle birden fazla URL altında indekslenebilir.
Render Stratejisi Seçimi
- Statik (SSG): İçerik derleme zamanında üretilir. Pazarlama sayfaları, blog yazıları için uygundur.
- Dinamik (SSR): Her istekte üretilir. Kişiselleştirilmiş veya sık değişen içerik için.
- ISR (Incremental Static Regeneration): Statik üretim + belirli aralıklarla yenileme.
SEO açısından kritik nokta: arama motorlarına sunulan HTML'in sunucuda üretilmiş olmasıdır. İçeriği yalnızca istemci tarafında render eden bir bileşen, tarayıcı dışı botlar için görünmez olabilir. Kritik içeriği sunucu bileşenlerinde tutun.
Caching Katmanları
Next.js'te caching tek bir mekanizma değil, birkaç katmandan oluşur. Bunları karıştırmak en yaygın hata kaynağıdır.
1. Request Memoization
Aynı render ağacında aynı fetch çağrısı tekrar edilirse, Next.js bunları tek isteğe indirger. Bu yalnızca tek bir render döngüsü içinde geçerlidir.
// Aynı render içinde iki kez çağrılsa da tek HTTP isteği gider
const a = await fetch("https://api.example.com/data");
const b = await fetch("https://api.example.com/data");
2. Data Cache
fetch çağrılarının sonucu sunucuda saklanabilir. Davranışı cache ve next.revalidate seçenekleriyle kontrol edersiniz.
// Belirli aralıklarla yenile
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 60 },
});
// Her istekte taze veri
const fresh = await fetch("https://api.example.com/user", {
cache: "no-store",
});
revalidate: 60 ifadesi "60 saniye sonra bir sonraki istekte yenile" anlamına gelir; arka planda sürekli çalışan bir zamanlayıcı değildir.
3. Full Route Cache
Statik rotalar derleme zamanında HTML ve RSC payload olarak saklanır. Bir rotayı dinamik yapan şeyler (örneğin cookies(), headers() kullanımı veya no-store fetch) bu cache'i devre dışı bırakır.
4. Router Cache (İstemci)
İstemci tarafında gezinti sırasında önceden getirilen (prefetch) segmentler bellekte tutulur. Bu cache'in süresi ve geçersiz kılma davranışı sürüme göre değişir; kesin davranış için sürüm notlarına bakın.
Cache Geçersiz Kılma
Sunucu tarafında veri değiştiğinde ilgili cache'i temizlemek için revalidatePath veya revalidateTag kullanılır.
// app/actions.ts
"use server";
import { revalidateTag } from "next/cache";
export async function updatePost(id: string, data: FormData) {
await savePost(id, data);
revalidateTag("posts");
}
revalidateTag'in çalışması için ilgili fetch çağrısında aynı etiketin tanımlı olması gerekir:
await fetch("https://api.example.com/posts", {
next: { tags: ["posts"] },
});
Performans Optimizasyonu
Bileşen Sınırlarını Doğru Çizmek
Sunucu bileşenlerini varsayılan kabul edin. "use client" yalnızca durum, efekt veya tarayıcı API'si gerektiğinde eklenmelidir. İstemci bileşeni ne kadar yukarıda başlarsa, o kadar fazla JavaScript pakete dahil olur.
// Kötü: tüm sayfayı istemciye taşır
"use client";
export default function Page() {
return <HeavyServerContent />;
}
// İyi: yalnızca etkileşimli kısmı istemciye taşır
import InteractiveWidget from "./InteractiveWidget";
export default function Page() {
return (
<>
<HeavyServerContent />
<InteractiveWidget />
</>
);
}
Görseller
next/image bileşeni boyutlandırma, modern format ve lazy loading sağlar. width/height veya fill belirtmek zorunludur; aksi halde düzen kayması (layout shift) oluşur ve Core Web Vitals metriği olumsuz etkilenir.
import Image from "next/image";
<Image
src="/hero.jpg"
alt="Açıklayıcı metin"
width={1200}
height={630}
priority
/>
priority yalnızca ekranın ilk görünümündeki (above-the-fold) görsele eklenmelidir. Her görsele eklemek fayda yerine zarar verir.
Fontlar
next/font ile yüklenen fontlar otomatik olarak self-host edilir ve düzen kaymasını azaltır.
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"], display: "swap" });
export default function RootLayout({ children }) {
return <html lang="tr" className={inter.className}>{children}</html>;
}
Paket Boyutu
- Sunucu bileşenlerine ağır bağımlılıkları taşıyın; bunlar istemci paketine girmez.
- İstemci tarafında büyük kütüphaneleri dinamik import ile geciktirin.
- Kullanılmayan bağımlılıkları düzenli olarak gözden geçirin. Paket analiz aracıyla hangi modülün boyutu şişirdiğini ölçün.
Yaygın Hatalar
Her şeyi no-store yapmak. Geliştirme sırasında "taze veri gelsin" diye her fetch'e no-store eklemek, üretimde Full Route Cache'i tamamen devre dışı bırakır ve sunucu yükünü artırır. Önce hangi verinin gerçekten anlık olması gerektiğini belirleyin.
revalidate ile zamanlayıcı beklemek. revalidate: 300 "5 dakikada bir yenile" değil, "en fazla 5 dakika eski veri sun, sonraki istekte yenile" demektir. Trafik yoksa yenileme de olmaz.
İstemci bileşenlerine veri çekmek. SEO açısından kritik içeriği istemci tarafında useEffect ile çekmek, botların içeriği görmemesine yol açabilir.
Cache etiketlerini tutarsız kullanmak. revalidateTag("posts") çağırıp ilgili fetch'te tags: ["post"] yazmak sessiz bir hatadır; hiçbir şey yenilenmez ve nedenini bulmak zorlaşır.
Güvenlik Etkileri
- Gizli veriyi cache'lemeyin. Kullanıcıya özel yanıtları (token, kişisel veri) paylaşılan bir cache katmanında saklamak, verinin başka kullanıcılara sızmasına yol açabilir. Kişiselleştirilmiş yanıtlar için
no-storekullanın. revalidatePathverevalidateTagçağrılarını yetkilendirin. Bu fonksiyonlar bir Server Action veya route handler içinde çalışır; çağıran kullanıcının yetkisini doğrulamadan cache'i temizlemesine izin vermeyin.- Ortam değişkenleri. Yalnızca sunucuda kullanılacak sırları
NEXT_PUBLIC_önekiyle tanımlamayın; bu önek değişkeni istemci paketine gömer. - Kullanıcı girdisiyle cache anahtarı üretmeyin. Doğrudan kullanıcıdan gelen bir değeri etiket veya yol olarak kullanmak, istenmeyen cache'lerin oluşmasına veya temizlenmesine neden olabilir.
Doğrulama Adımları
- Üretim derlemesi alın (
next build) ve çıktıdaki rota tablosunu inceleyin: hangi rota statik, hangisi dinamik işaretlenmiş? - Ağ sekmesinde ilgili sayfanın HTML'inin sunucudan geldiğini doğrulayın.
- Cache davranışını test ederken yanıt başlıklarına ve zaman damgasına bakın; verinin gerçekten yenilenip yenilenmediğini gözlemleyin.
- Core Web Vitals metriklerini gerçek kullanıcı verisiyle izleyin; laboratuvar ölçümü tek başına yeterli değildir.
Caching davranışı Next.js'in en hızlı değişen alanı olduğu için, buradaki API'leri kendi sürümünüzün dokümantasyonuyla karşılaştırmadan üretime almayın.