# Plan: App Base URL — configurable via env, no localhost fallback in production Stand: 2026-04-02 --- ## Anforderungsanalyse Email-Links (Invite, Password Reset) werden aktuell mit `process.env["NEXTAUTH_URL"] ?? "http://localhost:3100"` gebaut. Das ist korrekt, wenn `NEXTAUTH_URL` gesetzt ist. Das Problem: wenn der Wert fehlt oder leer ist, enthalten Produktions-E-Mails localhost-Links. Außerdem gibt es keine `.env.example`, die dokumentiert, welche Variablen gesetzt werden müssen. **Was gebaut wird:** 1. Zentrale `getAppBaseUrl()` Funktion in `packages/api` — liest `NEXTAUTH_URL`, wirft in production einen Fehler wenn nicht gesetzt, fällt in dev auf localhost zurück. 2. Beide Router (`invite.ts`, `auth.ts`) verwenden diese Funktion statt duplizierter Inline-Logik. 3. `.env.example` mit allen benötigten Variablen. 4. Health-Route zeigt ob `NEXTAUTH_URL` konfiguriert ist. **Betroffene Pakete:** `packages/api`, `apps/web` **Audit-Ergebnis — intentionale localhost-Referenzen (NICHT ändern):** - `apps/web/e2e/dev-system/` — alle E2E-Helfer, Specs, global-setup.ts - `apps/web/playwright.dev.config.ts` - `apps/web/src/middleware.test.ts` - `.github/workflows/deploy-test.yml` --- ## Betroffene Pakete & Dateien | Paket | Datei | Art | |-------|-------|-----| | `packages/api` | `src/lib/app-base-url.ts` | create | | `packages/api` | `src/router/invite.ts` | edit | | `packages/api` | `src/router/auth.ts` | edit | | `apps/web` | `src/app/api/health/route.ts` | edit | | root | `.env.example` | create | --- ## Task-Liste - [ ] **Task 1:** `getAppBaseUrl()` in `packages/api/src/lib/app-base-url.ts` erstellen. - Liest `process.env["NEXTAUTH_URL"]` (trimmed). - Wenn gesetzt und nicht leer → gibt den Wert zurück (trailing slash entfernen). - Wenn leer/fehlend **und** `NODE_ENV === "production"` → wirft `Error("NEXTAUTH_URL must be set in production — email links will be broken")`. - Sonst (development/test) → gibt `"http://localhost:3100"` zurück und loggt einmalig eine Warnung. - → Datei: `packages/api/src/lib/app-base-url.ts` - [ ] **Task 2:** `invite.ts` auf `getAppBaseUrl()` umstellen. - Ersetze `const baseUrl = process.env["NEXTAUTH_URL"] ?? "http://localhost:3100";` durch `const baseUrl = getAppBaseUrl();` - → Datei: `packages/api/src/router/invite.ts` Zeile 53 - [ ] **Task 3:** `auth.ts` auf `getAppBaseUrl()` umstellen. - Ersetze `const baseUrl = process.env["NEXTAUTH_URL"] ?? "http://localhost:3100";` durch `const baseUrl = getAppBaseUrl();` - → Datei: `packages/api/src/router/auth.ts` Zeile 50 - [ ] **Task 4:** `.env.example` anlegen mit allen required und optionalen Variablen. - Sections: App, Auth, Database, Redis, SMTP, AI, Dev-Tools (pgAdmin) - Jede Variable: Kommentar (required/optional, Beschreibung), Beispielwert. - `NEXTAUTH_URL` als REQUIRED mit Hinweis "must be the public URL (e.g. https://capakraken.example.com) — used in email links; do not use localhost in production" - → Datei: `.env.example` - [ ] **Task 5:** Health-Route um `baseUrl`-Check erweitern. - Liest `NEXTAUTH_URL` direkt (kein `getAppBaseUrl()` — soll nie werfen, nur reporten). - Fügt `"baseUrl": { "configured": bool, "isLocalhost": bool }` zum JSON-Response hinzu. - `configured: false` wenn Var fehlt/leer; `isLocalhost: true` wenn Wert mit `http://localhost` beginnt. - → Datei: `apps/web/src/app/api/health/route.ts` --- ## Abhängigkeiten - Task 2 **und** Task 3 benötigen Task 1 (Funktion muss existieren). - Task 2 und Task 3 können **parallel** ausgeführt werden (unterschiedliche Dateien). - Task 4 und Task 5 sind **unabhängig** von allen anderen und können parallel ausgeführt werden. --- ## Akzeptanzkriterien - [ ] `pnpm test:unit` läuft grün - [ ] `pnpm --filter @capakraken/web exec tsc --noEmit` — keine neuen TS-Errors - [ ] `pnpm test:e2e:email` — alle 3 E2E-Tests bestehen weiterhin - [ ] Wenn `NEXTAUTH_URL=https://capakraken.hartmut-noerenberg.com`: E-Mail-Links enthalten diese Domain - [ ] Wenn `NEXTAUTH_URL` fehlt und `NODE_ENV=production`: `createInvite` / `requestPasswordReset` werfen einen klaren Fehler beim Link-Bau - [ ] `GET /api/health` liefert `baseUrl.configured: true/false` und `baseUrl.isLocalhost: bool` - [ ] `.env.example` existiert und dokumentiert alle Pflichtfelder --- ## Risiken & offene Fragen - **Unit-Tests für Router:** Bestehende Tests für `invite.ts` und `auth.ts` müssen `NEXTAUTH_URL` in der Testumgebung gesetzt haben (oder `NODE_ENV=test` → Dev-Fallback greift). Vorhandene Tests prüfen, ob `process.env["NEXTAUTH_URL"]` dort gesetzt wird. - **`docker-compose.prod.yml`** delegiert ENV-Vars an `.env.production` (gitignored). Das `.env.example` deckt ab, was dort stehen muss — kein Code-Change nötig. - **Trailing Slash:** NEXTAUTH_URL könnte mit oder ohne `/` enden. `getAppBaseUrl()` sollte trailing slashes normalisieren.