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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.