Next.js CI/CD in der Praxis: Automatisierte Tests und Deployments mit GitHub Actions

Sie starren auf die scrollenden Logs im Terminal und tippen mechanisch git pull && npm install && npm run build && pm2 restart. Heute schon das dritte Deployment – morgens ein kleiner Bugfix, mittags eine API-Optimierung, jetzt wieder ein paar Style-Anpassungen. Drei Server, auf jedem derselbe Ablauf.
Nach dem letzten Server zeigt die Uhr sieben.
Auf dem Heimweg dachte ich: Es muss einen besseren Weg geben. Nicht nach jedem Code-Change dieselben Schritte auf dem Server wiederholen – und nicht die Sorge, dass ein vergessener Neustart Probleme in Produktion verursacht. Tests? Die wurden oft übersprungen – deployen ging vor, und später kam der Bug, dann alles von vorn.
Mit CI/CD und GitHub Actions hat sich der Workflow grundlegend geändert. Ein Push zu GitHub – Tests, Build und Deployment laufen automatisch. In der Zeit für einen Kaffee ist die neue Version online.
Wenn manuelles Deploy Sie nervt, zeige ich im Folgenden, wie Sie mit GitHub Actions eine automatisierte Next.js-Pipeline aufsetzen – von der Basis-Konfiguration bis zum vollständigen Praxisbeispiel, inklusive Fallstricken und Lösungen.
Warum CI/CD?
Die Schmerzen des manuellen Deployments
Am Anfang meiner Next.js-Projekte sah der Ablauf so aus: lokal entwickeln, kurz testen, per SSH auf den Server, git pull, npm install, npm run build, pm2 restart. Alles glatt – trotzdem gut zehn Minuten.
Oft lief es nicht glatt.
Einmal auf drei Servern deployt: zwei erfolgreich, beim dritten brach die SSH-Verbindung ab – unbemerkt. Am nächsten Tag: die Seite wirkt mal aktuell, mal veraltet – der Load Balancer traf auf den nicht aktualisierten Server. Ein anderes Mal: Code geändert, sofort deployed, Tests vergessen – kritischer Bug in Produktion, eiliges Rollback.
Besonders tückisch bei mehreren Servern: Jeder npm run build erzeugt eine neue Next.js-Build-ID. Drei Server, drei IDs – der Load Balancer verteilt Requests, Next.js erkennt ID-Wechsel und erzwingt einen Hard Refresh. Die UX leidet.
Was CI/CD löst
Kurz gesagt: wiederholbare, fehleranfällige Schritte werden automatisiert.
CI (Continuous Integration) übernimmt Tests. Bei jedem Push laufen Unit-Tests, Typ- und Lint-Checks. Schlägt etwas fehl, gibt es kein Deployment – Sie beheben zuerst den Fehler. So landet fehlerhafter Code seltener in Produktion.
CD (Continuous Deployment) übernimmt Build und Deployment. Tests grün? Build startet automatisch, danach Deployment auf die Server – ohne manuelles Eingreifen.
Mein Workflow heute: lokal coden → Push zu GitHub → Tests → Build → Deploy. Push bis Live: etwa fünf Minuten, ohne am Terminal zu warten.
Zusätzlich: Nachvollziehbarkeit. Jeder Lauf hat Logs – welcher Commit, welche Testergebnisse, welche Server. Bei Problemen finden Sie die Ursache schneller.
GitHub Actions: Basis-Konfiguration
Wie es funktioniert
Workflow, Job, Step – am Anfang wirkt das viel. Die Idee ist einfach:
Workflow = eine komplette Automatisierung (z. B. Test + Build + Deploy), definiert in einer YAML-Datei unter .github/workflows.
Job = eine Aufgabe im Workflow, z. B. „test“ und „deploy“. Jobs können parallel laufen oder per needs nacheinander.
Step = einzelne Aktion im Job: Code auschecken, Dependencies installieren, Tests starten.
Trigger flexibel: bei Push auf main, nur bei Pull Requests, oder per Cron.
Erster Workflow
Ordner .github/workflows anlegen, Datei ci-cd.yml:
name: CI/CD Pipeline
# Trigger: Push auf main
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
Bedeutung: Bei Push auf main läuft auf Ubuntu Checkout, Node 18, npm ci, npm run build.
Nach dem Commit unter Actions im Repository den Lauf sehen – das erste grüne Häkchen motiviert.
Secrets für sensible Daten
Für Server-Deploys brauchen Sie IP, SSH-Keys, API-Tokens. Nicht direkt in die YAML schreiben – das Repository wäre exponiert.
Stattdessen Secrets: Repository → Settings → Secrets and variables → Actions → New repository secret, z. B. SERVER_HOST mit der Server-IP.
Im Workflow: ${{ secrets.SERVER_HOST }} – GitHub ersetzt den Wert; in Logs wird maskiert.
Automatisierte Tests
Test-Setup
Alles, was automatisierbar ist, automatisieren: ESLint, TypeScript, Jest – damit fangen die meisten Probleme früh ab.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm' # npm-Cache beschleunigt Folgeläufe
- name: Install dependencies
run: npm ci
- name: Lint check
run: npm run lint
- name: Type check
run: npm run type-check
- name: Run tests
run: npm run test -- --coverage
cache: 'npm' cached node_modules – ab dem zweiten Lauf deutlich schneller.
Tests beschleunigen
Anfangs dauerte ein Lauf über zehn Minuten – nach Optimierung etwa drei.
Caching – npm und Next.js-Build-Cache:
- name: Cache Next.js build
uses: actions/cache@v3
with:
path: |
~/.npm
.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}
Next.js muss nicht jede Seite jedes Mal neu bauen.
Parallelisierung – unabhängige Checks in separate Jobs:
jobs:
lint:
runs-on: ubuntu-latest
steps: [...] # nur Lint
test:
runs-on: ubuntu-latest
steps: [...] # nur Unit-Tests
type-check:
runs-on: ubuntu-latest
steps: [...] # nur Typen
Die drei Jobs starten parallel; die Gesamtzeit entspricht dem langsamsten Job.
Typische Fallen
Tests lokal grün, in Actions rot? Bei PRs merged GitHub Actions oft PR-Branch und Ziel-Branch zu einem temporären Commit – den gibt es lokal nicht.
Lösung beim Checkout:
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }} # Original-PR-Branch
Timeout: Langsame E2E-Tests – timeout-minutes erhöhen:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15 # Standard: 6
Automatisches Deployment
Vercel: der einfachste Weg
Projekt auf Vercel? Nach Verknüpfung mit GitHub deployt jeder Push oft schon automatisch.
Feinere Kontrolle (z. B. Deploy nur nach grünen Tests) – offizielle Vercel Action:
jobs:
deploy:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
Token, Org-ID und Project-ID aus dem Vercel-Dashboard in GitHub Secrets legen.
Vercel erstellt pro PR oft Preview-URLs – Änderungen vor dem Merge prüfen.
Eigenes Hosting: flexibler, etwas aufwendiger
Eigene Server geben mehr Kontrolle. SSH einrichten, GitHub Actions führt Deploy-Befehle remote aus.
SSH-Key lokal erzeugen:
ssh-keygen -t ed25519 -C "github-actions"
Public Key in ~/.ssh/authorized_keys auf dem Server, Private Key als GitHub Secret (z. B. SSH_PRIVATE_KEY).
jobs:
deploy:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/my-nextjs-app
git pull origin main
npm install
npm run build
pm2 restart nextjs-app
Pull, Install, Build, Restart – ohne manuelles SSH.
Einheitliche Build-ID bei mehreren Servern
Bei Load Balancing müssen alle Instanzen dieselbe Build-ID haben – sonst Hard Refreshes.
Einmal bauen, Artefakte verteilen:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- run: npm ci
- run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: next-build
path: |
.next
public
deploy:
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
server: [server1, server2, server3]
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: next-build
- name: Deploy to ${{ matrix.server }}
uses: appleboy/scp-action@master
with:
host: ${{ secrets[format('{0}_HOST', matrix.server)] }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: ".next,public"
target: "/var/www/my-nextjs-app"
Ein Build, identische Artefakte auf allen Servern – konsistente Build-ID.
Optimierung und Best Practices
Wenn der Build scheitert
Automatisierung hilft nur, wenn Sie Fehler sehen. Push erfolgreich, Deploy fehlgeschlagen – ohne Benachrichtigung merken Sie es zu spät.
Slack-Benachrichtigung bei Fehler:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
[...Deploy-Schritte...]
- name: Notify on failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment fehlgeschlagen – bitte prüfen'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
channel: '#deploy-notifications'
Branch-Strategie: Staging und Produktion
develop → Testumgebung, main → Produktion:
on:
push:
branches:
- main
- develop
jobs:
deploy-staging:
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
steps:
- [...Staging-Deploy...]
deploy-production:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- [...Produktions-Deploy...]
Entwicklung auf develop, nach Prüfung Merge nach main.
Manuelle Freigabe für Produktion – environment im Job, Approver im Repository:
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
steps: [...]
Deploy pausiert bis zur Freigabe – sinnvoll für kritische Projekte.
Rollback
Trotz Tests kann Produktion Probleme haben – schnelles Rollback zählt.
Einfaches Muster: Tags pro Release, Artefakte versionieren, workflow_dispatch mit Tag-Eingabe:
on:
workflow_dispatch:
inputs:
tag:
description: 'Tag für Rollback'
required: true
jobs:
rollback:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag }}
- name: Deploy
[...normaler Deploy-Ablauf...]
In Actions „Run workflow“, Tag eingeben – Rollback startet.
Umgebungsvariablen
API-URLs, DB-Strings – nicht hardcoden, nicht ins Git.
Empfehlung:
- Sensible Werte in GitHub Secrets
- Im Workflow als
envinjizieren - Next.js liest sie beim Build
- name: Build
run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.API_URL }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
Pro Umgebung andere Secrets – Code bleibt gleich.
Praxisbeispiel: vollständige Konfiguration
Diese Pipeline kombiniert Test, Build und Deploy – als Vorlage anpassbar:
name: Next.js CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint check
run: npm run lint
- name: Type check
run: npm run type-check
- name: Run tests
run: npm run test -- --coverage
build:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Cache dependencies and build
uses: actions/cache@v3
with:
path: |
~/.npm
.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.API_URL }}
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: next-build
path: |
.next
public
package.json
deploy-staging:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/develop'
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: next-build
- name: Deploy to staging server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/staging
pm2 stop nextjs-app || true
rm -rf .next public
pm2 start npm --name "nextjs-app" -- start
pm2 save
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Staging-Deployment ${{ job.status == "success" && "erfolgreich" || "fehlgeschlagen" }}'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
deploy-production:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
environment:
name: production
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: next-build
- name: Deploy to production server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/production
pm2 stop nextjs-app || true
rm -rf .next public
pm2 start npm --name "nextjs-app" -- start
pm2 save
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Produktions-Deployment ${{ job.status == "success" && "erfolgreich" || "fehlgeschlagen" }}'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
PRs: nur Tests. develop → Staging, main → Produktion (mit Approval). Slack bei jedem Deploy.
Secrets im Repository:
API_URLSTAGING_HOST/PROD_HOSTSERVER_USERSSH_PRIVATE_KEYSLACK_WEBHOOK(optional)
Danach: coden, committen, pushen – der Rest läuft automatisch.
Fazit
Vom manuellen Deploy zur Automatisierung hat meinen Alltag verändert: keine wiederholten Server-Befehle, keine vergessenen Tests, kein Warten am Terminal.
GitHub Actions CI/CD braucht anfangs Setup-Zeit – lohnt sich dauerhaft. Mit minimaler Konfiguration starten, Tests und Build stabilisieren, dann Deploy, Benachrichtigungen und Rollback ergänzen.
Push, Kaffee, neue Version online – wer es einmal erlebt hat, will selten zurück zum manuellen Weg.
Legen Sie los: erste Workflow-Datei im Next.js-Projekt, unter Actions den ersten grünen Lauf ansehen – dann wird der Unterschied klar.
Next.js CI/CD: Vollständiger Konfigurationsablauf
Von der GitHub-Actions-Workflow-Datei bis zu Test-, Build- und Deploy-Schritten
⏱️ Estimated time: 2 hr
- 1
Step 1: GitHub-Actions-Workflow anlegen
Datei .github/workflows/deploy.yml erstellen:
```yaml
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
- run: npm run build
- run: npm test
```
Wichtig:
• Trigger: Push auf main
• Laufzeit: ubuntu-latest
• Schritte: checkout → setup-node → install → build → test - 2
Step 2: Test-Schritte konfigurieren
Tests hinzufügen:
```yaml
- name: Run tests
run: npm test
- name: Type check
run: npm run type-check
- name: Lint
run: npm run lint
```
Wichtig:
• Fehlgeschlagene Tests blockieren Deploy
• Typen- und Lint-Checks sichern Qualität
Vorteile:
• Fehlerhafter Code erreicht Produktion seltener
• Checks laufen automatisch – nichts vergessen - 3
Step 3: Build-Schritt konfigurieren
Build:
```yaml
- name: Build
run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
```
Cache:
```yaml
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
```
Wichtig:
• Umgebungsvariablen setzen
• Cache für schnellere Builds
• Build-Ausgabe prüfen - 4
Step 4: Deploy-Schritt konfigurieren
SSH-Deploy:
```yaml
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /path/to/app
git pull
npm install
npm run build
pm2 restart app
```
Multi-Server:
```yaml
- name: Deploy to servers
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOSTS }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /path/to/app
git pull
npm install
# Artefakte aus GitHub Actions verwenden
pm2 restart app
```
Wichtig:
• SSH-Key-Authentifizierung
• Einheitliche Build-ID (zentral in Actions bauen)
• Kein paralleles Build auf jedem Server
FAQ
Warum brauche ich CI/CD?
• Nach jedem Change dieselben Server-Schritte
• Vergessene Neustarts oder Tests
• Fehler bei Multi-Server-Deployments
• Unterschiedliche Build-IDs → Hard Refresh
Vorteile von CI/CD:
• Automatisierte Tests, Builds, Deployments
• Push → Live
• Weniger manuelle Fehler
• Einheitliche Build-ID
• Höhere Entwicklerproduktivität
Praxis:
• Drei Server – einer nicht aktualisiert, Load Balancer zeigt alte Version
• Deploy ohne Tests – kritischer Bug in Produktion
Lösung: gesamten Ablauf mit GitHub Actions automatisieren.
Wie konfiguriere ich GitHub Actions?
```yaml
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
- run: npm run build
- run: npm test
```
Wichtig:
• Trigger: Push auf main
• ubuntu-latest
• checkout → setup-node → install → build → test
Secrets:
• Settings → Secrets: HOST, USERNAME, SSH_KEY usw.
• Für SSH-Deploy
Tipp: minimal starten, Tests und Build zum Laufen bringen, dann Deploy ergänzen.
Wie vermeide ich unterschiedliche Build-IDs bei mehreren Servern?
Lösung: zentral bauen
In GitHub Actions:
```yaml
- name: Build
run: npm run build
- name: Deploy to servers
uses: appleboy/ssh-action@master
with:
script: |
cd /path/to/app
git pull
# Artefakte aus GitHub Actions
pm2 restart app
```
Oder mit Artefakten:
```yaml
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build
path: .next
- name: Deploy to servers
uses: appleboy/ssh-action@master
with:
script: |
# Artefakte herunterladen und deployen
```
Wichtig:
• Ein Build in Actions
• Gleiche Artefakte auf allen Servern
• Kein Build pro Server
Vorteil: konsistente ID, bessere UX.
Wie richte ich Test-Schritte ein?
- name: Run tests
run: npm test
- name: Type check
run: npm run type-check
- name: Lint
run: npm run lint
```
Wichtig:
• Fehlgeschlagene Tests stoppen Deploy
• Typen und Lint für Qualität
Vorteile:
• Weniger fehlerhafte Releases
• Automatische Checks
• Bessere Codequalität
Tipp: einfach starten, Coverage schrittweise erhöhen.
Wie konfiguriere ich Deploy-Benachrichtigungen?
```yaml
- name: Notify Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment abgeschlossen'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
```
E-Mail:
```yaml
- name: Send email
uses: dawidd6/action-send-mail@v3
with:
to: [email protected]
subject: 'Deployment abgeschlossen'
body: 'Produktions-Deployment erfolgreich'
```
Wichtig:
• Erfolg und Fehler melden
• Version, Zeit, Umgebung angeben
• Team schnell informieren
Tipp: Slack für Echtzeit, E-Mail als Backup.
Was sind CI/CD-Best Practices?
1. Tests und Build stabilisieren
2. Deploy hinzufügen
3. Benachrichtigungen und Rollback
Nicht alles auf einmal – Basis zuerst, dann verfeinern.
Kontinuierlich verbessern:
• Logs nach jedem Deploy prüfen
• Konfiguration anpassen
• Build-Zeit optimieren
• Test-Coverage erhöhen
Kennzahlen:
• Build-Dauer
• Test-Erfolgsrate
• Deploy-Erfolgsrate
• Rollback-Häufigkeit
CI/CD ist ein Prozess, kein einmaliges Setup.
9 Min. Lesezeit · Veröffentlicht am: 20. Dez. 2025 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js auf Vercel deployen: Umgebungsvariablen, Domains und Performance-Monitoring
Vollständiger Leitfaden zum Deployment von Next.js auf Vercel: Umgebungsvariablen, eigene Domains, SSL-Zertifikate und Performance-Monitoring – mit typischen Anfänger-Fallen.
Teil 40 von 51
Nächster
Weg von Vercel: Der vollständige Leitfaden zur Next.js Docker-Self-Hosting
Genervt von hohen Vercel-Rechnungen? Dieser Leitfaden zeigt, wie Sie Next.js mit Docker selbst hosten – inklusive Standalone-Konfiguration, Reverse-Proxy-Setup und Fixes für Streaming-Rendering. Sparen Sie monatlich 300–500 $.
Teil 42 von 51
Ähnliche Beiträge
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen