“Küçük ayakkabıcıların çocukları ayakkabı giyemez.” Müşteriler için kusursuz API’ler gönderirken, kendimize ait portföy sitelerimizin eski ISR sayfalarından ve çapraz alan kimlik doğrulama hatalarından çürüyüşüne tanık oluyoruz. yabasha.dev, her dağıtımda eski içerik sunmaya başladığında, onu bir yan proje olarak görmeyi bıraktım ve gerçek bir protokol inşa ettim.
Özet
Özet
- Laravel Sanctum + httpOnly cookies çapraz alanlardaki CSRF gizemlerini ortadan kaldırır
- Önbellek El Sıkışma Protokolü: Laravel olayları → Horizon kuyrukları → Redis durum izleme → Next.js yeniden doğrulama
- Dağıtım koreografisi: Sağlık kontrolü ile aşamalı dağıtımlar, yarış koşullarını önler
- Sonuç: 3 ayda 40’tan fazla dağıtımda sıfır eski sayfa vakası
Problem
Problem
Her dağıtım aynı ritüeli takip ediyordu:
- Next.js’i üretime gönder
- ISR sayfalarının yarısının eski olduğunu fark et, çünkü yeniden doğrulama çağrıları 30 saniyelik dağıtım penceresinde kaybolmuştu
- Redis’i manuel olarak temizle ve dua et
Kimlik doğrulama daha da kötüydü. Safari kullanıcıları, çapraz alan çerez sorunları nedeniyle oturumdan çıkıyordu. CSRF tokenleri oturum ortasında süresi doluyordu. Ben, özellikler geliştirmek yerine önbellek durumunu kontrol etmekle meşguldüm.
Teknoloji Yığını
Teknoloji Yığını
- Backend: Laravel 12 (API-first)
- Frontend: Next.js 16 (App Router + ISR)
- Cache/Kuyruk: Redis 7 + Laravel Horizon
- Gözlemlenebilirlik: Sentry + yapılandırılmış JSON günlükleri
Gerçekten Çalışan Kimlik Doğrulama
Gerçekten Çalışan Kimlik Doğrulama
localStorage’da token yok. Manuel Authorization başlıkları yok.
Laravel yapılandırması (config/sanctum.php):
'stateful' => ['localhost:3000', 'yabasha.dev', '*.yabasha.dev'],
'expiration' => 720, // 12 saat
Next.js API istemcisi:
export async function apiClient(endpoint: string, options: RequestInit = {}) {
const res = await fetch(`${API_URL}${endpoint}`, {
...options,
credentials: 'include',
mode: 'cors',
});
if (res.status === 419) {
// CSRF süresi doldu: tokenı yeniden al ve BİR KEZ yeniden dene
await getCsrfCookie();
return apiClient(endpoint, options);
}
return res;
}
Ticaret İlişkisi: Disiplinli CORS yapılandırması gerektirir. Fayıdası: XSS, httpOnly çerezleri çalamaz; token yenileme dansı yok.
Önbellek El Sıkışma Protokolü
Önbellek El Sıkışma Protokolü
Webhook çağrılarını ateşle ve unut tarzında kullanmak yerine, bir dağıtılmış durum makinesi oluşturdum:
Laravel Olay → Horizon Kuyruğu → Redis İzleme → Next.js Yeniden Doğrulama
1. Alan Olayı Yay
1. Alan Olayı Yay
// app/Events/ContentInvalidated.php
class ContentInvalidated
{
public function __construct(
public string $type,
public string $id,
public array $tags,
public string $revalidation_id,
) {}
}
2. İdempotans ile Kuyruk
2. İdempotans ile Kuyruk
// app/Jobs/RevalidateNextJsCache.php
class RevalidateNextJsCache implements ShouldBeUnique
{
public $tries = 5;
public $backoff = [10, 30, 60, 120, 300];
<span class="k">public</span> <span class="k">function</span> <span class="n">uniqueId</span><span class="p">():</span> <span class="kt">string</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="n">revalidation_id</span><span class="p">;</span> <span class="c1">// Tekrar eden işleri önler</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">handle</span><span class="p">():</span> <span class="kt">void</span>
<span class="p">{</span>
<span class="nc">Redis</span><span class="o">::</span><span class="nf">hset</span><span class="p">(</span><span class="s1">'revalidation_state'</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="n">revalidation_id</span><span class="p">,</span> <span class="nb">json_encode</span><span class="p">([</span>
<span class="s1>'status'</span> <span class="o">=></span> <span class="s1>'inflight'</span><span class="p">,</span>
<span class="s1>'attempt'</span> <span class="o">=></span> <span class="nv">$this</span><span class="o">-></span><span class="nf">attempts</span><span class="p">(),</span>
<span class="p">]));</span>
<span class="nv">$response</span> <span class="o">=</span> <span class="nc">Http</span><span class="o">::</span><span class="nf">post</span><span class="p">(</span><span class="nf">config</span><span class="p">(</span><span class="s1>'services.nextjs.url'</span><span class="p">)</span> <span class="mf">.</span> <span class="s1>'/api/revalidate'</span><span class="p">,</span> <span class="p">[</span>
<span class="s1>'tags'</span> <span class="o">=></span> <span class="nv">$this</span><span class="o">-></span><span class="n'>tags</span><span class="p">,</span>
<span class="p">]);</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$response</span><span class="o">-></span><span class="nf">failed</span><span class="p">())</span> <span class="p">{</span>
<span class="c1">// Durum güncellendi tekrar denemeden önce</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nc">Exception</span><span class="p">(</span><span class="s2>"Yeniden doğrulama başarısız"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nc">Redis</span><span class="o">::</span><span class="nf">hdel</span><span class="p">(</span><span class="s1>'revalidation_state'</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="n'>revalidation_id</span><span class="p">);</span>
<span class="p">}</span>
}
3. Next.js Yeniden Doğrulama Uç Noktası
3. Next.js Yeniden Doğrulama Uç Noktası
// app/api/revalidate/route.ts
export async function POST(request: NextRequest) {
const revalidation_id = request.headers.get('x-revalidation-id');
const { tags } = await request.json();
// İdempotans: Daha önce işlendi ise atla
const seen = await redis.get(<span class="s2>revalidations:processed:${</span><span class="nx">revalidation_id</span><span class="p">});
if (seen) return NextResponse.json({ status: 'already_processed'<span class="dl>' });
for (const tag of tags) {
revalidateTag(tag);
}
await redis.set(<span class="s2>revalidations:processed:${</span><span class="nx">revalidation_id</span><span class="p">}, '1'<span class="dl>', { ex: 86400 });
return NextResponse.json({ status: 'success'<span class="dl>' });
}
Bunun neden sınır durumlarına dayanmadığı:
- Next.js API kapalı mı? Horizon, üstel geri dönüş ile yeniden denemeler yapar
- Yanıt kayboldu mu? İdempotans, iki kat iş yapmayı önler
- İş başarısız mı? Redis kesin hatayı gösterir
Dağıtım Koreografisi
Dağıtım Koreografisi
Her iki uygulamayı aynı anda dağıtmak, zincirleme 500 hatalarına neden oluyordu. Çözüm:
- Önce API’yi dağıt → Sağlık kontrolü için bekle
- Ön yüzü dağıt → Geçersiz kılmaları duraklatmak için
MAX_REVALIDATION_RETRY=0ayarla - Geçersiz kılmaları yeniden başlat → Kuyruğa alınmış geri kalanı işle
# Ön yüz dağıtımından sonra CI tetikleyici
curl -X POST
Bu, dağıtım penceresi yarış koşulunu ortadan kaldırır.
Durum Makinesi
Durum Makinesi
Çoğu kılavuz, yeniden doğrulamayı ateşle ve unut tarzında ele alır. Ben bunu bir durum makinesi olarak modelledim:
pending → inflight → completed
↓ (fail & retry)
failed → dead-letter
Karar matrisim:
| Senkran Web Kancası Kullan | Kuyruk + Durum Kullan |
|---|---|
| > 10 etiket veya joker karakter | |
| Geliştirme/test | Üretim |
| Sesiz hata toleransı | Her değişikliği denetleme |
Sonuçlar
Sonuçlar
Önce: Haftada 2-3 eski içerik vakası; manuel Redis temizleme
Sonra: Sıfır vaka 3 ay içinde 40’tan fazla dağıtım
Ölçülebilir sonuçlar:
- Kuğu hata oranı: ~0.3% (oto-yenilemeler %95’ini çözüyor)
- Değişikliklerin ömrü: p95
- Dağıtım olay oranı: %30’dan 0’a düştü
Uygulama Kontrol Listesi
Uygulama Kontrol Listesi
- [ ] Sanctum’u
statefulalanları ile yapılandır - [ ]
ContentInvalidatedolayı veRevalidateNextJsCacheişini oluştur - [ ]
ShouldBeUnique‘irevalidation_idile uygula - [ ] MaxJobs geri basıncı ile Horizon’u kur
- [ ] İdempotent Next.js yeniden doğrulama API’si oluştur
- [ ] İş
handle()vefailed()alanında Redis durum izleme ekle - [ ] Dağıtım kapısını oluştur: geçersiz kılmaları duraklat / yeniden başlat
- [ ] Her iki uygulamada Sentry’yi ortak izleme kimlikleri ile yükle
Daha Geniş Ders
Daha Geniş Ders
Operasyonel mükemmeliyet bir alışkanlıktır, bütçe değil. İdempotans anahtarları veya geri basıncı uygulamak için bir platform ekibine ihtiyacınız yok. Kendi platformunuzun bu çabayı hak ettiğine karar vermeniz yeterli.
Detaylarıyla tam planı okumaya devam edin:
- Complete monorepo kurulumu ile Bun iş alanları
- 5 hata modu & üretim önlemleri
- Redis geri basıncı yapılandırması
- Kesin dağıtım betikleri
- Durum makinesi karar ağaçları
Kaynak: Orijinal Makale


