Musa Yılmaz
·5 dk okuma

Next.js ISR ve On-Demand Revalidation Nasıl Çalışır?

nextjsisrperformanscms

Statik üretim (SSG) hızlıdır: sayfa build sırasında bir kez üretilir, sonrasında CDN'den milisaniyeler içinde servis edilir. Ama bir sorunu vardır — içerik değiştiğinde sayfa eski halinde kalır. Yeniden deploy etmeden güncellenmez.

Sunucu tarafı render (SSR) bu sorunu çözer ama her istekte sunucuyu çalıştırır; hem yavaştır hem maliyetlidir.

ISR (Incremental Static Regeneration) ikisinin ortasıdır: sayfa statik kalır, ama arka planda belirli aralıklarla ya da bir tetikleyiciyle yeniden üretilir.

YöntemSayfa ne zaman üretilirHızİçerik güncelliği
SSGBuild anında, bir kezEn hızlıYeniden deploy etmeden değişmez
SSRHer istekteSunucu maliyetli, en yavaşHer zaman güncel
ISRBuild anında + arka planda periyodik ya da tetiklenerekSSG ile aynıDakikalar veya saniyeler içinde güncellenir

Zaman tabanlı yenileme

En basit kullanım, sayfaya bir yenileme süresi vermektir:

// app/blog/page.tsx
export const revalidate = 3600; // saniye cinsinden: 1 saat

Bu şu anlama gelir: sayfa statik servis edilir, ama üzerinden bir saat geçtikten sonra gelen ilk istek arka planda yeniden üretimi tetikler. O isteği yapan kullanıcı hâlâ eski sayfayı görür (bekletilmez), sonraki ziyaretçiler yeni halini alır.

Bu davranış "stale-while-revalidate" olarak bilinir ve önemli bir sonucu vardır: hiçbir kullanıcı yenileme için beklemez. Sayfa her zaman anında gelir, sadece bazen bir tur eski olabilir.

Vercel'de deploy ediyorsanız bu mekanizma ekstra ayar gerektirmez, framework'ün varsayılan davranışıdır; platformun ücretsiz katmanının diğer sınırlarını Vercel'de site yayınlama yazısında topladım.

Süreyi seçerken içeriğin değişme sıklığını düşünün:

İçerik türüMakul revalidate
Blog listesi3600 (1 saat)
Ürün fiyatı300 (5 dakika)
Kampanya sayfası60 (1 dakika)
Hakkımızda sayfası86400 (1 gün)

Zamanlanmış yayın: pratik bir kullanım

ISR'nin sık akla gelmeyen bir faydası, içeriği ileri tarihe programlamaktır. İçerik listesini filtrelerken tarihi gelecekte olanları eleyin:

function isVisible(post: Post) {
  const today = new Date().toISOString().slice(0, 10);
  if (post.published === false) return false;
  if (post.date > today) return false; // henüz yayın günü gelmedi
  return true;
}

revalidate tanımlı olduğu için sayfa periyodik olarak yeniden üretilir; yenilenme sırasında new Date() yeni günü döner ve o güne ait yazı kendiliğinden listeye girer. Cron job, otomatik commit veya harici bir servis gerekmez.

Detay sayfaları için bir ayrıntı var. generateStaticParams build sırasında sadece o an görünür olan yazıları üretir; yarın yayınlanacak yazının yolu listede olmaz. Bunun için dynamicParams açık olmalıdır:

export const revalidate = 3600;
export const dynamicParams = true; // listede olmayan slug'lar ilk istekte üretilsin

dynamicParams varsayılan olarak zaten true'dur, ama açıkça yazmak niyeti belli eder — biri bunu false yaparsa zamanlanmış yazılar 404 vermeye başlar.

On-demand revalidation: anında güncelleme

Zaman tabanlı yenileme çoğu durumda yeterlidir, ama bazen "şimdi güncellensin" demek gerekir. Bir müşteri admin panelinden fiyatı değiştirdiğinde bir saat beklemesini isteyemezsiniz.

Next.js bunun için revalidatePath ve revalidateTag fonksiyonlarını sunar. Bunları bir API route içinde çağırıp dışarıdan tetiklenebilir hale getirirsiniz:

// app/api/revalidate/route.ts
import { revalidatePath } from "next/cache";
import { NextResponse } from "next/server";
 
