Kuma/DEPLOYMENT.md

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 auf dev → automatisches Preview-Deployment (= Staging). main ist 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 dev gepinnt): 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 in eu-central-1 spü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)

  1. prisma.config.ts + Vercel = Crash beim Config-Laden. Prisma 6.19's env()-Helper wirft synchron beim Laden der Config, wenn die Variable zu dem Zeitpunkt nicht sichtbar ist (bekannter Prisma-Bug, siehe prisma/prisma#28607). Fix: in prisma.config.ts process.env.DATABASE_URL! statt env("DATABASE_URL") verwenden.
  2. 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ür DIRECT_URL stattdessen den Session Pooler verwenden: aws-0-eu-central-1.pooler.supabase.com:5432 (gleicher Host wie der Transaction Pooler, nur Port 5432 statt 6543, ohne pgbouncer=true).
  3. .env.example hat einen Fehler: zeigt DIRECT_URL mit 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.
  4. Schema-Drift zwischen schema.prisma und 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 per prisma db push direkt an einer DB angelegt, ohne die Migration zu committen. Behoben durch prisma 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 (per db push) — diese Migration darf dort nicht ausgeführt, sondern muss mit prisma migrate resolve --applied 20260729161133_sync_schema_drift als bereits angewendet markiert werden, sonst schlägt migrate deploy mit "column already exists" fehl.
  5. Next.js Image Optimization braucht die Storage-Domain explizit. next.config.ts → images.remotePatterns muss 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).
  6. 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

  1. Neues Supabase-Projekt anlegen, Connection-Strings/Keys notieren (Pooler für DATABASE_URL, Session Pooler für DIRECT_URL).
  2. Migrationen einmalig lokal einspielen: npx prisma migrate deploy mit .env.staging.
  3. Storage-Bucket avatars (public) im neuen Projekt anlegen.
  4. Vercel-Projekt importieren, Branch-Domain auf dev pinnen (Project Settings → Domains).
  5. Region auf fra1 stellen (Project Settings → Functions).
  6. Stripe-Test-Webhook auf die Staging-Domain anlegen, Signing Secret notieren.
  7. Alle Env-Vars in Vercel eintragen (Scope: Preview), Build Command überschreiben.
  8. Ersten Deploy auslösen, danach Google OAuth in Supabase (Site URL, Redirect URLs) auf die bestätigte Staging-Domain konfigurieren.
  9. next.config.ts um 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.paymentStatus wechselt 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