İçeriğe geç
nextjs

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

Tarık Tunç
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>
  );
}
İpucu: 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
Bilgi: ISR'da 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
}
Uyarı: Sıralı veri çekme, request waterfall oluşturur ve sayfa yükleme süresini ciddi şekilde uzatır. Birbirinden bağımsız istekleri mutlaka 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.

Kaynaklar

İlgili Yazılar