Skip to content
··8 Min. Lesezeit

Leere Bildschirme zerstören Vertrauen. Ich hatte 31 davon.

Ich fand 31 leere Bildschirme in meinem SaaS – alle weil ich vergessen hatte, dass Multi-Tenancy nicht nur um Datenzugriff geht, sondern um URL-Kontext. Hier ist, wie Claude Code mir half, alle in einer Nacht zu beheben.

  1. November, 8:47 Uhr. Ich bekam die E-Mail.

„Hey Chandler – ich habe auf ‚Ansehen' geklickt und eine leere Seite bekommen. Habe ich alle meine Interview-Daten verloren?"

Mein Magen sackte ab. Leere Seiten sind keine Bugs. Sie sind Vertrauenskiller.

Ich öffnete die Dev Console. Prüfte die Route. Agentur-Nutzer, sieht Kunde „Acme Corp", klickt auf einen Button, und... die URL wechselte von /clients/acme-corp/agents/agent-name/results/123 zu nur /agent-name/results/123.

Verlor den Kundenkontext. React Router konnte die Route nicht finden. Leerer Bildschirm.

„Okay, das ist ein Bug", dachte ich. „Ich behebe ihn und mache weiter."

Ich ließ Claude Code die Codebasis nach ähnlichen Mustern analysieren und widmete mich dann meinem Samstag mit der Familie. Mittagessen. Besorgungen. Kinder. Das Übliche.

Am späten Nachmittag schaute ich wieder rein. Claude Code hatte das Muster gefunden – und es war nicht schön.

Um 19:42 Uhr hatte ich 14 Bugs behoben. Um 19:48 Uhr fand ich 8 weitere. Um 19:56 Uhr lag die Zahl bei 31 insgesamt.

Alle gleiche Ursache. Alle gleicher Fix. Alle, weil ich beim Aufbau von Multi-Tenancy eine Sache vergessen hatte: Navigation geht nicht nur um Daten, sondern um Kontext.

---

Wie schlimm war es wirklich?

Ich möchte ehrlich sein, was diese 31 Bugs eigentlich bedeuteten:

Für Nutzer:

- Agentur-Nutzer trafen auf leere Bildschirme (sah kaputt aus, nicht buggy)

- Verloren den Workflow-Fortschritt mitten in der Sitzung

- „Ist diese Plattform stabil genug für unsere Kunden?"

- Jeder leere Bildschirm = ein Schritt näher daran, den Test zu kündigen

Für mich:

- 5+ Bug-Meldungen pro Tag (alle navigationsbedingt)

- 2-3 Stunden Debugging für jeden einzeln

- Konnte keine neuen Features shippen (zu sehr mit Feuerlöschen beschäftigt)

- Echte Angst: „Was, wenn ich etwas anderes kaputt gemacht habe und es noch nicht weiß?"

Die existenzielle Frage: Wenn ich nicht mal Navigation zum Laufen bringen kann, warum sollte jemand STRAŦUM mit der Marketingstrategie seiner Kunden vertrauen?

Leere Bildschirme zerstören Vertrauen schneller als alles andere.

---

Der Bug, der alles startete

Lass mich dir genau zeigen, was passierte.

Nutzer-Flow (was hätte funktionieren sollen):

1. Gehe zu /clients/acme-corp/agents/analysis

2. Klicke auf „Session starten"

3. Analyse abschließen

4. Klicke auf „Ergebnisse ansehen"

5. Ergebnisse sehen unter /clients/acme-corp/agents/analysis/results/123

Was tatsächlich passierte:

1. ✅ /clients/acme-corp/agents/analysis (gut)

2. ✅ Session starten (funktioniert)

3. ✅ Analyse abschließen (Daten gespeichert)

4. ❌ **Klick auf „Ergebnisse ansehen" → LEERE SEITE**

5. ❌ URL wechselte zu /analysis/results/123 (Kundenkontext verloren)

React Router suchte nach einer Route unter /analysis/results/123. Existierte nicht für Agentur-Nutzer. Renderte nichts.

Nutzer sieht: Leerer weißer Bildschirm. Keine Fehlermeldung. Kein Ladespinner. Nur... nichts.

---

Was ich fand, als ich (eigentlich Claude Code) anfing zu graben

So sah der fehlerhafte Code aus:

