Next.js na własnym VPS — deployment bez Vercela
Opublikowano: 10 kwietnia 2026 · Kategoria: VPS
Vercel to natywna platforma dla Next.js i jest wygodna — ale kosztowna przy poważnym ruchu i zamknięta dla zaawansowanych konfiguracji. Własny VPS daje pełną kontrolę: możesz uruchomić kilka aplikacji na jednym serwerze, skonfigurować cache na poziomie Nginx, używać własnej bazy danych i płacić stałą kwotę niezależnie od ruchu. Ten artykuł pokazuje jak wdrożyć Next.js 14+ (App Router i Pages Router) na VPS z PM2, Nginx i opcjonalnie Dockerem.
Konfiguracja next.config.js dla produkcji
// next.config.js / next.config.ts
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone', // KLUCZOWE — generuje samodzielny katalog do deployu
// Optymalizacja obrazow (WAZNE: nie dziala bez Node.js po stronie serwera)
images: {
domains: ['cdn.example.com'],
formats: ['image/avif', 'image/webp'],
},
// Naglowki bezpieczenstwa (alternatywnie w Nginx)
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
],
},
];
},
// Przekierowania
async redirects() {
return [
{ source: '/old-page', destination: '/new-page', permanent: true },
];
},
};
export default nextConfig; Build produkcyjny i struktura standalone
# Na serwerze VPS # 1. Zainstaluj Node.js (LTS przez nvm) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc nvm install --lts node --version # v20.x lub v22.x # 2. Sklonuj repo i zbuduj git clone https://github.com/twoj-user/twoj-projekt.git /srv/nextjs cd /srv/nextjs npm ci # instalacja zalenosci (z package-lock.json) npm run build # generuje .next/standalone # Struktura po buildzie z output: 'standalone': # .next/ # ├── standalone/ # │ ├── server.js <--- PLIK STARTOWY # │ ├── node_modules/ <--- minimalne zależności # │ └── .next/ # ├── static/ <--- pliki statyczne (serwowane przez Nginx) # └── cache/ # 3. Skopiuj pliki statyczne do standalone (wymagane!) cp -r public .next/standalone/public cp -r .next/static .next/standalone/.next/static # 4. Test uruchomienia node .next/standalone/server.js # Domyslnie sluchuje na porcie 3000 # HOSTNAME i PORT kontrolowane przez zmienne srodowiskowe
PM2 — process manager dla Next.js
# Instalacja PM2 globalnie
npm install -g pm2
# ecosystem.config.cjs (dla ESM-compat)
module.exports = {
apps: [
{
name: 'nextjs-app',
script: '.next/standalone/server.js',
cwd: '/srv/nextjs',
instances: 2, // lub 'max' dla wszystkich rdzeni
exec_mode: 'cluster', // UWAGA: ISR cache nie jest wspoldzielony miedzy instanceami
autorestart: true,
max_memory_restart: '1G',
env_production: {
NODE_ENV: 'production',
PORT: 3000,
HOSTNAME: '127.0.0.1',
// Nie wpisuj sekretow tutaj — uzyj .env.production lub EnvironmentFile
},
error_file: '/var/log/nextjs/error.log',
out_file: '/var/log/nextjs/out.log',
},
],
};
# Start i autostart
sudo mkdir -p /var/log/nextjs
pm2 start ecosystem.config.cjs --env production
pm2 save && pm2 startup
# Aktualizacja aplikacji (zero-downtime)
cd /srv/nextjs && git pull && npm ci && npm run build
cp -r public .next/standalone/public
cp -r .next/static .next/standalone/.next/static
pm2 reload nextjs-app Nginx z cache dla plików statycznych
Kluczowa różnica w stosunku do Vercela: musisz sam skonfigurować cache dla statycznych
assetów. Next.js generuje pliki w _next/static/ z hashem w nazwie — można je cache'ować
na rok. Nginx obsłuży je bezpośrednio z dysku, bez angażowania Node.js.
# /etc/nginx/sites-available/nextjs-app
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Pliki statyczne serwowane bezposrednio — omijamy Node.js
location /_next/static/ {
alias /srv/nextjs/.next/static/;
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary Accept-Encoding;
gzip_static on;
}
# Pliki z katalogu public/
location /public/ {
alias /srv/nextjs/public/;
expires 30d;
add_header Cache-Control "public";
}
# Favicon i robots.txt
location = /favicon.ico {
root /srv/nextjs/public;
expires 30d;
}
# Next.js Node.js server
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
} Zmienne środowiskowe i sekrety
# /srv/nextjs/.env.production.local (NIE commituj do git)
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
NEXTAUTH_SECRET=twoj-super-tajny-klucz
NEXTAUTH_URL=https://example.com
NEXT_PUBLIC_API_URL=https://api.example.com # ta zostanie wbudowana w JS (publiczna!)
# Uwaga: NEXT_PUBLIC_* sa wbudowane w JavaScript podczas budowania
# — NIE mozna ich zmienic bez rebuildu aplikacji
# Wszystkie inne zmienne sa dostepne tylko po stronie serwera
# Zaladowanie .env w PM2 (dodaj do ecosystem.config.cjs):
# env_production: {
# NODE_ENV: 'production',
# PORT: 3000,
# }
# Lub uzyj "node-config" / dotenv w server.js
# Alternatywnie — systemd z EnvironmentFile:
# /etc/systemd/system/nextjs.service
# ...
# EnvironmentFile=/srv/nextjs/.env.production.local
# ExecStart=/usr/bin/node /srv/nextjs/.next/standalone/server.js Docker multi-stage build (alternatywa)
# Dockerfile dla Next.js standalone
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
FROM node:20-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:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
USER nextjs
EXPOSE 3000
ENV PORT=3000 HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
# docker-compose.yml (uproszczony)
# services:
# nextjs:
# build: .
# restart: unless-stopped
# environment:
# - DATABASE_URL=${DATABASE_URL}
# ports:
# - "127.0.0.1:3000:3000" Vercel vs VPS — porównanie kosztów
| Aspekt | Vercel Free | Vercel Pro | VPS Contabo (4 vCPU) |
|---|---|---|---|
| Cena miesięczna | 0 USD | $20 + użycie | ~25 EUR (stała) |
| Bandwidth | 100 GB/msc | 1 TB/msc | Zwykle nielimitowany |
| Liczba projektów | Unlimited | Unlimited | Unlimited (1 serwer) |
| Własna baza danych | Vercel Postgres ($) | Vercel Postgres ($) | PostgreSQL na serwerze |
| Edge Functions | Tak (globalnie) | Tak (globalnie) | Nie (jeden region) |
| Konfiguracja | Zero | Zero | Wymagana (Nginx, PM2) |