空白屏会摧毁信任。我有 31 个。
我在自己的 SaaS 里发现了 31 个空白屏问题——根因是我忘了多租户不只是数据访问,还包括 URL 上下文。下面是我如何在 Claude Code 帮助下一晚修完。
11 月 1 日,早上 8:47。我收到一封邮件。
“Hey Chandler - 我点了 ‘View’,然后是空白页。我是不是把所有 interview 数据都弄丢了?”
我胃一沉。空白页不是普通 bug,它是信任杀手。
我打开 dev console。检查路由。一个 agency 用户,查看 client “Acme Corp”,点了按钮,然后……URL 从 /clients/acme-corp/agents/agent-name/results/123 变成了 /agent-name/results/123。
客户上下文丢了。React Router 找不到对应路由。空白屏。
“好吧,这算一个 bug,”我当时想,“修完继续做别的。”
我让 Claude Code 去扫描代码库里相似模式,然后我照常过周六:吃饭、办事、陪孩子,日常安排。
下午晚些时候我回来查看。Claude Code 确实找到了模式——而且情况很难看。
到晚上 7:42,我修了 14 个 bug。7:48 又找到 8 个。7:56,总数来到 31 个。
根因全部相同。修法全部相同。全因为我做多租户时漏了一件事:导航不只是数据问题,它是上下文问题。
---
到底有多糟?
我得坦白这 31 个 bug 的实际意义:
对用户:
- Agency 用户频繁撞空白页(看起来像系统坏了,不像小 bug)
- 会话中途丢失工作流连续性
- “这个平台稳定到能给我们客户用吗?”
- 每一个空白页,都是一次更接近取消试用
对我:
- 每天 5+ 个 bug 反馈(全是导航相关)
- 每个单独排查要 2-3 小时
- 新功能根本发不出去(一直在救火)
- 真实恐惧:“会不会还有别的地方也坏了,只是我还没发现?”
存在性问题是: 如果连导航都做不稳,别人为什么要把客户营销策略交给 STRAŦUM?
空白屏摧毁信任的速度,比任何问题都快。
---
一切从这个 bug 开始
我直接展示发生了什么。
用户路径(本应正常):
1. 打开 /clients/acme-corp/agents/analysis
2. 点击 “Start Session”
3. 完成分析
4. 点击 “View Results”
5. 在 /clients/acme-corp/agents/analysis/results/123 看到结果
实际发生:
1. ✅ /clients/acme-corp/agents/analysis(正常)
2. ✅ 开始会话(正常)
3. ✅ 完成分析(数据已保存)
4. ❌ 点 “View Results” → 空白页
5. ❌ URL 变成 /analysis/results/123(client 上下文丢失)
React Router 去找 /analysis/results/123 这条路由。对 agency 用户根本不存在,于是渲染为空。
用户看到的就是:白屏。没有错误提示,没有 loading,什么都没有。
---
开始深挖后(严格说是 Claude Code 在挖)我看到了什么
这是坏掉代码的样子:
```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
);
}
```
问题看到了吗?
我从 URL 里取了 clientSlug,也用它拉了数据。但在导航时,我完全忘记了它。
Agency 路由是:/clients/acme-corp/agents/agent-name
SME 路由是:/agent-name
而我把 SME 模式写死了。Agency 用户自然崩。
---
关键认知:我有 31 个这种问题
下午 2:15。我修了第一个 bug,提交,感觉不错。
3:42。另一个 agent 页面,完全同样模式,修掉。
4:18。又一个 agent。还是一样。
5:30。我盯着屏幕发呆了整整 10 分钟。
每个 agent 页面都有同类 bug。 每个带导航的嵌套组件几乎都中招。每个“Go to...”按钮的共享 UI 也有同类风险。
我可以一个个修,花两天。也可以先抽象模式,再系统化一次修完。
我选了后者。
---
修复方案:上下文感知导航(Context-Aware Navigation)
我没再让每个组件都直接 useParams(),而是让 Claude Code 先做一个 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;
}
```
然后把 agency 路由包在 provider 里:
```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>
```
这样内部组件都能通过 context 拿到 clientSlug,不用每层传 params。
再加一个路由 helper(因为同样 if/else 写 20 次没意义):
```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 \};
}
```
使用方式(干净很多):
```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
);
}
```
一个 helper,带上下文,SME 和 Agency 都能走通。
---
系统化修复:一天,31 个文件
有了模式后,后面就变成了机械执行。
2025 年 11 月 1 日 - 冲刺日
下午(2 PM - 7 PM) - 发现并分类:
- 在所有 agent 页面发现共性模式
- 搭好 Context provider 和 routing helper
- 在第一个 agent 上验证方案
晚上(7:00 PM - 8:00 PM) - 批量修复:
7:42 PM - 第一波(14 个 bug):
```
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 - 第二波(再 8 个):
```
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 - 最后一波(再 9 个):
```
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
```
我把所有 agent 流程全跑了一遍。SME 用户、Agency 用户、每个按钮都点。没有空白页。
总变更规模: 17 个文件,新增 849 行,删除 197 行。
投入时间: 与 Claude Code 协作、非连续约 6 小时(下午 2 点发现,到晚上 8 点最终提交)。
节省时间: 估算至少 30+ 小时的逐个修 bug 与用户支持成本。
---
我学到的(硬课)
1. 多租户导航比数据隔离更难
按 org_id 过滤数据并不难。
难的是导航。你对同一个功能要同时支持 两套合法 URL 结构:
```
SME: /agent-name/session/123
Agency: /clients/acme-corp/agents/agent-name/session/123
```
每次 navigate() 都必须知道当前该用哪套模式。错一次,用户就看到白屏。
2. useParams() 会“骗你”
```typescript
const \{ clientSlug \} = useParams();
```
它看起来可用……直到路由变化。然后 clientSlug 变成 undefined,你下一次导航就炸。
React Context 不会“骗你”。它始终可用、始终一致。
3. 不要修到一半就宣布胜利
我一开始以为只有 14 个。后来又挖出 8 个。再后来又 9 个。
教训: 不要只看你怀疑有问题的几个文件。先 grep 整个代码库。
4. 同类 bug 出现两次,就该停下来抽象模式
逐个修: 31 个 bug 可能要一周
系统化修: 找模式 → 做 helper → 一次改完 = 6 小时
那天 5:30 PM 我盯屏幕发呆的 10 分钟,给我省了几天。
5. 导航 bug 是生存级威胁
我们总在讨论状态管理、数据拉取、API 优化。
但导航坏了 = 白屏 = “平台坏了”。
用户不会关心你 RLS 策略有多漂亮,也不会关心多租户架构多优雅。他们只关心点了 “View Results” 能不能看到结果。同样这个“技术上可用”和“产品上完成”之间的差距,我在 用 AI 构建原生 iOS 应用 时也再次遇到:Claude Code 能很快生成骨架,但真正让用户信任产品的打磨,仍是那 40% 人工工作。
---
这套模式(给你的多租户应用)
如果你在做分层 URL 的多租户 SaaS:
✅ 第 1 步: 在 layout 层创建 Context provider
```typescript
<ClientContextProvider>
\{/* All tenant-scoped routes */\}
</ClientContextProvider>
```
✅ 第 2 步: 做一个路由 helper
```typescript
const \{ buildRoute \} = useContextRoute();
navigate(buildRoute('agents/analysis/session/123'));
```
✅ 第 3 步: 禁止硬编码路由
```typescript
// ❌ BAD
navigate('/analysis/session/123');
// ✅ GOOD
navigate(buildRoute('agents/analysis/session/123'));
```
✅ 第 4 步: 把所有导航调用全搜出来
```bash
# Find every navigate() call
grep -r "navigate(" src/ > navigation_audit.txt
# Find hardcoded routes
grep -r "navigate('/" src/ | grep -v "buildRoute"
```
✅ 第 5 步: 用所有用户类型做回归
SME 流程。Agency 流程。每个按钮。每个链接。不能有白屏。
---
结果
11 月 1 日之前:
- 有 31 个潜伏导航 bug
- 每天 5+ 个 bug 反馈
- Agency 用户质疑平台稳定性
- 我自己不敢放心发新功能
11 月 1 日之后:
- 导航 bug 归零
- 导航相关支持工单归零
- 未来开发有了稳定模式
- 我重新有信心继续发新 agent 路由
开发速度变化:
- 新增路由:5 分钟(以前 30 分钟 + “会不会又坏?”)
- bug 反馈:0(以前每天 5+)
- 用户信任:恢复(“平台现在稳定了”)
这 6 小时修复,按支持成本和信任恢复来算,至少回报了 10 倍。
---
为什么我要分享这个
多租户导航不性感。没人会像庆祝“发了一个新功能”那样庆祝“6 小时修了 31 个 bug”。
但如果你和我一样在做多租户 SaaS,比如 https://stratum.chandlernguyen.com/ 这样的 agency 营销 AI 平台——你迟早会遇到这个问题。也许不是 31 个,也许只是 5 个,但一定会遇到。
遇到时请记住:
1. 导航状态里,Context > Params
2. 修复策略里,系统化 > 逐个修
3. 一定要 grep 全库(你会比想象中发现更多)
4. 在宣布修好前,用所有用户类型走完整测试
如果你也曾盯着空白屏,纳闷上下文到底去哪了,知道你不是一个人。我也一样。 :)
你上线后最痛的 bug 是什么?那种只能靠真实用户反馈才暴露的问题。我真的很想听。
致敬,
Chandler
STRAŦUM 架构系列: 这次导航危机,是更大多租户旅程的一部分。它开始于 Day 2 就做多租户,在 Day 67 重建整套 schema 时升级,最后收尾于我发现数据库 逻辑正确但慢了 296 倍。
---
Still coding, still learning, still finding bugs in groups of 31.
申请 alpha: https://stratum.chandlernguyen.com/request-invitation
---
P.S. - 报告第一个 bug 的那位用户?他们的 interview 数据并没有丢。数据一直在数据库里,只是坏掉的路由让他们看不到。晚上 7:42 修完后,他们所有成果都还在。这个小小的“还好没丢”让那 6 小时非常值得。
---





