name: Deploy Next.js to VPS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Cache Next.js build
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: npm ci
- run: npm run lint
- run: npm test -- --passWithNoTests
- run: npm run build
- name: Copy build to VPS
uses: appleboy/rsync-action@master
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
source: ".next/standalone/ .next/static/ public/"
target: "/var/www/my-app"
script: |
cp -r /var/www/my-app/.next/static /var/www/my-app/.next/standalone/.next/
cp -r /var/www/my-app/public /var/www/my-app/.next/standalone/
pm2 reload my-app || pm2 start /var/www/my-app/.next/standalone/server.js --name my-appVercel e genial când lansezi un MVP, dar când crește traficul sau vrei control total, abonamentele Pro devin scumpe. Am mutat recent o aplicație Next.js 14 cu peste 12k utilizatori activi de pe Vercel direct pe un VPS Hetzner de 6 euro. În postarea asta vă arăt pipeline-ul meu de GitHub Actions care face linting, teste, build și deploy automat prin SSH, fără downtime.
De ce facem build-ul în GitHub și nu direct pe VPS?
Prima greșeală pe care am făcut-o pe un proiect mai vechi a fost să dau git pull pe server și să rulez npm run build acolo. Dacă ai un VPS ieftin cu 2GB RAM, procesul next build îți va mânca rapid toată memoria și va crăpa violent cu eroare de OOM (Out of Memory). Plus că leși serverul de producție la 100% CPU timp de câteva minute, ceea ce afectează request-urile userilor reali.
Soluția curată este să folosești GitHub Actions pe post de builder. Runner-ul gratuit de la GitHub vine cu 7GB RAM și 2 vCPU. Compilezi totul în cloud-ul lor, creezi artefactul și îl trimiți pe server gata împachetat.
Trucul esențial: Standalone Output în Next.js
Înainte să scrii vreo linie de YAML în GitHub Actions, deschide next.config.js și adaugă opțiunea output: 'standalone'. Fără configurarea asta, ar trebui să copiezi tot folderul node_modules pe server — adică vreo 800MB de fișiere mici care se transferă groaznic de încet prin SSH.
Cu modul standalone, Next.js analizează exact ce pachete folosești și creează un folder .next/standalone extrem de minimalist, care include doar strictul necesar. Artefactul final scade la doar 50-80MB.
Cum structurăm pipeline-ul de CI/CD
Am împărțit workflow-ul în 4 pași logici, ca să prindem erorile înainte ca vreun fișier să atingă producția:
- Linting și type-checking: Rulăm
npm run lintși verificăm compilarea TypeScript. Dacă uit dintr-o greșeală unanyinterzis sau am o sintaxă greșită, pipeline-ul se oprește în primele 15 secunde. - Rularea testelor unitare: Testăm componentele critice și reducer-ele.
- Build cu Caching inteligent: Cache-uim directoarele
node_modulesși.next/cache. Asta e cheia vitezei: am tăiat build time-ul de la 4 minute jumătate la doar 1 minut și 20 de secunde. - Deploy prin SSH și Rsync: Trimitem artefactele standalone pe VPS și reîncărcăm procesul.
Gestionarea secretelor și zero downtime
Setările de acces (IP-ul serverului, userul de SSH și cheia privată) trebuie salvate obligatoriu în GitHub -> Settings -> Secrets and variables -> Actions. Niciodată nu hardcodăm credențiale în cod.
Pe serverul de producție folosesc pm2 ca manager de procese Node.js. În loc să dau pm2 restart, comanda mea din pipeline folosește pm2 reload. Diferența e enormă: reload face un hot swap în fundal — pornește instanța nouă, așteaptă să fie ready, și abia apoi o oprește pe cea veche. Zero conexiuni pierdute pentru utilizatori.
Trade-off-ul sincer: VPS vs. Vercel
Să fim realiști: pierzi confortul de Preview Deployments pe fiecare Pull Request pe care ți-l oferă Vercel out-of-the-box. Dacă ai nevoie de medii de staging dinamice pentru fiecare PR, configurarea pe VPS propriu cu Nginx reverse proxy devine destul de complicată.
Însă pentru un proiect cu staging fix și producție, diferența de costuri e colosală. La proiectul meu am salvat în jur de 140$ pe lună, iar performanța e identică, dacă pui un Cloudflare gratuit în față pentru caching de resurse statice.
Voi ce setup folosiți pentru aplicațiile de Next.js în producție? Ați rămas pe serverless sau preferați un VPS clasic cu PM2/Docker?