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.
---