Next.js → Cloudflare Workers (OpenNext) Kurulum ve Tuzak Rehberi
RestoPromt15 Temmuz 2026
Next.js'i Cloudflare Workers'a (OpenNext) taşımanın eksiksiz üretim rehberi: kurulum, env katmanları, keep_vars, Workers çalışma zamanı kısıtları, ISR/önbellek, görsel optimizasyonu, veritabanı bağlantıları, limitler, CI/CD, güvenlik ve doğrulama checklist'i. Bölüm A gerçek üretim sorunlarından (Restomarkt), Bölüm B sektörde bilinen tüm tuzaklardan derlenmiştir.
# Next.js → Cloudflare Workers (OpenNext) — Eksiksiz Üretim Rehberi
Bir Next.js (App Router) projesini Cloudflare Workers üzerinde OpenNext ile yayına alırken bu rehberi uygula. **Bölüm A** temel kurulum ve birebir yaşanmış sorunlar; **Bölüm B** başka projelerde karşılaşılabilecek, sektörde bilinen tüm tuzak alanları.
---
# BÖLÜM A — Kurulum ve Temel Tuzaklar
## A1. Paketler ve script'ler
- `npm i -D @opennextjs/cloudflare wrangler`
- package.json:
- `"cf:build": "CLOUDFLARE_BUILD=1 opennextjs-cloudflare build"`
- `"cf:preview": "npm run cf:build && opennextjs-cloudflare preview"`
- `"cf:deploy": "npm run cf:build && opennextjs-cloudflare deploy"`
- Sayfalardan `export const runtime = "edge"` satırlarını KALDIR — OpenNext, workerd üzerinde Node uyumluluk katmanıyla çalışır; edge runtime işareti çakışma yaratır.
## A2. wrangler.jsonc (kritik alanlar)
```jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "<worker-adi>", // TUZAK: dashboard'daki worker adıyla BİREBİR aynı olmalı;
// uyuşmazsa build sistemi uyarır ve otomatik düzeltme PR'ı açar
"main": ".open-next/worker.js",
"compatibility_date": "<güncel-tarih>",
"compatibility_flags": ["nodejs_compat"],
"assets": { "directory": ".open-next/assets", "binding": "ASSETS" },
// TUZAK (en kritik): keep_vars yoksa HER deploy, dashboard'dan girilen düz
// değişkenleri SİLER (Secret'lar korunur). "Panelde tanımladım ama her push'ta
// site düşüyor" sorununun kök nedeni budur.
"keep_vars": true,
// Yalnız HERKESE AÇIK değerler (NEXT_PUBLIC_*). Sır buraya ASLA yazılmaz.
"vars": { "NEXT_PUBLIC_...": "..." },
"observability": { "enabled": true }
}
```
## A3. Ortam değişkenleri — ÜÇ ayrı katman (en çok karıştırılan konu)
| Katman | Nereye girilir | Ne için |
|---|---|---|
| **Build-time** | Workers Builds → Settings → Build → Variables (VEYA repoya `.env.production`) | `NEXT_PUBLIC_*` build sırasında istemci koduna GÖMÜLÜR. Runtime'a eklemek YETMEZ. |
| **Runtime vars** | wrangler.jsonc `vars` veya dashboard Variables | Sunucunun `process.env` ile okuduğu açık değerler |
| **Runtime Secrets** | Worker → Settings → Variables and Secrets → tip: **Secret** (veya `wrangler secret put`) | API anahtarları, service role vb. Deploy'da SİLİNMEZ — tek kez girilir. |
- TUZAK: `NEXT_PUBLIC_*` yalnız runtime'daysa SSR çalışır ama tarayıcı paketi `undefined` alır; yalnız build'deyse tersi. **İkisine de** gir — en garantisi: gerçekten kamuya açık değerleri `.env.production` olarak repoya koymak + `keep_vars: true`.
- Sırlar (service role, AI anahtarı, OAuth secret) SADECE Secret; repoya/vars'a asla — git geçmişi sonsuza dek hatırlar.
## A4. Workers çalışma zamanı kısıtları (kod yazarken)
- **Dosya sistemi YOK.** `fs.existsSync` / `path` + `process.cwd()` Workers'ta hep "yok" döner — Vercel/lokalde çalışıp Workers'ta sessizce bozulur. Dosya varlığına dayalı mantık kurma; upload için R2 kullan.
- Statik prerender edilen sayfalar BUILD makinesinde (Node) render edilir → "statik sayfada çalışıyor, dinamikte bozuk" farkının klasik sebebi.
- Binding'lere `getCloudflareContext()` (@opennextjs/cloudflare) ile eriş; Workers dışı ortamda fırlatır → try/catch + fallback (çoklu ortam projelerinde adapter deseni).
## A5. Teşhis imzaları (site bozulduğunda)
- **Tüm dinamik sayfalar 500 + statik dosyalar 200** → build'e NEXT_PUBLIC env girmemiş (middleware her istekte çöküyor).
- **Belirli özellikler bozuk (AI, harici API, mail), vitrin sağlam** → runtime Secret eksik.
- **"Panelde tanımladım, push sonrası yine düştü"** → `keep_vars` eksik.
- **Vercel'de görünen şey Workers'ta yok** → fs/Node-özel API kullanımı.
## A6. Workers Builds (git ile otomatik deploy)
- Workers & Pages → Create → **Workers** sekmesi → Import a repository (Pages DEĞİL).
- Her push otomatik build alır → A2/A3 kalıcı yapılmadan otomatik build'ler siteyi bozabilir.
- Build ortamı değişkenleri, runtime değişkenlerinden AYRI tanımlanır (Settings → Build).
## A7. Domain ve DNS
- Worker → Settings → Domains & Routes → custom domain.
- SSL/TLS → **Always Use HTTPS** aç (yoksa http:// düz 200 döner).
- **www** için DNS kaydı + apex redirect (unutulursa www hiç açılmaz).
- **AI Crawl Control** robots.txt'ye otomatik AI bot engeli enjekte edebilir (GPTBot, ClaudeBot, Google-Extended) — AI aramalarında görünürlük stratejinle çelişmesin; gerekirse kapat.
## A8. E-posta (Cloudflare Email Service)
- `"send_email": [{ "name": "EMAIL" }]` + `npx wrangler email sending enable <domain>` (SPF/DKIM otomatik).
- `env.EMAIL.send({ to, from: { email, name }, subject, html, text })` — `text` sürümünü her zaman ekle.
- Adapter deseni: CF binding → (varsa) Resend/SES → sessiz no-op; mail hatası ana akışı asla bozmasın.
## A9. iframe/embed
- next.config headers ile `Content-Security-Policy: frame-ancestors <izinli-originler>`; embed sayfasının postMessage dinleyicisinde origin doğrula.
---
# BÖLÜM B — Diğer Projelerde Karşılaşılabilecek Sorunlar (sektör standardı kontrol alanları)
## B1. ISR, önbellek ve revalidate
- OpenNext'te ISR/`revalidate` kalıcılığı için **incremental cache** yapılandırması gerekir (open-next.config.ts → R2 veya KV cache). Yapılandırılmazsa revalidasyonlar instance ömrüyle sınırlı kalır, sayfalar bayatlamaz ya da hiç tazelenmez.
- `revalidateTag` / on-demand revalidation için OpenNext'in queue/tag cache mekanizması ayrıca kurulmalı — Vercel'deki "her şey hazır" davranışını varsayma.
- HTML CDN'de varsayılan olarak CACHE'LENMEZ; `Cache-Control` başlıklarını bilinçli yönet. `/_next/static` assets binding'iyle otomatik immutable gelir.
## B2. Görsel optimizasyonu (next/image)
- Workers'ta Vercel'in yerleşik image optimizer'ı YOK. Seçenekler: Cloudflare Images/Image Resizing'e custom loader, OpenNext'in image çözümü veya `unoptimized: true`.
- `remotePatterns` tanımlı olsa bile optimizer yoksa dış görseller ham iner — LCP ve maliyeti ölç.
- **sharp, canvas gibi native modüller Workers'ta ÇALIŞMAZ** — görsel işleme gerekiyorsa Cloudflare Images API veya harici servis.
## B3. Veritabanı bağlantıları
- Workers'ta kalıcı TCP havuzu yok. Ham `pg`/`mysql2` doğrudan KULLANILMAZ:
- HTTP tabanlı sürücüler: Supabase (PostgREST), Neon serverless driver, PlanetScale HTTP
- TCP şartsa: **Hyperdrive** (bağlantı havuzlama + bölgesel hızlandırma)
- Her istek yeni izole ortamda çalışabilir → global bağlantı önbelleği garantisi yok; "connection storm" senaryosunu Hyperdrive/pgbouncer ile çöz.
- Prisma kullanılacaksa driver adapter (Prisma + Hyperdrive/Neon) gerekir; klasik Prisma engine çalışmaz.
## B4. Boyut, CPU ve istek limitleri
- **Worker boyutu**: gzip'li bundle limiti (Free ~3MB / Paid ~10MB). Ağır sunucu bağımlılıkları (puppeteer, pdfkit, büyük SDK'lar) limiti patlatır → `serverExternalPackages`, dinamik import, ya da işi Queues/Container'a taşı.
- **CPU süresi**: istek başına CPU-ms sınırlıdır (duvar saati değil). PDF üretimi, büyük parse, kripto madenciliği gibi CPU-yoğun işler timeout'lamaz ama CPU limitine takılır → Cloudflare Queues, Workflows veya harici işçi.
- **Subrequest limiti**: istek başına fetch sayısı sınırlı (plana göre 50/1000). N+1 harici çağrı desenlerinden kaçın.
- Kendi zone'una fetch atarken sonsuz döngü riskine dikkat (worker kendini çağırır) — `global_fetch_strictly_public` bayrağı veya doğrudan origin URL kullan.
## B5. Node uyumluluk boşlukları
- `nodejs_compat` çoğu şeyi getirir ama TAM Node değildir: `child_process`, `worker_threads`, ham `net/tls` (kısmi), `fs` yok.
- Native binding'li paketler (bcrypt, sharp, better-sqlite3) çalışmaz → saf-JS/webcrypto alternatifleri (bcryptjs, jose, @noble/*).
- Paket seçerken "edge/workerd uyumlu mu" kontrolü yap; büyük SDK'ların "node" girişi yerine "worker/edge" girişini kullan.
## B6. Zamanlanmış işler ve kuyruklar
- Vercel Cron yerine **Cron Triggers** (wrangler.jsonc `triggers.crons`) + worker'da `scheduled` handler. OpenNext worker'ına scheduled handler eklenebilir; alternatif: ayrı küçük bir cron worker'ı ana uygulamanın HTTP ucunu çağırır.
- Uzun/tekrarlı arka plan işleri: Cloudflare **Queues** (retry/DLQ ile) veya **Workflows** (durable, adım adım). "fire-and-forget fetch" güvenilir değildir — `ctx.waitUntil` yalnız kısa işler için.
## B7. Oturum, çerez ve istek bilgisi
- Gerçek IP: `CF-Connecting-IP` başlığı; coğrafya: `request.cf` (ülke/şehir) — `x-forwarded-for` zincirine güvenme.
- Çerezlerde `Secure` + doğru `SameSite`; apex↔www geçişinde domain scope'unu bilinçli seç (www kurulumu eksikse auth çerezleri "kayboluyor" sanılır).
- Büyük başlık/çerez toplamı limitlerine dikkat (ör. JWT şişmesi).
## B8. Gözlemlenebilirlik ve hata takibi
- `observability.enabled: true` + dashboard Workers Logs; canlı akış için `wrangler tail`.
- Sentry vb. için Workers uyumlu SDK/`instrumentation` kullan; Node-özel Sentry paketi çalışmayabilir.
- `console.log` yüksek trafikte örneklenebilir — kritik denetim kayıtlarını log yerine veritabanına/kuyruğa yaz.
## B9. Sürümleme, kademeli dağıtım ve geri alma
- Workers **Versions & Deployments**: her deploy bir sürüm; **gradual deployment** ile %X trafiğe kanarya aç; sorun anında tek tıkla eski sürüme dön (rollback) — büyük değişikliklerde bunu kullan.
- Ortamlar: wrangler `env` blokları (staging/prod) veya ayrı worker'lar; staging'e ayrı domain bağla.
- CI'da (GitHub Actions) OAuth yerine **API Token** (Account → Workers Scripts:Edit kapsamlı) kullan; `wrangler-action` ile deploy et.
## B10. Güvenlik katmanları (uygulama dışı)
- **WAF + Rate Limiting Rules**: login, API ve webhook uçlarına Cloudflare katmanında hız limiti koy (uygulama içi limitin önüne geçer, ucuzdur).
- **HSTS** (Always Use HTTPS sonrası), Security headers (CSP/HSTS) next.config headers veya Transform Rules ile.
- Bot Fight/Turnstile: form spam'i için Turnstile'ı düşün; ama API/webhook uçlarını bot korumasının YANLIŞLIKLA engellemediğini doğrula (custom rule ile bypass).
- Cloudflare Access ile admin panelini ekstra kimlik katmanına alabilirsin.
## B11. Yükleme/dosya işleri
- Kullanıcı dosyaları için **R2** (S3 uyumlu) + presigned URL veya Worker proxy'si; `fs` olmadığından geçici dosya yazma yok — stream'le çalış.
- Kayıt silinince R2 objesini de sil (yetim obje bırakma); toplu silmede DeleteObjects.
## B12. Yerel geliştirme ve test paritesi
- `next dev` ≠ Workers. Canlı davranışı **`cf:preview`** (wrangler dev + workerd) ile test et; özellikle env, binding, fs ve Node-compat farkları yalnız burada yakalanır.
- Binding'ler lokalde `.dev.vars` + wrangler dev ile simüle edilir; `"remote": true` bazı binding'leri (ör. email) gerçek servise proxy'ler.
- CI'ya "cf:build başarılı mı" adımı ekle — Vercel build'i geçen kod Workers build'inde kırılabilir.
## B13. Maliyet farkındalığı
- Ücretlendirme istek + CPU-ms üzerinden; ağır SSR sayfaları CPU-ms yakar. ISR/önbellekle SSR'ı azalt.
- Image Resizing, R2 işlemleri, Queues mesajları ayrı kalemler — mimariyi seçerken fiyat sayfasına bak.
## B14. Yayın sonrası doğrulama checklist'i
```
curl -s -o /dev/null -w "%{http_code}" https://<domain>/ # 200
curl -s -o /dev/null -w "%{http_code}" https://<domain>/<dinamik> # 200 (500 ise env)
curl -s -o /dev/null -w "%{http_code}" http://<domain>/ # 301 → https
curl -s -o /dev/null -w "%{http_code}" https://www.<domain>/ # 200/301 (000 ise DNS yok)
curl -s https://<domain>/robots.txt # AI bot bloklarını gözden geçir
curl -s https://<domain>/sitemap.xml | grep -c "<loc>" # beklenen URL sayısı
```
- Runtime secret gerektiren her özelliği (AI, harici API, mail, webhook) canlıda ayrı ayrı dene.
- Push → otomatik build → site ayakta mı? (keep_vars + env kalıcılığı testi)
- ISR'lı bir sayfada içerik değiştir → revalidate süresi sonunda tazelendi mi?
- Bir sürüm geri alma (rollback) provası yap — kriz gününde ilk kez denenmesin.
Değişiklik öner
Bu promptu yalnızca sahibi düzenleyebilir; ama sen bir iyileştirme önerebilirsin. Sahibi kabul ederse içerik güncellenir.