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