·

Blank Screens Kill Trust. I Had 31 of Them.

November 1st, 8:47 AM. I got the email.

"Hey Chandler - I clicked 'View' and got a blank page. Did I lose all my interview data?"

My stomach dropped. Blank pages aren't bugs. They're trust killers.

I opened the dev console. Checked the route. Agency user, viewing client "Acme Corp", clicked a button, and... the URL changed from `/clients/acme-corp/agents/agent-name/results/123` to just `/agent-name/results/123`.

Lost the client context. React Router couldn't find the route. Blank screen.

"Okay, that's one bug," I thought. "I'll fix it and move on."

I set up Claude Code to analyze the codebase for similar patterns, then went about my Saturday with the family. Lunch. Errands. Kids. The usual.

By late afternoon, I checked back in. Claude Code had found the pattern—and it wasn't pretty.

By 7:42 PM, I'd fixed 14 bugs. By 7:48 PM, found 8 more. By 7:56 PM, the count hit 31 total.

All the same root cause. All the same fix. All because I forgot one thing when I built multi-tenancy: navigation isn't just about data, it's about context.

---

How Bad Was It, Really?

Let me be honest about what these 31 bugs actually meant:

For users:

- Agency users hitting blank screens (looked broken, not buggy)

- Lost workflow progress mid-session

- "Is this platform stable enough for our clients?"

- Every blank screen = one step closer to canceling trial

For me:

- 5+ bug reports per day (all navigation-related)

- 2-3 hours debugging each one individually

- Couldn't ship new features (too busy firefighting)

- Genuine fear: "What if I broke something else and don't know it yet?"

The existential question: If I can't even get navigation working, why should anyone trust STRAŦUM with their clients' marketing strategy?

Blank screens kill trust faster than anything.

---

The Bug That Started It All

Let me show you exactly what happened.

User flow (what should've worked):

1. Go to `/clients/acme-corp/agents/analysis`

2. Click "Start Session"

3. Complete analysis

4. Click "View Results"

5. See results at `/clients/acme-corp/agents/analysis/results/123`

What actually happened:

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

2. ✅ Start session (working)

3. ✅ Complete analysis (data saved)

4. ❌ **Click "View Results" → BLANK PAGE**

5. ❌ URL changed to `/analysis/results/123` (lost client context)

React Router looked for a route at `/analysis/results/123`. Didn't exist for agency users. Rendered nothing.

User sees: Blank white screen. No error message. No loading spinner. Just... nothing.

---

What I Found When I (actually Claude Code) Started Digging

Here's what the broken code looked like:

```typescript
// AgentPage.tsx (SANITIZED - the broken pattern)
import \{ useParams, useNavigate \} from 'react-router-dom';

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

  const handleViewResults = (sessionId: string) => \{
    // Problem: Hardcoded route, no client context
    navigate(`/agent-name/results/${sessionId\}`);
  };

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

See the problem?

I extracted `clientSlug` from the URL. I used it to fetch data. But when I navigated, I completely forgot about it.

Agency routes look like this: `/clients/acme-corp/agents/agent-name`

SME routes look like this: `/agent-name`

I hardcoded the SME pattern. Agency users = broken.

---

The Realization: I Had 31 of These

2:15 PM. I fixed the first bug. Committed. Felt good.

3:42 PM. Found another one in a different agent. Same pattern. Fixed it.

4:18 PM. Another agent. Same thing.

5:30 PM. I stopped and stared at my screen for a solid 10 minutes.

Every agent page had the same bug. Every nested component that navigated. Every shared UI element with a "Go to..." button.

I could fix them one by one and spend 2 days. Or I could find the pattern and fix them all systematically.

I chose systematic.

---

The Fix: Context-Aware Navigation

Instead of `useParams()` in every component, I asked Claude Code to create a Context provider:

```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 \}) \{
  // Extract clientSlug ONCE at the layout level
  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;
}
```

Then I wrapped the agency routes:

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

Now every component inside has access to `clientSlug` via context, not params.

The routing helper (because typing the same if/else 20 times gets old):

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

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

  const buildRoute = (route: string) => \{
    if (clientSlug) {
      // Agency 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 \};
}
```

Usage (so much cleaner):

