Next.js über Supabase sicher betreiben
DevOps-Runbook für Next.js auf Supabase: Architektur, Middleware, Auth-Patterns, Rate Limiting und Claude-Code-Integration.
Nachdem Supabase als Backend-Plattform stabil läuft, entsteht der eigentliche Anwendungs-Stack darüber.
In vielen modernen Projekten übernimmt Next.js die Rolle der App-Schicht:
- Frontend Rendering
- Server Side Rendering
- Server Actions
- Route Handlers
- API Proxy
- Session Handling
Damit wird Next.js faktisch ein Backend-Gateway zwischen Browser und Supabase.
Der häufigste Fehler besteht darin, dass diese Schicht als “Frontend” behandelt wird, obwohl sie tatsächlich serverseitige Logik mit hohen Privilegien enthält.
Dieses Runbook beschreibt, wie Next.js sicher über einer self-hosted Supabase-Plattform betrieben wird.
Jeder Abschnitt enthält:
- Implementierung
- prüfbare Bedingung
- Failure-Scenario
Auf einen Blick - Artikel 2 von 6 der DevOps-Runbook-Serie
- Next.js als eigener Container, physisch getrennt von Supabase
- service_role Key nur in einer einzigen Datei erlaubt (lib/supabase/admin.ts)
- Middleware mit getUser() auf jedem Request (nicht getSession())
- Ownership Checks in allen Server Actions vor Datenänderungen
- Rate Limiting mit Upstash Redis auf allen Auth-Endpunkten
Serien-Inhaltsverzeichnis
Diese Anleitung ist Teil unserer DevOps-Runbook-Serie für self-hosted App-Stacks.
- Supabase Self-Hosting Runbook
- Next.js über Supabase sicher betreiben - dieser Artikel
- Supabase Edge Functions sicher einsetzen
- Trigger.dev Background Jobs sicher betreiben
- Claude Code als Sicherheitskontrolle im DevOps-Workflow
- Security Baseline für den gesamten Stack
Artikel 1 beschreibt die Plattform-Basis mit allen Services und deren Sicherheitskonfiguration. Dieser Artikel beschreibt die App-Schicht darüber.
Architekturüberblick
Browser (Client)
|
| HTTPS
|
Next.js App Layer
|
+-- @supabase/ssr
|
+-- service_role client
(nur isolierte Admin-Kontexte)
|
Supabase Platform Layer
|
+-- Kong API Gateway
+-- GoTrue Auth
+-- PostgREST API
+-- Realtime
|
PostgreSQL Data Layer
|
+-- Row Level Security
Grundregeln:
Browser -> spricht nur mit Next.js
Next.js -> spricht mit Supabase
Supabase -> kontrolliert Zugriff über RLS
Wenn der Browser mehrere Backend-Services direkt anspricht, entstehen unkontrollierbare Sicherheitsgrenzen.
Teil A - Architekturentscheidungen
Diese Entscheidungen werden selten geändert und bilden das Fundament.
A1 - Next.js als eigenen Service betreiben
Umsetzung
Next.js läuft als eigener Container.
services
nextjs-app
supabase-stack
postgres
Beispiel docker-compose:
services:
nextjs-app:
build: ./app
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_SUPABASE_URL=http://kong:8000
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${ANON_KEY}
- SUPABASE_SERVICE_ROLE_KEY=${SERVICE_ROLE_KEY}
networks:
- internal
Next.js darf nicht innerhalb des Supabase-Stacks laufen. Eine Analyse von über 500 Next.js-Deployments zeigt, dass 23% aller produktiven Next.js-Instanzen mindestens eine serverseitige Umgebungsvariable im Client-Bundle exponieren (Snyk State of Open Source Security 2024).
Prüfbare Bedingung
docker ps --format '{{.Names}}'
Erwartung:
nextjs-app
supabase-kong
supabase-postgres
supabase-auth
Failure Scenario
Wenn Next.js im selben Container wie Supabase läuft:
- Prozessraum wird geteilt
- Secrets liegen im selben Environment
- ein kompromittierter Next.js Server hat direkten Zugriff auf alle Backend-Services
A2 - Browser spricht nur mit Next.js
Umsetzung
Der Browser darf nur eine öffentliche URL sehen.
https://app.example.com
Nicht erlaubt:
https://app.example.com:8000
https://app.example.com:5432
https://app.example.com:9000
Firewall-Beispiel:
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 8000 -j DROP
iptables -A INPUT -p tcp --dport 5432 -j DROP
iptables -A INPUT -p tcp --dport 9000 -j DROP
Prüfbare Bedingung
nmap -p 443,3000,5432,8000,9000 app.example.com
Erwartung:
443 open
alle anderen filtered
Failure Scenario
Wenn Supabase Studio öffentlich erreichbar ist:
- vollständiger Datenbankzugriff
- Schema-Manipulation
- Zugriff auf Storage Buckets
A3 - Security Headers setzen
Next.js fungiert als Gateway und muss HTTP-Security-Header setzen.
Umsetzung
next.config.js
const securityHeaders = [
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=()" },
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
{
key: "Content-Security-Policy",
value: "default-src 'self'; img-src 'self' data: blob:; frame-ancestors 'none'"
}
]
Prüfbare Bedingung
curl -I https://app.example.com
Erwartete Header:
X-Frame-Options
Content-Security-Policy
Strict-Transport-Security
Failure Scenario
Ohne CSP:
- XSS-Angriffe laden externe Skripte
- Token können exfiltriert werden
Teil B - Implementierungschecks
Diese Regeln gelten für jede Codeänderung.
B1 - Environment Variablen trennen
Übersicht der Umgebungsvariablen
| Variable | Sichtbarkeit | Erlaubter Ort | Risiko bei Leak |
|---|---|---|---|
| NEXT_PUBLIC_SUPABASE_URL | Client | .env, Code | Gering (öffentlich) |
| NEXT_PUBLIC_SUPABASE_ANON_KEY | Client | .env, Code | Mittel (RLS schützt) |
| SUPABASE_SERVICE_ROLE_KEY | Server-only | .env, lib/supabase/admin.ts | Kritisch (umgeht RLS) |
| DATABASE_URL | Server-only | .env | Kritisch (voller DB-Zugang) |
| TRIGGER_API_KEY | Server-only | .env | Hoch (Job-Ausführung) |
Client-sichtbar:
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
Server-only:
SUPABASE_SERVICE_ROLE_KEY
DATABASE_URL
TRIGGER_API_KEY
Prüfbare Bedingung
grep -r "NEXT_PUBLIC_" .env*
Secrets dürfen dort nicht vorkommen.
Build Leak Check
grep -r "SERVICE_ROLE" .next/
Erwartung:
keine Treffer
Failure Scenario
service_role im Client-Bundle bedeutet:
- vollständiger DB-Zugriff
- RLS komplett wirkungslos
Wer die Grundlagen der Secret-Verwaltung kennt, versteht, warum diese Trennung essenziell ist.
B2 - Supabase SSR Client korrekt konfigurieren
Server-Client:
import { createServerClient } from "@supabase/ssr"
Server verwendet anon key, nicht service_role.
Admin-Client:
createClient(url, SERVICE_ROLE_KEY)
Nur für administrative Aufgaben.
Prüfbare Bedingung
grep -rn "SERVICE_ROLE" app/
Erwartung: nur in
lib/supabase/admin.ts
Failure Scenario
Server-Client mit service_role:
- RLS wird komplett umgangen
- alle Requests haben Adminrechte
B3 - Middleware für Auth und Token Refresh
Middleware läuft vor jedem Request.
middleware.ts
Umsetzung
const { data: { user } } = await supabase.auth.getUser()
Nicht:
getSession()
Prüfbare Bedingung
grep getUser middleware.ts
Failure Scenario
Ohne Middleware:
- Token Refresh funktioniert nicht
- Session bricht nach 1h ab
B4 - Mutation Pattern
Alle Mutationen folgen dem gleichen Ablauf.
Auth
Input Validation
Ownership Check
Mutation
Logging
Beispiel
const { user } = await supabase.auth.getUser()
if (!user) return
const post = await supabase
.from("posts")
.select("user_id")
.eq("id", postId)
if (post.user_id !== user.id)
throw new Error("forbidden")
Failure Scenario
Ohne Ownership Check:
deletePost(id)
Ein Nutzer kann fremde Daten löschen.
B5 - Rate Limiting
Next.js hat kein integriertes Rate Limiting.
Empfohlene Lösung:
Upstash Redis
Beispiel
10 requests / minute / IP
Prüfbare Bedingung
for i in {1..20}
do curl -X POST /api/login
done
Erwartung:
HTTP 429
B6 - Logging ohne Secrets
Logs dürfen nicht enthalten:
JWT Tokens
service_role keys
emails
passwords
Prüfbare Bedingung
grep console.log app/
Teil C - Betrieb
C1 - Dependency Updates
Next.js Releases sind häufig.
Wöchentlich prüfen:
npm audit
npm outdated
Empfohlen:
Renovate / Dependabot
C2 - Claude Code Integration
Architektur:
Git Push
|
Deterministische Checks
|
Security Report
|
Claude Analyse
|
DevOps Entscheidung
Deterministische Checks
grep service_role
grep NEXT_PUBLIC
npm audit
Claude Analyse
Claude prüft:
- neue Server Actions
- neue API Routes
- Ownership Patterns
- Input Validation
- Architektur-Drift
Claude führt keine Änderungen auf Production aus.
Deployment-Checkliste
Vor jedem Deployment prüfen:
[ ] Next.js läuft als eigener Service
[ ] Supabase Ports extern geschlossen
[ ] Security Headers aktiv
[ ] kein service_role im Client Bundle
[ ] service_role nur im Admin Client
[ ] middleware.ts vorhanden
[ ] Server Actions prüfen Auth
[ ] Ownership Checks implementiert
[ ] Login Rate Limit aktiv
[ ] npm audit ohne critical findings
Fazit
Next.js ist im modernen Stack kein Frontend, sondern eine privilegierte Server-Schicht.
Sicherheit entsteht durch drei Ebenen:
Architektur
Implementierungschecks
laufende Audits
Die Kombination aus CI Security Checks und Claude Code Analyse erkennt sowohl bekannte Muster als auch neue Risiken.
Wer diese Prinzipien zusammen mit einer Cert-Ready-by-Design-Architektur verfolgt, baut prüfbare Sicherheit statt nachträglicher Audits.
Audit-Checkliste als Download
Vorbereiteter Prompt für Claude Code. Laden Sie die Datei auf Ihren Server und starten Sie Claude Code im Projektverzeichnis Ihrer Next.js-Anwendung. Claude Code prüft automatisch alle Sicherheitspunkte aus diesem Runbook und meldet BESTANDEN, WARNUNG oder KRITISCH.
claude -p "$(cat claude-check-artikel-2-nextjs.md)" --allowedTools Read,Grep,Glob,Bash
Checkliste herunterladenSerien-Inhaltsverzeichnis
- Supabase Self-Hosting Runbook
- Next.js über Supabase sicher betreiben - dieser Artikel
- Supabase Edge Functions sicher einsetzen
- Trigger.dev Background Jobs sicher betreiben
- Claude Code als Sicherheitskontrolle im DevOps-Workflow
- Security Baseline für den gesamten Stack
Der nächste Artikel beschreibt, wie Supabase Edge Functions als Integrationspunkte sicher eingesetzt werden - ohne eine zweite Backend-Architektur zu bauen.

Bert Gogolin
Geschäftsführer, Gosign
AI Governance Briefing
Enterprise AI, Regulierung und Infrastruktur - einmal im Monat, direkt von mir.