Genellikle şu ekranla başlıyor: Vercel faturasında "Function Invocations" ve "Image Optimization" kalemleri aylık planın üstüne çıkmış, ay sonunda ödeyeceğiniz rakam elinizdeki bir VDS'in yıllık bedelini geçmiş. Ya da sebep tam tersi: uygulamanız şirket içi bir veritabanına bağlanmak zorunda ve o makine yalnızca belirli bir IP'ye açık. Her iki durumda da varacağınız yer aynı — Next.js'i kendi sunucunuzda çalıştırmak.
Buradaki asıl zorluk next build komutu değil. Zorluk, terminalden çıktığınızda uygulamanın ölmemesi, sunucu yeniden başladığında kendiliğinden ayağa kalkması, 3000 portunun internete açık kalmaması, statik dosyaların Node süreci yerine Nginx tarafından servis edilmesi ve adres çubuğunda kilit simgesinin görünmesi. Yani "çalıştırmak" ile "yayına almak" arasındaki fark.
Bu rehber, boş bir Ubuntu 24.04 sunucusundan başlayıp uygulamanızın https://alanadiniz.com üzerinden yayında olduğu noktaya kadar olan zinciri kuruyor: Node kurulumu, output: 'standalone' derlemesi, bellek yetmediğinde build'i yerelde alıp taşıma, PM2 veya systemd ile süreci ayakta tutma, Nginx reverse proxy, statik dosyaların doğrudan servisi ve Let's Encrypt sertifikası. Sonunda güncelleme akışını ve en sık düşülen tuzakları ele alacağız.
Vercel'den Kendi Sunucunuza Geçerken Neyi Devralıyorsunuz#
Vercel, Next.js'in bazı özelliklerini altyapı seviyesinde çözer. Kendi sunucunuza taşındığınızda bu işlerin sahibi siz olursunuz. Taşınmadan önce hangi özellikleri kullandığınızı bilmek, deploy sonrası "neden çalışmıyor" turuna çıkmanızı engeller.
| Özellik | Vercel'de | Kendi sunucunuzda |
|---|---|---|
| SSR ve API rotaları | Otomatik | Node süreci sürekli çalışmalı (PM2/systemd) |
| Statik dosyalar | Global CDN | Nginx veya önüne koyacağınız bir CDN |
next/image optimizasyonu | Yönetilen servis | sharp paketi ve sunucu CPU'su |
ISR ve revalidate | Otomatik | .next/cache dizini deploy'lar arasında korunmalı |
| Middleware | Edge çalışma ortamı | Node çalışma ortamı, davranış farkı olabilir |
| Zamanlanmış işler | Vercel Cron | Sistem cron veya systemd timer |
| Ortam değişkenleri | Panel | .env.production veya servis dosyası |
En çok sürprize yol açan satır next/image. Kendi sunucunuzda görsel optimizasyonu CPU harcar ve sharp paketinin kurulu olmasını ister. Trafiğiniz görsel ağırlıklıysa ya sharpı bağımlılıklara ekleyin ya da next.config içinde görselleri optimize edilmemiş bırakıp hazırlığı build aşamasına taşıyın.
İkinci sürpriz ISR'dir. Sunucuda revalidate ile yeniden üretilen sayfalar .next/cache altına yazılır. Her deploy'da bu dizini sıfırlarsanız ilk ziyaretçiler her seferinde yavaş yanıt alır. Deploy betiğinizde bu dizini yeni sürüme taşımak küçük ama etkili bir iyileştirmedir.
Sunucuyu Hazırlama: Node Sürümü, Kullanıcı ve Dizin Yapısı#
Uygulamayı root ile çalıştırmayın. Ayrı bir sistem kullanıcısı açmak, olası bir kod yürütme açığında zararı tek dizine hapseder.
# Uygulama icin ayri kullanici ve dizin
sudo adduser --system --group --home /var/www/nextapp nextapp
sudo mkdir -p /var/www/nextapp/releases /var/www/nextapp/shared
sudo chown -R nextapp:nextapp /var/www/nextapp
Node.js sürümünü dağıtımın paket deposundan değil, bir sürüm yöneticisiyle kurmanız uzun vadede işinizi kolaylaştırır; Next.js sürümleri belirli Node LTS aralıklarını şart koşar ve depodaki sürüm çoğu zaman geride kalır. Kurulum ayrıntıları için nvm ile Node.js kurulumu yazısına bakabilirsiniz. Ardından sürümü doğrulayın:
node -v # projenizin istedigi LTS surumu
npm -v
Dizin düzeninde işe yarayan basit bir kalıp şudur: her sürüm kendi klasörüne açılır, current ise aktif sürüme bakan bir sembolik bağdır. Böylece hatalı bir deploy'dan geri dönmek tek komuta iner.
/var/www/nextapp/
├── releases/
│ ├── 2026-08-18-1420/
│ └── 2026-08-17-0930/
├── shared/
│ ├── .env.production
│ └── cache/ # .next/cache burada saklanir
└── current -> releases/2026-08-18-1420
next build Standalone Çıktısı Nedir, Neden Kullanmalısınız#
Varsayılan build çıktısını çalıştırmak için next start kullanılır ve bu, projenin tüm node_modules ağacının sunucuda bulunmasını gerektirir. Orta ölçekli bir projede bu klasör kolayca birkaç yüz megabaytı bulur ve her deploy'da npm ci çalıştırmak dakikalar alır.
Standalone çıktı modu bu sorunu ortadan kaldırır. next.config dosyasına tek satır eklersiniz:
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
};
export default nextConfig;
Build sonrası Next.js, .next/standalone/ altında kendi kendine yeten bir klasör üretir: içinde küçük bir server.js ve yalnızca çalışma zamanında gerçekten gereken node_modules parçaları bulunur. Bu klasörü sunucuya kopyalayıp node server.js demeniz yeterlidir, ayrıca npm install çalıştırmanız gerekmez.
Tek bir tuzağı var ve neredeyse herkes ilk seferinde buna takılır: public/ ve .next/static/ klasörleri standalone çıktısına otomatik kopyalanmaz. Next.js bunları bir CDN'in servis etmesini varsayar. Kopyalamazsanız uygulama açılır ama CSS'siz, görselsiz, "stili bozulmuş" bir sayfa görürsünüz.
# Projede build
npm ci
npm run build
# Standalone ciktisini tamamla
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/
Bu üç komutu deploy betiğinizin sabit parçası hâline getirin. Aşağıdaki tablo iki modu karşılaştırır:
next start | output: 'standalone' | |
|---|---|---|
Sunucuda node_modules | Tam ağaç gerekir | Gerekmez |
| Deploy paketi boyutu | Birkaç yüz MB | Onlarca MB |
| Başlatma komutu | npm run start | node server.js |
| Statik dosya kopyalama | Gerekmez | public ve .next/static elle |
| Konteyner imajı için uygunluk | Zayıf | Çok iyi |
Standalone sunucu, portu ve dinleyeceği adresi ortam değişkenlerinden okur. Nginx arkasında çalışacağı için yalnızca yerel arayüzü dinlemesi doğrudur:
cd /var/www/nextapp/current
NODE_ENV=production PORT=3000 HOSTNAME=127.0.0.1 node server.js
HOSTNAME=127.0.0.1 satırını atlamayın. Aksi hâlde süreç tüm arayüzlerde dinler ve http://sunucu-ip:3000 adresi Nginx'i, dolayısıyla HTTPS'i ve tüm güvenlik başlıklarını atlayarak doğrudan erişilebilir hâle gelir.
Build Sırasında Bellek Yetmiyor: Yerelde Derleyip Sunucuya Taşıma#
2 GB RAM'li bir VDS'te orta ölçekli bir Next.js projesini derlemeye kalktığınızda tipik manzara şudur: build ilerler, optimize edilmiş üretim derlemesi oluşturulurken takılır ve süreç sessizce ölür. Çekirdek günlüğünde ise net bir cevap vardır.
sudo dmesg -T | grep -i "killed process"
# ... Out of memory: Killed process 21894 (next-build) ...
Belirti: Build hatasız görünen bir yerde aniden kesiliyor, Killed yazıyor ya da hiçbir mesaj vermeden bitiyor.
Sebep: Derleme sırasında bellek tükeniyor ve çekirdeğin OOM killer'ı süreci sonlandırıyor.
Çözüm: Üç seçeneğiniz var; hangisinin size uyduğuna karar verin.
1. Swap alanı ekleyin. En hızlı çözümdür. Derleme diske taşar, yavaşlar ama tamamlanır. Sürekli çalışan bir sunucuda swap zaten olmalıdır; nasıl oluşturulacağını swap alanı oluşturma yazısında bulabilirsiniz.
2. Node'un heap limitini yükseltin. Sunucuda gerçekten boş bellek varsa ama Node kendi varsayılan sınırına takılıyorsa işe yarar:
NODE_OPTIONS="--max-old-space-size=3072" npm run build
3. Build'i yerelde alıp yalnızca çıktıyı taşıyın. Uzun vadede en sağlıklı yöntem budur; sunucu yalnızca çalıştırma işini yapar, derlemenin CPU ve bellek yükünü hiç görmez.
# Yerel makinede
npm ci
npm run build
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/
# Sadece calismaya hazir ciktiyi gonder
rsync -avz --delete .next/standalone/ \
nextapp@sunucu-ip:/var/www/nextapp/releases/2026-08-18-1420/
Bu yöntemin bilinmesi gereken bir şartı var: NEXT_PUBLIC_ ile başlayan ortam değişkenleri build sırasında koda gömülür. Yerelde derliyorsanız bu değişkenleri üretim değerleriyle derlemeniz gerekir; sunucudaki .env.production dosyasına sonradan yazmanız hiçbir şeyi değiştirmez. Sunucu tarafında okunan gizli anahtarlar (veritabanı adresi, API anahtarları) ise çalışma zamanında okunur ve bu kısıtlamaya tabi değildir.
Bir de mimari uyum meselesi vardır: yerel makineniz arm64, sunucunuz x86_64 ise derlenmiş yerel eklenti içeren paketler (sharp, bcrypt, better-sqlite3 gibi) uyumsuz olur. Bu durumda ya sunucuda derleyin ya da bu paketleri sunucuda yeniden kurun.
PM2 ile Uygulamayı Ayakta Tutma#
Süreç yönetimini elle yapmak yerine PM2 kullanmak; çökme sonrası otomatik yeniden başlatma, log toplama ve sunucu açılışında ayağa kalkma özelliklerini hazır getirir. PM2'nin genel kullanımı PM2 ile Node.js süreç yönetimi yazısında ayrıntılı anlatılıyor; burada Next.js'e özgü yapılandırmaya odaklanalım.
// /var/www/nextapp/ecosystem.config.js
module.exports = {
apps: [
{
name: 'nextapp',
cwd: '/var/www/nextapp/current',
script: 'server.js',
instances: 1,
exec_mode: 'fork',
max_memory_restart: '600M',
env: {
NODE_ENV: 'production',
PORT: 3000,
HOSTNAME: '127.0.0.1',
},
},
],
};
Birkaç ayrıntı üzerinde durmaya değer. cwd mutlaka standalone çıktısının kök dizinini göstermelidir, çünkü server.js kendi konumuna göre .next klasörünü arar. instances değerini artırıp exec_mode alanını cluster yaparsanız gelen bağlantılar birden fazla süreç arasında paylaştırılır; ancak her sürecin kendi belleğini tükettiğini unutmayın — 2 GB RAM'li bir sunucuda tek örnek genellikle daha akıllıcadır. max_memory_restart ise bellek sızıntısı olan bir sürümde sunucuyu boğulmaktan kurtaran emniyet supabıdır.
pm2 start /var/www/nextapp/ecosystem.config.js
pm2 save # mevcut surec listesini kaydet
pm2 startup systemd # ciktidaki komutu sudo ile calistirin
pm2 logs nextapp --lines 100
pm2 save ve pm2 startup çiftini atlarsanız her şey çalışıyor görünür — ta ki sunucu yeniden başlayana kadar. O anda uygulamanız açılmaz ve nedenini bulmak dakikalarınızı alır.
systemd ile Çalıştırma: PM2'ye Alternatif#
PM2 kurmak istemiyorsanız, standalone çıktısı zaten sade bir Node sunucusu olduğu için systemd tek başına yeterlidir. Bir bağımlılık daha az, dağıtımla tam bütünleşik log ve yeniden başlatma politikası demektir. Birim dosyalarının genel yapısını systemd servis yönetimi yazısında bulabilirsiniz.
# /etc/systemd/system/nextapp.service
[Unit]
Description=Next.js uygulamasi
After=network.target
[Service]
Type=simple
User=nextapp
Group=nextapp
WorkingDirectory=/var/www/nextapp/current
EnvironmentFile=/var/www/nextapp/shared/.env.production
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=HOSTNAME=127.0.0.1
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now nextapp
sudo systemctl status nextapp
sudo journalctl -u nextapp -f
ExecStart satırındaki Node yolunu which node ile doğrulayın; nvm ile kurduysanız yol kullanıcı ev dizininin altında olabilir ve nextapp kullanıcısı oraya erişemez. Bu durumda ya Node'u sistem geneline kurun ya da ilgili sürümü herkesin okuyabileceği bir yola bağlayın.
İki seçenek arasında karar verirken pratik ayrım şudur: aynı sunucuda birden fazla Node uygulaması yönetiyor ve tek panelden bakmak istiyorsanız PM2, tek uygulama çalıştırıyor ve bağımlılık sayısını en aza indirmek istiyorsanız systemd daha uygundur.
Nginx Reverse Proxy ve Statik Dosyaların Doğrudan Servisi#
Node süreci artık 127.0.0.1:3000 üzerinde dinliyor. Dünyaya açılan kapı Nginx olacak. Reverse proxy mantığının genel anlatımı Nginx reverse proxy yapılandırması yazısında; aşağıdaki yapılandırma bunun Next.js'e uyarlanmış hâlidir.
# /etc/nginx/sites-available/nextapp.conf
upstream nextapp_upstream {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 80;
server_name alanadiniz.com www.alanadiniz.com;
client_max_body_size 20M;
# Hash'li ve degismeyen build ciktilari: dogrudan diskten servis
location /_next/static/ {
alias /var/www/nextapp/current/.next/static/;
access_log off;
expires 365d;
add_header Cache-Control "public, immutable";
}
# public/static altindaki sabit dosyalar
location /static/ {
alias /var/www/nextapp/current/public/static/;
access_log off;
expires 30d;
}
location / {
proxy_pass http://nextapp_upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 60s;
}
}
/_next/static/ bloğu bu yapılandırmanın en kârlı parçasıdır. Bu yol altındaki dosya adları içerik özeti taşır, yani içerik değiştiğinde ad da değişir — bu yüzden bir yıllık immutable önbellek tamamen güvenlidir. Blok olmadan her CSS ve JS isteği Node sürecine gider ve olay döngüsünü meşgul eder. Sıkıştırmayı da Nginx tarafında açmanız yerinde olur; ayrıntılar Nginx gzip ve Brotli sıkıştırma yazısında.
X-Forwarded-Proto başlığını da atlamayın. Uygulamanız yönlendirme üretirken veya çerezin secure bayrağını belirlerken bu başlığa bakar. Eksik olduğunda HTTPS üzerinden gelen istekler için http:// şemalı yönlendirmeler üretilir ve sonsuz döngüye girebilirsiniz.
sudo ln -s /etc/nginx/sites-available/nextapp.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
HTTPS: Let's Encrypt Sertifikası ve Yönlendirme#
Site HTTP üzerinden ayağa kalktıktan ve alan adı sunucunun IP'sine yöneldikten sonra sertifika almak birkaç dakikalık iştir.
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d alanadiniz.com -d www.alanadiniz.com
Certbot, Nginx yapılandırmasına 443 bloğunu ve 80'den 443'e yönlendirmeyi kendisi ekler. Yenilemenin çalıştığını görmeden bırakmayın — sertifikanın 90 günde bir yenilenmesi gerekir ve sessizce bozulan bir yenileme, üç ay sonra ziyaretçilerin uyarı ekranıyla karşılaşması demektir:
sudo certbot renew --dry-run
systemctl list-timers | grep certbot
Sertifika süreci ve doğrulama yöntemleri hakkında ayrıntı için Let's Encrypt ücretsiz SSL yazısına göz atabilirsiniz. HTTPS aktifleştikten sonra X-Forwarded-Proto başlığının https geldiğini bir kez doğrulayın; uygulama tarafındaki yönlendirme hatalarının büyük bölümü buradan çıkar.
Güncelleme, Kesintisiz Deploy ve Sık Karşılaşılan Hatalar#
Yeni sürüm çıkarken izlenecek akış şudur: yeni sürümü ayrı klasöre aç, korunması gereken dosyaları bağla, sembolik bağı çevir, süreci yeniden yükle.
#!/usr/bin/env bash
set -euo pipefail
REL="/var/www/nextapp/releases/2026-08-18-1420"
# Yereldeki standalone ciktisi bu dizine rsync'lendi varsayiliyor
ln -sfn /var/www/nextapp/shared/.env.production "$REL/.env.production"
mkdir -p "$REL/.next"
ln -sfn /var/www/nextapp/shared/cache "$REL/.next/cache"
ln -sfn "$REL" /var/www/nextapp/current
pm2 reload nextapp --update-env
# Son 5 surumu tut, digerlerini sil
ls -1dt /var/www/nextapp/releases/* | tail -n +6 | xargs -r rm -rf
.next/cache dizinini shared/ altına bağlamak, ISR ile üretilen sayfaların deploy'lar arasında hayatta kalmasını sağlar. Sembolik bağı çevirdikten sonra pm2 reload süreçleri yeniler; tek örnek çalıştırıyorsanız bir iki saniyelik kesinti olur. Bunu tolere etmek istemiyorsanız iki farklı port üzerinde iki servis çalıştırıp Nginx upstream tanımını değiştirerek geçiş yapabilirsiniz.
Sık karşılaşılan durumlar ve nedenleri:
| Belirti | Sebep | Çözüm |
|---|---|---|
| Sayfa açılıyor, CSS ve görseller yok | .next/static ve public kopyalanmamış | Build sonrası iki kopyalama komutunu ekleyin |
| 502 Bad Gateway | Node süreci ölmüş veya farklı portta | pm2 list, systemctl status, ss -ltnp çıktısını kontrol edin |
| Ortam değişkeni okunmuyor | NEXT_PUBLIC_ değişkeni build'de yoktu | Değişkeni build ortamına ekleyip yeniden derleyin |
| Sonsuz yönlendirme döngüsü | X-Forwarded-Proto eksik | Nginx proxy başlıklarını tamamlayın |
Cannot find module hatası | cwd yanlış, server.js .next bulamıyor | PM2 cwd veya systemd WorkingDirectory değerini düzeltin |
Son olarak sunucunun kendisini de unutmayın: 3000 portunu güvenlik duvarında dışarıya kapatın, SSH'a anahtarla girişi zorunlu kılın ve otomatik güvenlik güncellemelerini açın. Bu adımların derli toplu bir özetini sunucu güvenliği temelleri yazısında bulabilirsiniz. Makinenin kaynak ihtiyacını tahmin etmekte zorlanıyorsanız, tek örnekli bir Next.js uygulaması için 2 vCPU ve 4 GB RAM çoğu orta ölçekli site için rahat bir başlangıç noktasıdır.
Sıkça Sorulan Sorular#
Next.js'i kendi sunucumda çalıştırmak için Vercel'e ihtiyacım var mı?#
Hayır. Next.js açık kaynaklı bir çatıdır ve tüm özellikleri kendi sunucunuzda çalışır. Vercel yalnızca bu uygulamayı yönetilen bir platformda barındırma hizmeti sunar. SSR, API rotaları, ISR ve middleware dâhil her şeyi bir Node.js süreci olarak kendi VDS'inizde çalıştırabilirsiniz. Devraldığınız tek şey işletim sorumluluğudur: süreç yönetimi, sertifika, yedekleme ve ölçekleme sizin işiniz olur.
Standalone çıktısını kullanmazsam ne olur?#
Uygulamanız yine çalışır ancak sunucuda projenin tüm bağımlılık ağacının bulunması ve orada npm ci çalıştırmanız gerekir. Bu, deploy süresini dakikalar mertebesine çıkarır ve disk kullanımını birkaç yüz megabayt artırır. Küçük projelerde katlanılabilir, ancak standalone modunu açmak tek satırlık bir değişiklik olduğu için genellikle bunu tercih etmemek için bir neden kalmıyor.
PM2 mi systemd mi kullanmalıyım?#
Aynı sunucuda birden fazla Node uygulaması yönetiyorsanız hepsini tek listede görmek ve cluster modunu kolayca açmak için PM2 pratiktir. Tek bir uygulama çalıştırıyorsanız systemd fazladan bağımlılık gerektirmez, loglar günlük sisteminde toplanır ve yeniden başlatma politikası dağıtımla bütünleşiktir. İkisini aynı işi yapacak şekilde üst üste kurmayın; PM2 zaten kendini systemd üzerinden başlatır.
Sunucumun RAM'i build için yetmiyor, ne yapmalıyım?#
Önce swap alanı ekleyin; derleme yavaşlar ama tamamlanır. Yeterli olmazsa Node'un heap sınırını NODE_OPTIONS ile yükseltin. Kalıcı çözüm ise build'i yerel makinenizde veya bir sürekli entegrasyon ortamında alıp yalnızca standalone çıktısını sunucuya göndermektir. Bu yöntemde NEXT_PUBLIC_ ile başlayan değişkenlerin üretim değerleriyle derlendiğinden emin olun.
Nginx olmadan Next.js'i doğrudan 80 portunda çalıştırabilir miyim?#
Teknik olarak mümkündür ama önerilmez. Nginx olmadan TLS sonlandırmayı, statik dosyaların önbelleklenmesini, sıkıştırmayı, istek boyutu sınırlarını ve hız sınırlamayı uygulama içinde çözmeniz gerekir. Ayrıca 1024 altındaki portlara bağlanmak yükseltilmiş yetki ister. Node sürecini yerel arayüzde tutup önüne Nginx koymak hem güvenli hem de bakımı kolay olan standart yaklaşımdır.
Deploy sonrası eski sayfalar görünmeye devam ediyor, sebebi ne olabilir?#
İki yaygın neden var. Birincisi ISR: revalidate süresi dolmadığı için önbellekten üretilmiş eski HTML servis ediliyordur; ilgili sayfanın revalidate değerini kontrol edin veya isteğe bağlı yeniden doğrulama kullanın. İkincisi tarayıcı ya da araya giren bir CDN önbelleğidir. Statik dosya yolundaki dosyalar özet taşıdığı için sorun çıkarmaz, ancak HTML yanıtlarına uzun süreli önbellek başlığı verdiyseniz eski sayfa uzun süre yaşar.