export async function POST(request: Request) {
  const secret = request.headers.get("authorization")?.replace("Bearer ", "");
 
  if (!secret || secret !== process.env.REVALIDATE_SECRET) {
    return NextResponse.json({ message: "Yetkisiz" }, { status: 401 });
  }
 
  const { path } = await request.json();
 
  if (typeof path !== "string" || !path.startsWith("/")) {
    return NextResponse.json({ message: "Geçersiz yol" }, { status: 400 });
  }
 
  revalidatePath(path);
  return NextResponse.json({ revalidated: true, path });
}

Üç güvenlik noktasına dikkat edin:

  1. Endpoint mutlaka korunmalı. Açık bırakılırsa herkes tetikleyebilir ve sürekli istek göndererek sunucunuzu yeniden üretim döngüsüne sokabilir.
  2. Sır header'da gitmeli, query string'de değil. Query string sunucu loglarına, tarayıcı geçmişine ve yönlendiren (referrer) bilgisine düşer.
  3. Gelen yol doğrulanmalı. İstemciden gelen değeri kontrolsüz kullanmak, beklenmedik yolların yenilenmesine yol açar.

Backend tarafından tetiklemek

Next.js ve Django'yu aynı origin'de çalıştıran bir kurulumda (CORS derdi olmadan tek origin mimarisi) bu tetikleme genelde Django tarafında olur. İçerik kaydedildiğinde bu endpoint'i çağırmak için model sinyali kullanılabilir:

# signals.py
import requests
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
 
@receiver(post_save, sender=Route)
def revalidate_route_page(sender, instance, **kwargs):
    try:
        requests.post(
            f"{settings.FRONTEND_URL}/api/revalidate",
            json={"path": f"/transfer/{instance.slug}"},
            headers={"Authorization": f"Bearer {settings.REVALIDATE_SECRET}"},
            timeout=5,
        )
    except requests.RequestException:
        # Yenileme başarısız olursa içerik kaydı yine de tamamlanmalı;
        # sayfa zaten zaman tabanlı yenilemeyle güncellenecek.
        pass

Buradaki try/except ve timeout önemlidir. Frontend geçici olarak yanıt vermezse, admin panelinde kayıt işleminin de başarısız olmasını istemezsiniz. Yenileme "en iyi çaba" olarak çalışmalı, kritik yolda durmamalıdır.

Ağır trafikli sistemlerde bu isteği doğrudan sinyalde yapmak yerine bir görev kuyruğuna (Celery gibi) atmak daha doğrudur — böylece kaydetme işlemi ağ gecikmesinden hiç etkilenmez.

Etiket tabanlı yenileme

Bir içerik değiştiğinde birden fazla sayfanın etkilendiği durumlar olur. Bir yazının başlığı değişirse hem detay sayfası, hem liste, hem ana sayfadaki "son yazı" kartı eskir.

Her birini tek tek yenilemek yerine etiket kullanabilirsiniz:

// Veri çekerken etiketleyin
const posts = await fetch(`${API}/posts`, {
  next: { tags: ["posts"] },
}).then((r) => r.json());
// Tek çağrıyla hepsini yenileyin
revalidateTag("posts");

Bu, fetch ile veri çeken kurulumlar için geçerlidir. Dosya sisteminden okuyan (MDX gibi) kurulumlarda revalidatePath daha uygundur.

Yaygın hata: revalidate'i unutmak

En sık karşılaşılan sorun, sayfaya revalidate koymayı unutmaktır. Sayfa build'de donar ve içerik ne yaparsanız yapın güncellenmez. Belirti tanıdıktır: "veritabanında değişti ama sitede eski görünüyor."

Kontrol etmenin hızlı yolu, next build çıktısına bakmaktır. Yenileme süresi olan sayfalar bu bilgiyle listelenir:

├ ● /[locale]/blog        1h      1y
│ ├ /tr/blog
│ └ /en/blog

Sağdaki 1h yenileme süresidir. Boşsa o sayfa hiç yenilenmiyor demektir.

Özet

ISR, statik sitelerin hız avantajını kaybetmeden dinamik içerik sunmanın yoludur. Pratikte iki katmanı birlikte kullanmak iyi sonuç verir: zaman tabanlı yenileme güvenlik ağı olarak (her şey en kötü ihtimalle şu kadar sürede güncellenir), on-demand revalidation ise kritik değişiklikler için anında güncelleme sağlar. Webhook başarısız olsa bile sayfa eninde sonunda tazelenir.