name: Deploy Next.js App
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
env:
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
run: npm run build
- name: Copy build to VPS
uses: appleboy/scp-action@master
with:
host: ${{ secrets.VPS_IP }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: ".next,public,package.json,package-lock.json,next.config.js"
target: "/var/www/my-app"
- name: Restart App via SSH
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.VPS_IP }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/my-app
npm ci --only=production
pm2 reload my-next-app || pm2 start npm --name "my-next-app" -- startVercel e genial pentru MVP-uri, dar când proiectul începe să aibă tracțiune, costurile devin ridicole. Am mutat recent o aplicație Next.js cu 12.000 de utilizatori activi pe un VPS de 6 euro de la Hetzner și am automatizat totul printr-un pipeline simplu de GitHub Actions.
Dacă vrei control total și costuri fixe, varianta asta e sfântă. Pierzi preview deploys-urile automate, dar salvezi bani grei.
Structura pipeline-ului: Cache-ul e rege
Cea mai mare greșeală pe care o văd la configurarea CI/CD pentru Next.js este ignorarea cache-ului. Fără el, fiecare build descarcă iar node_modules și recompilează absolut tot de la zero.
La primul run pe proiectul meu, build-ul a durat aproape 6 minute. Destul de enervant când ai de dat un hotfix rapid. După ce am configurat corect cache-ul pentru npm și .next/cache, timpul a scăzut la 1 minut și 40 de secunde. Am economisit cam 70% din timp la fiecare rulare a pipeline-ului.
Workflow-ul rulează pe trei piloni simpli: linting, teste și build-ul propriu-zis. Doar dacă toate astea trec cu succes, trecem la faza de deploy.
Deploy-ul prin SSH: Cum facem update fără downtime mare
Pentru deploy, am ales varianta clasică: rsync și PM2. Rulăm build-ul direct în runner-ul de GitHub, apoi copiem doar fișierele necesare pe server (fără codul sursă necompilat). E mult mai curat așa decât să faci git pull și npm run build direct pe serverul de producție, unde ai risca să blochezi CPU-ul VPS-ului în timpul compilării.
Folosesc appleboy/scp-action ca să urc build-ul și apoi appleboy/ssh-action ca să rulez un script de restart. Pe server, PM2 se ocupă de rularea procesului Next.js în fundal.
Capcane de care m-am lovit
Cea mai mare bătaie de cap am avut-o cu variabilele de mediu (.env). Next.js are nevoie de variabilele care încep cu NEXT_PUBLIC_ în timpul build-ului (build-time), nu doar la runtime. Dacă le pui doar pe server, client-side-ul nu le va vedea și te trezești cu API calls către undefined. Am rezolvat asta injectând secretele din GitHub direct în pasul de build din workflow.
Un alt trade-off e downtime-ul de o secundă când PM2 dă restart. Pentru un proiect mic spre mediu, e perfect acceptabil. Dacă ai nevoie de zero-downtime real, trebuie să te uiți spre un reverse proxy mai deștept sau Docker cu blue-green deployment, dar asta adaugă o complexitate pe care s-ar putea să nu o vrei încă.
Voi cum faceți? Rămâneți pe Vercel până când factura devine o problemă, sau preferați bătaia de cap inițială cu un VPS configurat manual?