name: Deploy Next.js to VPS
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: Cache Next.js build
uses: actions/cache@v3
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') }}-
- name: Install dependencies
run: npm ci
- name: Lint and Test
run: |
npm run lint
npm run test --if-present
- name: Build application
run: npm run build
- name: Copy files to VPS
uses: appleboy/scp-action@v0.1.4
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: ".next,public,package.json,package-lock.json,next.config.js"
target: "/var/www/my-next-app"
- name: SSH Command to restart PM2
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/my-next-app
npm install --production
pm2 reload my-next-app || pm2 start npm --name "my-next-app" -- startSă fim serioși, Vercel e mișto până când îți vine factura sau ai nevoie de resurse mari pe care un VPS ieftin de la Hetzner sau DigitalOcean le rezolvă instant. Am trecut recent un proiect de Next.js de pe Vercel pe un VPS propriu și am pus la punct un pipeline de CI/CD simplu și rapid în GitHub Actions. Hai să-ți arăt cum am configurat totul, de la linting la deploy-ul final prin SSH, fără să ne complicăm cu Docker dacă nu e cazul.
De ce nu Docker? Trade-off-ul simplității
La un proiect recent cu 8.000 de useri activi, am ales să nu folosesc Docker. Docker e excelent pentru izolare, dar adaugă un layer de complexitate la build time și mănâncă resurse prețioase pe un VPS mic de 2GB RAM. Am mers pe varianta clasică: build în runner-ul de GitHub Actions, trimis fișierele prin SCP și restart la PM2 pe server.
Merge brici pentru proiecte medii, dar are un trade-off sincer: în timpul deploy-ului (acele câteva secunde de instalare și restart), procesul PM2 poate da scurte erori de conexiune (502 Bad Gateway) dacă nu ai configurat PM2 în cluster mode sau un reload de tip graceful. Pentru noi, a fost un compromis acceptabil față de overhead-ul Docker.
Cum am economisit 60% din timpul de build
Next.js e notoriu pentru timpii de build dacă nu refolosești cache-ul. Prima rulare în GitHub Actions fără cache a durat aproape 4 minute. După ce am configurat corect cache-ul pentru Next.js (.next/cache) și node_modules, build-ul a scăzut la 1 minut și 15 secunde.
Cheia este să folosești actions/cache cu chei de hashing bazate pe package-lock.json. Next.js are nevoie specifică de cache-ul său intern pentru a nu recompila paginile statice neschimbate la fiecare rulare.
Pipeline-ul cap-la-cap și deploy-ul
Configurația rulează pe fiecare push în branch-ul main. Face instalarea, rulează linter-ul (next lint), testele și apoi build-ul local în runner. Dacă totul trece cu succes, trimitem fișierele pe VPS.
Pentru ca deploy-ul să fie rapid, trimitem doar fișierele esențiale: .next, public, package.json și next.config.js. Nu trimite niciodată node_modules prin SSH; e o moarte lentă din cauza sutelor de mii de fișiere mici. Mai bine rulezi npm install --production direct pe server după transfer.
Folosesc appleboy/scp-action ca să mut fișierele de build pe server, iar apoi appleboy/ssh-action ca să rulez comanda de restart. Pe server, PM2 se ocupă de rularea aplicației în background. Ai nevoie de chei SSH configurate în GitHub Secrets pentru asta (SSH_PRIVATE_KEY, SSH_HOST, SSH_USER).
Voi cum faceți deploy-ul la proiectele Next.js când vreți să evitați costurile de Vercel? Mergeți pe varianta clasică cu PM2 sau preferați să împachetați totul în Docker de la început?