```typescript
// AgentPage.tsx (BEREINIGT - das fehlerhafte Muster)
import \{ useParams, useNavigate \} from 'react-router-dom';

export function AgentPage() \{
  const { clientSlug \} = useParams<\{ clientSlug: string \}>();
  const navigate = useNavigate();

  const handleViewResults = (sessionId: string) => \{
    // Problem: Hardcodierte Route, kein Kundenkontext
    navigate(`/agent-name/results/${sessionId\}`);
  };

  return (
    // ... Komponente
  );
}
```

Siehst du das Problem?

Ich extrahierte clientSlug aus der URL. Ich verwendete ihn zum Abrufen von Daten. Aber beim Navigieren vergaß ich ihn komplett.

Agentur-Routen sehen so aus: /clients/acme-corp/agents/agent-name

SME-Routen sehen so aus: /agent-name

Ich codierte das SME-Muster fest. Agentur-Nutzer = kaputt.

---

Die Erkenntnis: Ich hatte 31 davon

14:15 Uhr. Ich behob den ersten Bug. Commit. Fühlte mich gut.

15:42 Uhr. Fand einen weiteren in einem anderen Agenten. Gleiches Muster. Behoben.

16:18 Uhr. Noch ein Agent. Dasselbe.

17:30 Uhr. Ich hielt inne und starrte 10 Minuten lang auf meinen Bildschirm.

Jede Agent-Seite hatte denselben Bug. Jede verschachtelte Komponente, die navigierte. Jedes gemeinsam genutzte UI-Element mit einem „Gehe zu..."-Button.

Ich konnte sie einzeln beheben und 2 Tage verbringen. Oder ich konnte das Muster finden und alle systematisch beheben.

Ich entschied mich für systematisch.

---

Der Fix: Kontextbewusste Navigation

Anstatt useParams() in jeder Komponente bat ich Claude Code, einen Context Provider zu erstellen:

```typescript
// contexts/ClientContext.tsx
import \{ createContext, useContext \} from 'react';
import \{ useParams \} from 'react-router-dom';

interface ClientContextValue \{
  clientSlug: string | null;
\}

const ClientContext = createContext<ClientContextValue | null>(null);

export function ClientContextProvider(\{ children \}: \{ children: React.ReactNode \}) \{
  // clientSlug EINMAL auf Layout-Ebene extrahieren
  const { clientSlug \} = useParams<\{ clientSlug: string \}>();

  return (
    <ClientContext.Provider value=\{{ clientSlug: clientSlug || null \}}>
      \{children\}
    </ClientContext.Provider>
  );
}

export function useClientContext() \{
  const context = useContext(ClientContext);
  if (!context) {
    throw new Error('useClientContext must be used within ClientContextProvider');
  \}
  return context;
}
```

Dann umhüllte ich die Agentur-Routen:

```typescript
// App.tsx
<Route path="/clients/:clientSlug/*" element={
  <ClientContextProvider>
    <ClientLayout />
  </ClientContextProvider>
}>
  <Route path="agents/analysis" element={<AnalysisAgent />} />
  <Route path="agents/strategy" element={<StrategyAgent />} />
  \{/* ... alle client-bezogenen Routen */\}
</Route>
```

Jetzt hat jede Komponente innerhalb Zugriff auf clientSlug über Kontext, nicht über Params.

Der Routing-Helfer (weil dasselbe if/else 20 Mal tippen langweilig wird):

```typescript
// hooks/useContextRoute.ts
import \{ useClientContext \} from '@/contexts/ClientContext';

export function useContextRoute() \{
  const { clientSlug \} = useClientContext();

  const buildRoute = (route: string) => \{
    if (clientSlug) {
      // Agentur-Route: /clients/acme-corp/agents/...
      const cleanRoute = route.startsWith('/') ? route.slice(1) : route;
      return `/clients/${clientSlug\}/$\{cleanRoute\}`;
    }
    // SME-Route: /agent-name/...
    return route;
  };

  return \{ buildRoute, clientSlug \};
}
```

Verwendung (so viel sauberer):

```typescript
// AgentPage.tsx (BEREINIGT - das behobene Muster)
import \{ useClientContext \} from '@/contexts/ClientContext';
import \{ useNavigate \} from 'react-router-dom';
import \{ useContextRoute \} from '@/hooks/useContextRoute';

export function AgentPage() \{
  const { clientSlug \} = useClientContext();
  const \{ buildRoute \} = useContextRoute();
  const navigate = useNavigate();

  const handleViewResults = (sessionId: string) => \{
    // Funktioniert für SOWOHL SME- als auch Agentur-Nutzer
    navigate(buildRoute(`agents/analysis/results/${sessionId\}`));
  };

  return (
    // ... Komponente
  );
}
```

