8.7 KiB
Deployment: Staging (Vercel)
Kumasy läuft als Staging-Umgebung auf Vercel, getriggert über den dev-Branch. Dieses Dokument beschreibt den aktuellen Aufbau, damit er reproduzierbar ist und man bei Fehlern schnell die Ursache findet.
Kein Docker: die App ist ein reines Next.js 16 (App Router) Full-Stack-Projekt ohne separaten Server/WebSockets — alles läuft als Vercel Serverless/Edge Functions. Ein eigenes Image würde hier nur Komplexität ohne Nutzen hinzufügen.
Architektur
- Hosting: Vercel, Git-Integration mit
Since-3/Kuma. Push aufdev→ automatisches Preview-Deployment (= Staging).mainist als Production-Branch gesetzt, aber Production ist bislang nicht eingerichtet (kein eigenes Supabase-Projekt, keine Production-Env-Vars) — dieses Dokument behandelt nur Staging. - Staging-URL (fest, an
devgepinnt):https://kumasy-git-dev-kumasy.vercel.app - Region: Vercel Functions laufen in
fra1(Frankfurt) — bewusst umgestellt, weil die Default-Region (Cleveland/US East) wegen der Entfernung zur Supabase-DB ineu-central-1spürbar langsam war. - Datenbank/Auth: eigenes, separates Supabase-Projekt für Staging (Projekt-Ref
pyxjpsalchmnlyqfnzfw), nicht dasselbe wie Produktion. Postgres + Supabase Auth (E-Mail/Passwort + Google OAuth). - Storage: Supabase Storage, Bucket
avatars(public) — wird für Profilbilder und Kursbilder verwendet. - Zahlungen: Stripe, Test-Mode. Eigener Webhook-Endpoint für die Staging-URL (Webhook-Secrets sind pro Endpoint eindeutig, können nicht mit Produktion geteilt werden).
- E-Mail: Resend, gleicher API-Key wie lokal/Produktion (kein Test/Live-Split wie bei Stripe).
Environment-Variablen
Alle in Vercel unter Project Settings → Environment Variables, Scope "Preview" (niemals "Production" — das hält Staging- und Produktions-Secrets sauber getrennt).
| Variable | Quelle |
|---|---|
DATABASE_URL |
Supabase Staging, Transaction Pooler, Port 6543, ?pgbouncer=true&connection_limit=1 |
DIRECT_URL |
Supabase Staging, Session Pooler (nicht Direct Connection!), Port 5432 — siehe Stolperfalle unten |
NEXT_PUBLIC_SUPABASE_URL |
Supabase Staging Projekt-URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase Staging anon key |
SUPABASE_SERVICE_ROLE_KEY |
Supabase Staging service_role key |
RESEND_API_KEY / RESEND_FROM / SMTP_TEST_MAIL |
wiederverwendet von lokal |
STRIPE_SECRET_KEY / NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
Test-Mode-Keys, wiederverwendet von lokal |
STRIPE_WEBHOOK_SECRET |
eigener Wert, siehe Stripe-Webhook unten |
STRIPE_PLATFORM_FEE_PERCENT |
gleicher Wert wie lokal |
NEXT_PUBLIC_SITE_URL / NEXT_PUBLIC_APP_URL |
https://kumasy-git-dev-kumasy.vercel.app |
COMPANY_NAME |
"Kumasy" |
Lokales Pendant für einmalige DB-Operationen gegen Staging (z.B. neue Migrationen einspielen): .env.staging im Repo-Root, gitignored (.env* steht in .gitignore), wird nie committed.
Build Command (Vercel Project Settings → Build & Development Settings)
npx prisma generate && npx prisma migrate deploy && next build
package.json hat kein postinstall für Prisma — ohne diesen Override würde der Build mit fehlendem/veraltetem Prisma Client scheitern, und neue Migrationen würden nie automatisch angewendet.
Bekannte Stolperfallen (aufgetreten während des Erstaufbaus)
prisma.config.ts+ Vercel = Crash beim Config-Laden. Prisma 6.19'senv()-Helper wirft synchron beim Laden der Config, wenn die Variable zu dem Zeitpunkt nicht sichtbar ist (bekannter Prisma-Bug, siehe prisma/prisma#28607). Fix: inprisma.config.tsprocess.env.DATABASE_URL!stattenv("DATABASE_URL")verwenden.- Supabase Direct Connection ist IPv6-only.
db.<project-ref>.supabase.co:5432(die klassische "Direct Connection") funktioniert nicht von Vercels Build-Containern aus (P1001: Can't reach database server), weil dort kein IPv6 verfügbar ist. FürDIRECT_URLstattdessen den Session Pooler verwenden:aws-0-eu-central-1.pooler.supabase.com:5432(gleicher Host wie der Transaction Pooler, nur Port 5432 statt 6543, ohnepgbouncer=true). .env.examplehat einen Fehler: zeigtDIRECT_URLmit Port 6543, obwohl der Kommentar "für Migrationen" sagt. Der Pooler/Direct-Unterschied ist der Port, nicht nur der Host — siehe Punkt 2 für den korrekten Wert.- Schema-Drift zwischen
schema.prismaund den committeten Migrationen. Mehrere Felder (Employee.permissions/roles/locations/onboardingToken,Business.stripeAccountId/slug,CourseBooking-Zahlungsfelder,GuestLead-Tabelle) existierten im Schema, aber in keiner Migrationsdatei — offenbar wurden sie irgendwann perprisma db pushdirekt an einer DB angelegt, ohne die Migration zu committen. Behoben durchprisma migrate diff --from-url $DIRECT_URL --to-schema-datamodel prisma/schema.prisma --script, das Ergebnis als neue Migration (prisma/migrations/20260729161133_sync_schema_drift/) committed. Wichtig für Produktion: dort existieren dieselben Spalten vermutlich schon (perdb push) — diese Migration darf dort nicht ausgeführt, sondern muss mitprisma migrate resolve --applied 20260729161133_sync_schema_driftals bereits angewendet markiert werden, sonst schlägtmigrate deploymit "column already exists" fehl. - Next.js Image Optimization braucht die Storage-Domain explizit.
next.config.ts→images.remotePatternsmuss den Hostnamen des jeweiligen Supabase-Projekts enthalten. Staging-Domain wurde ergänzt, Produktions-Domain bleibt zusätzlich bestehen (dieselbe Datei bedient später auch Production). - Storage-Buckets werden nicht mitkopiert. Ein neues Supabase-Projekt hat keine Buckets —
avatars(public) musste manuell im Staging-Projekt angelegt werden, sonst schlagen Uploads mit "Bucket not found" fehl.
Reihenfolge, falls das Setup je neu aufgebaut werden muss
- Neues Supabase-Projekt anlegen, Connection-Strings/Keys notieren (Pooler für
DATABASE_URL, Session Pooler fürDIRECT_URL). - Migrationen einmalig lokal einspielen:
npx prisma migrate deploymit.env.staging. - Storage-Bucket
avatars(public) im neuen Projekt anlegen. - Vercel-Projekt importieren, Branch-Domain auf
devpinnen (Project Settings → Domains). - Region auf
fra1stellen (Project Settings → Functions). - Stripe-Test-Webhook auf die Staging-Domain anlegen, Signing Secret notieren.
- Alle Env-Vars in Vercel eintragen (Scope: Preview), Build Command überschreiben.
- Ersten Deploy auslösen, danach Google OAuth in Supabase (Site URL, Redirect URLs) auf die bestätigte Staging-Domain konfigurieren.
next.config.tsum die neue Storage-Domain ergänzen, committen.
Verifikations-Checkliste
- Homepage lädt, Bilder rendern (dicebear, Supabase Storage, Google-Avatare)
- Öffentliche Listing-Seite zeigt echte Daten aus der Staging-DB
- E-Mail/Passwort-Registrierung funktioniert
- Google-Login funktioniert Ende-zu-Ende
- Schreibvorgang (z.B. Kurs anlegen) funktioniert
- Bild-Upload rendert korrekt
- Stripe-Checkout mit Testkarte
4242 4242 4242 4242— noch offen, hängt an Stripe Connect Onboarding - Stripe-Webhook zeigt "delivered",
CourseBooking.paymentStatuswechselt auf"paid"— noch offen - Buchungsbestätigungs-Mail kommt tatsächlich an (Resend) — noch offen
- Stripe Connect Onboarding — noch offen
- Beide Sprachen (de/en) spot-gecheckt