Next.js 16 Self-Hosting Rehberi: Docker, Standalone Çıktı ve Üretim Dağıtımı
Next.js 16 self-hosting için tam rehber: standalone output, çok aşamalı Docker imajı, Redis ISR cache handler, Nginx ters proxy, graceful shutdown ve Kubernetes deployment.
Next.js 16 self-hosting, uygulamanızı Vercel dışında herhangi bir Node.js sunucusunda (Docker container, VPS, Kubernetes veya bare-metal) çalıştırmak anlamına gelir ve next.config.ts dosyasında output: 'standalone' ayarını etkinleştirerek minimal bir üretim bundle'ı üretmekle başlar. Geçen ay bir müşteri projesini Vercel'den kendi Hetzner sunucusuna taşırken aşağıda anlatacağım hemen hemen her tuzağa bizzat takıldım; bu rehberde çok aşamalı Docker imajı oluşturmaktan ISR önbelleğini Redis ile kalıcı hale getirmeye, Nginx ters proxy yapılandırmasına ve düzgün (graceful) kapatma akışına kadar üretim için ihtiyacınız olan her şeyi adım adım anlatıyorum.
output: 'standalone' modu, node_modules'u izleyip sadece gerekli dosyaları kopyalar; final Docker imajı 150-250 MB seviyesine düşer.
sharp paketini üretim bağımlılığı olarak kurmazsanız Next.js 16 Image Optimization yavaş, CPU-yoğun bir JS fallback'a düşer.
ISR ve revalidateTag'ı çoklu instance üzerinde çalıştırmak için özel bir cacheHandler (Redis tabanlı) gerekir, aksi halde her pod kendi cache'ini tutar.
Next.js 16 varsayılan olarak çoklu çekirdek kullanmaz; PM2 cluster modu veya Kubernetes replica'ları ile yatay ölçekleyin.
Nginx'de X-Forwarded-* başlıklarını forward etmeden request.headers.get('host') ve middleware geolocation hatalı sonuç verir.
Graceful shutdown için Node.js sinyal yakalama (SIGTERM) zorunludur, aksi takdirde rolling deploy sırasında istekler düşer.
Next.js 16 standalone output nedir ve neden gerekli?
Standalone output, Next.js'in build sırasında uygulamanızın ve node_modules ağacının bağımlılık grafiğini izleyerek (file tracing) yalnızca runtime için gereken dosyaları .next/standalone/ klasörüne kopyaladığı bir derleme modu. Tipik bir Next.js projesinin node_modules dizini 400-800 MB iken, standalone çıktısı 50-80 MB'a iner. Üretim Docker imajını küçültmenin ve cold-start süresini düşürmenin en etkili yolu da bu.
Resmi Next.js dağıtım dokümantasyonu bu modu özellikle Docker ve container tabanlı ortamlar için öneriyor. Aktif etmek için next.config.ts dosyasında tek satır yeterli:
Build tamamlandığında .next/standalone/server.js çalıştırılabilir bir Node.js sunucusudur, yani next start'a ihtiyacınız yok. Tek püf nokta şu: .next/static ve public/ klasörleri ayrıca kopyalanmalı; bunlar tracing'e dahil edilmez çünkü browser tarafından doğrudan sunuluyor. İlk denememde bu adımı atlamıştım ve üretimde 404 hataları ile eksik CSS gördüm. Self-hosting'de en sık karşılaşılan problem bu.
Çok aşamalı Docker imajı nasıl oluşturulur?
Üretim için optimize edilmiş bir Docker imajı üç aşamadan oluşur: deps (bağımlılıkları yükler ve cache'ler), builder (kaynak kodu derler) ve runner (yalnızca standalone çıktısını içerir). Bu kalıp, kodda küçük bir değişiklik yaptığınızda npm ci'nin tekrar çalışmamasını sağlar ve final imajı 200 MB civarında tutar.
# Dockerfile
FROM node:22-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --include=dev
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
Bir şeyi kesinlikle atlamamalısınız: HOSTNAME=0.0.0.0. Varsayılan localhost binding container dışından erişime izin vermez ve docker run sonrası "connection refused" hatası alırsınız (bunu da bizzat ilk dakikada yaşadım). NEXT_TELEMETRY_DISABLED=1 opsiyonel ama CI ortamlarında network trafiğini azaltır.
Bir de .dockerignore dosyası var. İmaj boyutu kadar önemli; node_modules, .next, .git, .env*.local, coverage ve README.md gibi dosyaları dışlamalısınız. Bu olmadan build context yüzlerce MB'a şişer ve docker build dakikalarca sürebilir.
Image Optimization ve sharp kurulumu
Next.js 16, next/image bileşeni ile WebP/AVIF dönüşümü ve sunucu tarafında yeniden boyutlandırma yapar. Vercel üzerinde bu işlem yönetilen bir image servisi tarafından yürütülür; self-hosted bir sunucuda ise Next.js doğrudan Node.js process içinde sharp kütüphanesini kullanır. sharp kurulu değilse Next.js 16 ihtiyaç anında uyarı verir ve hızlı bir JS fallback'ına döner; bu fallback'ın CPU maliyeti üretim trafiğinde son derece yüksek.
npm install sharp
Alpine imajında sharp'ın native binary'leri için libc6-compat paketini eklemeniz gerekir (yukarıdaki Dockerfile'da mevcuttur). Ayrıca uzak alanlardaki görselleri optimize edeceksen images.remotePatterns'ı tanımlamalısın:
Tek instance çalışan bir Next.js sunucusunda ISR (Incremental Static Regeneration) sorunsuz çalışır; yeniden oluşturulan sayfalar .next/cache dizinine yazılır. Birden fazla instance'ı yük dengeleyici arkasında çalıştırdığınızda ise her instance kendi cache'ini tutar, revalidateTag veya revalidatePath çağrısı yalnızca o isteği işleyen instance'ı günceller ve diğerleri eski içeriği sunmaya devam eder. Çözüm: paylaşımlı bir cache handler tanımlamak.
Bu yapı ile revalidateTag('products') çağrısı tüm pod'lardaki ilgili sayfaları aynı anda geçersiz kılar. Edge Runtime tarafında middleware ile kimlik doğrulama veya yönlendirme yapıyorsanız Next.js middleware ve Edge Runtime rehberindeki kalıpları da gözden geçirin; cache invalidation ve middleware yönlendirmesi sık sık birlikte çalışır.
Nginx ters proxy ve başlık yapılandırması
Self-hosted Next.js'i doğrudan internete açmak yerine TLS sonlandırma, statik dosya cache'leme ve rate limiting için Nginx (veya Caddy/Traefik) arkasına koymak standarttır. Doğru başlık yapılandırması olmadan request.headers.get('host'), x-forwarded-for tabanlı IP geolocation ve middleware yönlendirmeleri yanlış sonuç verir. Nginx HTTP proxy modülü dokümantasyonu tüm yönergelerin detayını içerir.
Container orchestrator'lar (Kubernetes, Docker Swarm) deploy sırasında pod'a önce SIGTERM gönderir, ardından bir terminationGracePeriod (varsayılan 30 saniye) sonra SIGKILL ile öldürür. Next.js'in standalone server.js'i bu sinyali handle ederse, devam eden HTTP istekleri bitirildikten sonra port serbest bırakılır ve hiçbir istek düşmez.
Next.js 16, standalone modda SIGTERM sinyalini varsayılan olarak yakalar ve aktif bağlantıları boşaltmaya çalışır. Ancak kendi server.js sarmalayıcınızı kullanıyorsanız bunu manuel yapmanız gerekir:
Next.js'te ortam değişkenlerinin iki kategorisi vardır: build-time embed edilen NEXT_PUBLIC_* değişkenleri ve runtime'da okunan sunucu değişkenleri. NEXT_PUBLIC_API_URL gibi bir değer npm run build çalıştığında client bundle'ına yazılır; yani Docker imajını birden fazla ortama (staging/prod) dağıtmak istiyorsanız bu yaklaşım bozulur.
Üretim için pratik: build sırasında yalnızca public olmayan değerleri verin (örn. DATABASE_URL), NEXT_PUBLIC_* değerlerini ise her ortam için ayrı build üretin VEYA runtime-config kullanın. Sırları doğrudan Dockerfile'a yazmayın; BuildKit secret mount kullanın:
# docker build sırasında secret enjekte et
docker build \
--secret id=db_url,src=./db_url.txt \
-t myapp:latest .
# Dockerfile (builder stage içinde)
RUN --mount=type=secret,id=db_url \
DATABASE_URL="$(cat /run/secrets/db_url)" \
npm run build
Kubernetes ortamında ise Secret kaynağı ile envFrom kullanın; ConfigMap public, Secret hassas değerler için. Bir not: Edge Runtime üzerinde middleware çalıştırıyorsanız, process.env erişimi build-time inlining ile sınırlı kalabilir.
Yatay ölçekleme: PM2 cluster ve Kubernetes
Node.js tek thread çalıştığı için çok çekirdekli sunuculardan yararlanmak istiyorsanız ya birden fazla container instance'ı çalıştırın ya da PM2'nin cluster modunu kullanın. PM2, fork-temelli child process spawn eder ve aralarında round-robin yük dengeler:
// ecosystem.config.js
module.exports = {
apps: [
{
name: 'nextjs',
script: '.next/standalone/server.js',
instances: 'max', // CPU çekirdek sayısı kadar
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000,
HOSTNAME: '0.0.0.0',
},
// SIGTERM gönderildikten sonra graceful shutdown için ekstra süre
kill_timeout: 25_000,
},
],
}
Kubernetes'te ise her pod tek instance çalışır ve HorizontalPodAutoscaler ile CPU/memory metriklerine göre ölçeklenir. Tipik bir deployment manifest:
Evet, Next.js tamamen open source bir Node.js framework'üdür ve output: 'standalone' modu özellikle bu kullanım senaryosu için tasarlanmıştır. Docker, VPS (DigitalOcean, Hetzner, Linode), AWS ECS/Fargate, Google Cloud Run veya kendi bare-metal sunucunuzda sorunsuz çalışır. Yalnızca Image Optimization ve ISR için bazı ek yapılandırmalar gerekir.
Next.js Docker imajı tipik olarak ne kadar büyük olur?
Standalone output ve çok aşamalı build kullanarak Alpine tabanlı bir imajı 150-250 MB seviyesinde tutmak mümkündür. output: 'standalone' olmadan tüm node_modules kopyalandığı için imaj kolayca 1 GB'ı aşar. sharp ekleyince yaklaşık 30 MB daha eklenir.
Self-hosting için minimum sunucu özellikleri nelerdir?
Düşük trafikli bir Next.js 16 uygulaması için 1 vCPU ve 512 MB RAM yeterlidir; build aşaması için en az 2 GB RAM önerilir, aksi takdirde Turbopack OOM hatası verir. Üretimde 2 vCPU + 2 GB RAM, yüzlerce eşzamanlı istek için rahat bir başlangıçtır.
ISR self-hosted ortamda nasıl çalışır?
Tek instance'ta ISR doğrudan çalışır; yeniden oluşturulan sayfalar .next/cache'e yazılır. Çoklu instance senaryolarında cacheHandler seçeneği ile Redis veya başka bir paylaşımlı store kullanmanız gerekir, aksi halde her instance kendi izole cache'ini tutar ve revalidateTag yalnızca isteği alan instance'ı günceller.
Next.js standalone modunda next start kullanılır mı?
Hayır. Standalone build, .next/standalone/server.js adında çalıştırılabilir bir Node.js dosyası üretir ve doğrudan node server.js ile başlatılır. next start tüm node_modules'a ihtiyaç duyar ve standalone'un boyut avantajını ortadan kaldırır.
Next.js 16'da Turbopack varsayılan derleyici. Üretim build yapılandırması, filesystem cache, Webpack'ten geçiş ve performans ölçümleri için pratik rehber.
Next.js 16 Metadata API'sini App Router'da nasıl kullanacağınızı, dinamik Open Graph görsellerini, sitemap üretimini ve JSON-LD yapılandırılmış veriyi gerçek üretim koduyla anlatan adım adım rehber.
Next.js 16 App Router'da next-intl ile sıfırdan çoklu dil (i18n) kurulumu: middleware, hreflang, ICU MessageFormat, PPR uyumu ve üretim kalıpları gerçek örneklerle.