Eine Hilfsfunktion. Kontextbewusst. Funktioniert für beide Nutzertypen.

---

Der systematische Fix: Ein Tag, 31 Dateien

Sobald ich das Muster hatte, wurde es mechanisch.

1. November 2025 – Der Sprint

Nachmittag (14-19 Uhr) – Finden und kategorisieren:

- Muster über alle Agent-Seiten entdeckt

- Context Provider und Routing-Helfer aufgebaut

- Ansatz am ersten Agenten getestet

Abend (19-20 Uhr) – Der systematische Fix:

19:42 Uhr – Erste Welle (14 Bugs):

```
fix(multi-tenant): fix 14 navigation bugs and refactor all agents to useClientContext()

Frontend Changes (8 files):
- Refactored 6 agents to use useClientContext() hook
- Fixed 14 navigation bugs that lost client context across multiple agents

Backend Changes (5 files):
- Added client_id parameter to all save operations
- Updated base classes to extract client_id properly

Files changed: 14
Insertions: +732
Deletions: -164
```

19:48 Uhr – Zweite Welle (8 weitere Bugs):

```
fix(multi-tenant): fix 8 navigation bugs in strategy page

- Fixed 8 navigation buttons that lost client context
- Total bugs fixed: 22 (14 + 8)

Files changed: 1
Insertions: +71
Deletions: -23
```

19:56 Uhr – Letzte Welle (9 weitere Bugs):

```
fix(multi-tenant): fix 9 navigation bugs in interview and tool pages

- Fixed remaining navigation issues in nested components
- Total bugs fixed: 31 bugs across entire application

Files changed: 2
Insertions: +46
Deletions: -10
```

Ich ging jeden Agenten-Flow durch. SME-Nutzer. Agentur-Nutzer. Jeden Button geklickt. Jeden Link. Keine leeren Seiten.

Gesamtschaden: 17 Dateien geändert, 849 Einfügungen, 197 Löschungen.

Investierte Zeit: Etwa 6 Stunden nicht-kontinuierlicher Arbeit mit Claude Code (14 Uhr Entdeckung → 20 Uhr letzter Commit).

Gesparte Zeit: Wahrscheinlich 30+ Stunden einzelner Bugfixes und Nutzersupport.

---

Was ich gelernt habe (auf die harte Tour)

1. Multi-Tenant-Navigation ist schwieriger als Datenisolation

Daten nach org\_id zu filtern kannst du. Das ist der einfache Teil.

Aber Navigation? Du hast zwei gültige URL-Strukturen für dieselbe Funktion:

```
SME:    /agent-name/session/123
Agentur: /clients/acme-corp/agents/agent-name/session/123
```

Jeder navigate()-Aufruf muss wissen, welches Muster zu verwenden ist. Einmal falsch, und Nutzer sehen leere Seiten.

2. useParams() belügt dich

```typescript
const \{ clientSlug \} = useParams();
```

Das funktioniert... bis die Route wechselt. Dann wird clientSlug zu undefined, und deine nächste Navigation bricht.

React Context lügt nicht. Er ist immer verfügbar, immer konsistent.

3. Jeden Bug zählen bevor du den Sieg verkündest

Ich dachte, ich hätte 14 Bugs. Dann fand ich 8 weitere. Dann noch 9.

Lektion: Grepfe deine gesamte Codebasis, nicht nur die Dateien, von denen du denkst, dass sie Bugs haben.

4. Wenn du denselben Bug zweimal findest, halte inne und erstelle ein Muster

Einzelne Behebung: 31 Bugs = wahrscheinlich eine Woche

Systematische Behebung: Muster finden → Helfer erstellen → Alle Instanzen beheben = 6 Stunden

Die 10 Minuten, die ich um 17:30 Uhr auf meinen Bildschirm starrte, sparten mir Tage.

5. Navigations-Bugs sind existenzielle Bedrohungen

Wir besessen über State Management, Datenabruf, API-Optimierung.

Aber kaputte Navigation = leere Bildschirme = „Diese Plattform ist kaputt."

Nutzer interessiert sich nicht für deine RLS-Richtlinien oder deine Multi-Tenant-Architektur. Sie interessiert sich dafür, dass „Ergebnisse ansehen" Ergebnisse anzeigt. Diese Lücke zwischen „technisch funktioniert" und „wirklich fertig" tauchte wieder auf, als ich eine native iOS-App mit KI baute – Claude Code generierte das Gerüst schnell, aber der Schliff, der Nutzer zum Vertrauen bringt? Das sind die 40%, die nur Menschen liefern können.