```typescript
// AgentPage.tsx (SANITIZED - the fixed pattern)
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) => \{
    // This works for BOTH SME and Agency users
    navigate(buildRoute(`agents/analysis/results/${sessionId\}`));
  };

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

One helper function. Context-aware. Works for both user types.

---

The Systematic Fix: One Day, 31 Files

Once I had the pattern, it became mechanical.

November 1st, 2025 - The Sprint

Afternoon (2 PM - 7 PM) - Finding and categorizing:

- Discovered the pattern across all agent pages

- Built Context provider and routing helper

- Tested the approach on first agent

Evening (7:00 PM - 8:00 PM) - The systematic fix:

7:42 PM - First wave (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
```

7:48 PM - Second wave (8 more 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
```

7:56 PM - Final wave (9 more 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
```

I ran through every agent flow. SME user. Agency user. Clicked every button. No blank pages.

Total damage: 17 files changed, 849 insertions, 197 deletions.

Time invested: About 6 hours of non continuous work with Claude Code (2 PM discovery → 8 PM final commit).

Time saved: Probably 30+ hours of individual bug fixes and user support.

---

What I Learned (The Hard Way)

1. Multi-tenant navigation is harder than data isolation

You can filter data by `org_id`. That's the easy part.

But navigation? You have two valid URL structures for the same feature:

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

Every `navigate()` call needs to know which pattern to use. Get it wrong once, and users see blank pages.

2. useParams() lies to you

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

This works... until the route changes. Then `clientSlug` becomes `undefined`, and your next navigation breaks.

React Context doesn't lie. It's always available, always consistent.

3. Count every bug before claiming victory

I thought I had 14 bugs. Then found 8 more. Then 9 more.

Lesson: Grep your entire codebase, not just the files you think have bugs.

4. When you find the same bug twice, stop and create a pattern

Individual fixing: 31 bugs = probably a week

Systematic fixing: Find pattern → Create helper → Fix all instances = 6 hours

The 10 minutes I spent staring at my screen at 5:30 PM saved me days.

5. Navigation bugs are existential threats

We obsess over state management, data fetching, API optimization.

But broken navigation = blank screens = "This platform is broken."

Users don't care about your RLS policies or your multi-tenant architecture. They care that clicking "View Results" shows results.

---

The Pattern (For Your Multi-Tenant App)

If you're building multi-tenant SaaS with hierarchical URLs:

✅ Step 1: Create a Context provider at the layout level

```typescript
<ClientContextProvider>
  \{/* All tenant-scoped routes */\}
</ClientContextProvider>
```

✅ Step 2: Build a routing helper

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

✅ Step 3: Never hardcode routes

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

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

✅ Step 4: Grep for ALL navigation calls

```bash
# Find every navigate() call
grep -r "navigate(" src/ > navigation_audit.txt

# Find hardcoded routes
grep -r "navigate('/" src/ | grep -v "buildRoute"
```

✅ Step 5: Test with all user types

SME flow. Agency flow. Every button. Every link. No blank pages.

---

The Results

Before November 1st:

- 31 navigation bugs lurking

- 5+ bug reports per day

- Agency users questioning platform stability

- Me: Terrified to ship new features

After November 1st:

- 0 navigation bugs

- 0 navigation-related support tickets

- Pattern established for future development

- Me: Confident shipping new agent routes

Development velocity:

- Adding new routes: 5 minutes (was 30 minutes + "will this break?")

- Bug reports: 0 (was 5+ per day)

- User trust: Restored ("Platform feels stable now")

The 6 hours I spent fixing these bugs paid back 10× in reduced support burden and restored user confidence.

---

Why I'm Sharing This

Multi-tenant navigation isn't sexy. No one celebrates "fixed 31 bugs in 6 hours" the way they celebrate "shipped new feature."

But if you're building multi-tenant SaaS like I am with https://stratum.chandlernguyen.com/
an AI marketing platform for agencies—you're going to hit this. Maybe not 31 bugs. Maybe just 5. But you'll hit it.

When you do, remember:

1. Context > Params for navigation state

2. Systematic > Individual fixes

3. Grep your entire codebase (you'll find more than you think)

4. Test with all user types before claiming victory

And if you find yourself staring at a blank screen wondering where your context went, know that I am in the same situation. :)

---

*Still coding, still learning, still finding bugs in groups of 31.*

Request alpha access at https://stratum.chandlernguyen.com/request-invitation

---

P.S. - The user who reported that first bug? They didn't lose their interview data. It was saved in the database. They just couldn't see it because of a broken route. When I fixed it at 7:42 PM, all their work was still there. That small relief made the 6 hours worth it.

---

Continue Reading

My Journey
Connect
Preferences