Next.js'te Veri Çekme Stratejileri: SSR, SSG, ISR

Next.js'te Veri Çekme Stratejileri: SSR, SSG, ISR
Veri çekme (data fetching), her web uygulamasının temel taşıdır. Next.js, farklı senaryolar için farklı veri çekme stratejileri sunar: Server-Side Rendering (SSR), Static Site Generation (SSG) ve Incremental Static Regeneration (ISR). Her stratejinin kendine özgü avantajları ve kullanım alanları vardır. Bu rehberde tüm stratejileri detaylı örneklerle inceleyeceğiz.
Server Component'lerde Veri Çekme
App Router'da Server Component'ler async olabilir, bu sayede veri çekme son derece basit hale gelir:
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
if (!res.ok) {
throw new Error('Yazılar yüklenemedi');
}
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
<h1>Blog Yazıları</h1>
{posts.map((post: Post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
Static Site Generation (SSG)
SSG, sayfaları build zamanında oluşturur. En yüksek performansı sağlar çünkü sayfalar CDN'den sunulur. Next.js'te fetch varsayılan olarak cache'lenir:
// Varsayılan: SSG (Static)
// fetch sonuçları build zamanında cache'lenir
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
cache: 'force-cache', // Varsayılan davranış
});
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<div className="grid grid-cols-3 gap-6">
{products.map((product: Product) => (
<div key={product.id} className="border p-4 rounded">
<h3>{product.name}</h3>
<p>{product.price} TL</p>
</div>
))}
</div>
);
}
Dynamic Params ile SSG
Dinamik route'lar için generateStaticParams fonksiyonunu kullanarak build zamanında hangi sayfaların oluşturulacağını belirleyebilirsiniz:
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then((r) =>
r.json()
);
return posts.map((post: Post) => ({
slug: post.slug,
}));
}
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`);
if (!res.ok) return null;
return res.json();
}
export default async function BlogPost({
params,
}: {
params: { slug: string };
}) {
const post = await getPost(params.slug);
if (!post) {
notFound();
}
return (
<article>
<h1>{post.title}</h1>
<time>{post.date}</time>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
generateStaticParams ile birlikte dynamicParams = false ayarlarsanız, tanımlı olmayan path'ler 404 döner. Varsayılanda ise on-demand olarak render edilir.
Server-Side Rendering (SSR)
SSR, her istekte sayfayı sunucu tarafında render eder. Her zaman güncel veri gerektiğinde kullanılır:
// app/dashboard/page.tsx
// cache: 'no-store' ile her istekte taze veri çekilir
async function getDashboardData() {
const res = await fetch('https://api.example.com/dashboard', {
cache: 'no-store', // SSR: her istekte yeni veri
});
return res.json();
}
export default async function DashboardPage() {
const data = await getDashboardData();
return (
<div>
<h1>Dashboard</h1>
<div className="grid grid-cols-4 gap-4">
<StatCard title="Toplam Kullanıcı" value={data.totalUsers} />
<StatCard title="Aktif Oturum" value={data.activeSessions} />
<StatCard title="Günlük Gelir" value={data.dailyRevenue} />
<StatCard title="Yeni Kayıt" value={data.newSignups} />
</div>
</div>
);
}
// Alternatif: Route segment config ile
export const dynamic = 'force-dynamic'; // Tüm sayfa SSR
Incremental Static Regeneration (ISR)
ISR, SSG ve SSR'ın en iyi yanlarını birleştirir. Statik sayfa belirli bir süre sonra arka planda yenilenir:
// app/news/page.tsx
async function getNews() {
const res = await fetch('https://api.example.com/news', {
next: { revalidate: 3600 }, // 1 saat sonra yenile
});
return res.json();
}
export default async function NewsPage() {
const news = await getNews();
return (
<div>
<h1>Haberler</h1>
{news.map((item: NewsItem) => (
<article key={item.id} className="border-b py-4">
<h2>{item.title}</h2>
<p className="text-gray-600">{item.summary}</p>
<time>{item.publishedAt}</time>
</article>
))}
</div>
);
}
// Alternatif: Route segment config ile
export const revalidate = 3600; // Tüm sayfa için
revalidate süresi dolduğunda, ilk gelen kullanıcı cache'li versiyonu görür. Arka planda sayfa yenilenir ve sonraki kullanıcılar güncel versiyonu alır (stale-while-revalidate stratejisi).
On-Demand Revalidation
Zamana dayalı revalidation yerine, belirli olaylar tetiklendiğinde sayfayı yenileyebilirsiniz:
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
const { path, tag, secret } = await request.json();
// Güvenlik kontrolü
if (secret !== process.env.REVALIDATION_SECRET) {
return Response.json({ message: 'Geçersiz token' }, { status: 401 });
}
if (path) {
revalidatePath(path);
return Response.json({ revalidated: true, path });
}
if (tag) {
revalidateTag(tag);
return Response.json({ revalidated: true, tag });
}
return Response.json({ message: 'Path veya tag gerekli' }, { status: 400 });
}
// Tag ile fetch kullanımı
async function getProduct(id: string) {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { tags: [`product-${id}`] },
});
return res.json();
}
Paralel ve Sıralı Veri Çekme
Performans için bağımsız veri çekme işlemlerini paralel yapın:
// ❌ Sıralı (Yavaş) — Toplam süre: A + B + C
export default async function Page() {
const users = await getUsers(); // 1 saniye
const posts = await getPosts(); // 1 saniye
const comments = await getComments(); // 1 saniye
// Toplam: ~3 saniye
}
// ✅ Paralel (Hızlı) — Toplam süre: max(A, B, C)
export default async function Page() {
const [users, posts, comments] = await Promise.all([
getUsers(), // 1 saniye
getPosts(), // 1 saniye
getComments(), // 1 saniye
]);
// Toplam: ~1 saniye
}
Promise.all ile paralel yapın.
Strateji Karşılaştırması
| Strateji | Ne Zaman Render? | Ne Zaman Kullanılır? | Performans |
|---|---|---|---|
| SSG | Build zamanı | Blog, dokümantasyon, pazarlama sayfaları | En hızlı |
| ISR | Build + periyodik yenileme | E-ticaret, haber siteleri | Çok hızlı |
| SSR | Her istek | Dashboard, kişiselleştirilmiş içerik | Hızlı |
ORM ile Doğrudan Veritabanı Erişimi
Server Component'lerde ORM kullanarak doğrudan veritabanına erişebilirsiniz:
// lib/db.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma = globalForPrisma.prisma || new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
// app/users/page.tsx
import { prisma } from '@/lib/db';
export default async function UsersPage() {
const users = await prisma.user.findMany({
include: { posts: { take: 3 } },
orderBy: { createdAt: 'desc' },
});
return (
<ul>
{users.map((user) => (
<li key={user.id}>
<strong>{user.name}</strong> — {user.posts.length} yazı
</li>
))}
</ul>
);
}
Sonuç
Next.js'in veri çekme stratejileri, her senaryo için doğru çözümü sunar. SSG ile maksimum performans, SSR ile anlık güncellik, ISR ile her ikisinin dengesi elde edilebilir. Doğru stratejiyi seçmek, uygulamanızın performansı ve kullanıcı deneyimi açısından kritik öneme sahiptir. Paralel veri çekme ve on-demand revalidation gibi teknikleri de kullanarak uygulamanızı optimize edebilirsiniz.