---

Das Muster (für deine Multi-Tenant-App)

Wenn du Multi-Tenant-SaaS mit hierarchischen URLs baust:

✅ Schritt 1: Context Provider auf Layout-Ebene erstellen

```typescript
<ClientContextProvider>
  \{/* Alle tenant-bezogenen Routen */\}
</ClientContextProvider>
```

✅ Schritt 2: Routing-Helfer aufbauen

```typescript
const \{ buildRoute \} = useContextRoute();
navigate(buildRoute('agents/analysis/session/123'));
```

✅ Schritt 3: Routen niemals hardcoden

```typescript
// ❌ SCHLECHT
navigate('/analysis/session/123');

// ✅ GUT
navigate(buildRoute('agents/analysis/session/123'));
```

✅ Schritt 4: Alle Navigationsaufrufe greppen

```bash
# Jeden navigate()-Aufruf finden
grep -r "navigate(" src/ > navigation_audit.txt

# Hardcodierte Routen finden
grep -r "navigate('/" src/ | grep -v "buildRoute"
```

✅ Schritt 5: Mit allen Nutzertypen testen

SME-Flow. Agentur-Flow. Jeden Button. Jeden Link. Keine leeren Seiten.

---

Die Ergebnisse

Vor dem 1. November:

- 31 Navigations-Bugs lauern

- 5+ Bug-Meldungen pro Tag

- Agentur-Nutzer bezweifeln Plattform-Stabilität

- Ich: Zu verängstigt, neue Features zu shippen

Nach dem 1. November:

- 0 Navigations-Bugs

- 0 navigationsbezogene Support-Tickets

- Muster für zukünftige Entwicklung etabliert

- Ich: Zuversichtlich beim Shippen neuer Agenten-Routen

Entwicklungsgeschwindigkeit:

- Neue Routen hinzufügen: 5 Minuten (war 30 Minuten + „wird das kaputt gehen?")

- Bug-Meldungen: 0 (waren 5+ pro Tag)

- Nutzervertrauen: Wiederhergestellt („Plattform fühlt sich jetzt stabil an")

Die 6 Stunden, die ich für die Behebung dieser Bugs aufwandte, zahlten sich 10-fach in reduzierter Support-Last und wiederhergestelltem Nutzervertrauen aus.

---

Warum ich das teile

Multi-Tenant-Navigation ist nicht sexy. Niemand feiert „31 Bugs in 6 Stunden behoben" so wie „neues Feature geshipped".

Aber wenn du Multi-Tenant-SaaS baust wie ich mit https://stratum.chandlernguyen.com/ – einer KI-Marketing-Plattform für Agenturen – wirst du darauf stoßen. Vielleicht nicht 31 Bugs. Vielleicht nur 5. Aber du wirst darauf stoßen.

Wenn das passiert, denk daran:

1. Kontext > Params für den Navigations-State

2. Systematisch > Einzeln beim Beheben

3. Grepfe deine gesamte Codebasis (du wirst mehr finden als du denkst)

4. Mit allen Nutzertypen testen bevor du Sieg verkündest

Und wenn du dich fragst, wo dein Kontext hingegangen ist, schau auf einen leeren Bildschirm und weißt, dass ich in derselben Situation war. :)

Was ist der schmerzhafteste Bug, den du an echte Nutzer geshippt hast – der, den du nur durch eine Meldung entdeckt hast? Ich würde das wirklich gerne hören.

Viele Grüße,

Chandler

Die STRAŦUM-Architektur-Serie: Diese Navigationskrise war Teil einer größeren Multi-Tenancy-Reise. Sie begann mit dem Aufbau von Multi-Tenancy an Tag 2, eskalierte als ich das gesamte Schema an Tag 67 neu bauen musste, und endete damit, dass ich herausfand, meine Datenbank war korrekt aber 296x zu langsam.

---

*Programmiere weiter, lerne weiter, finde Bugs weiterhin in Gruppen von 31.*

Alpha-Zugang beantragen unter https://stratum.chandlernguyen.com/request-invitation

---

P.S. – Der Nutzer, der den ersten Bug meldete? Er verlor seine Interview-Daten nicht. Sie waren in der Datenbank gespeichert. Er konnte sie nur wegen einer kaputten Route nicht sehen. Als ich das um 19:42 Uhr behob, war seine gesamte Arbeit noch da. Diese kleine Erleichterung machte die 6 Stunden wert.

---

Weiterlesen

Mein Weg
Vernetzen
Sprache
Einstellungen