【实战+爆笑】我和AI斗智斗勇30天:一个网站背后的50+次bug修复记录
摘要
本文记录了使用Claude AI开发MCP Hub网站的真实经历,包括架构设计、无数bug修复、性能优化等环节。通过Git提交记录还原开发过程中的"崩溃瞬间",展示AI编程的真实一面——既有惊喜,也有"惊吓"。
关键词:AI编程、Claude、React、SSR地狱、Bug修复、全栈开发
1. 项目背景:一个美好的开始
6月的某天,我突然想开发一个mcp导航平台,聚合github上所有的mcp服务,并带收藏和投票功能,为了避免中途放弃,破釜沉舟,直接花了80大洋注册了域名magicmcp.net,又充值了当今最火爆的ai编程助手claude code。起初,我天真地以为有了AI助手,开发会一帆风顺。
然而,当我看到Git历史中密密麻麻的 fix: 开头的提交时,我意识到:AI也会挖坑,而且挖得很深。
1.1 技术选型
经过与Claude讨论,确定技术栈:
{
"frontend": {
"framework": "React 19",
"language": "TypeScript",
"styling": "Tailwind CSS",
"state": "Zustand",
"data-fetching": "TanStack Query"
},
"backend": {
"database": "Supabase (PostgreSQL)",
"auth": "Clerk",
"hosting": "Vercel"
}
}
2. 开发初期:AI的"热情"让我措手不及
2.1 需求分析:AI比产品经理还"贪心"
与Claude的第一次对话就让我意识到,这个AI有点"过分热情":
我:需要一个简单的MCP服务发现平台
Claude:简单?不存在的!我建议加入:
1. 实时搜索(带防抖)
2. 多维度筛选
3. 7种语言国际化
4. 深色模式
5. 响应式设计
6. 粒子特效背景
7. 无限滚动
8. 骨架屏加载
9. ...(还有20多项)
我:等等,我只是想要个列表页面...
Claude:Trust me, you'll need all of these! 😎
2.2 架构设计:看起来很美
Claude给出的架构设计确实很专业:
// 项目结构
src/
├── components/ # UI组件
│ ├── Home/ # 首页组件
│ ├── Layout/ # 布局组件
│ └── Common/ # 通用组件
├── hooks/ # 自定义Hooks
├── store/ # Zustand状态管理
├── services/ # 业务逻辑
├── types/ # TypeScript类型
└── utils/ # 工具函数
但是,作为一个只会jquery的后端程序员,这是不是有点强人所难了....
3. 核心功能实现:噩梦的开始
3.1 第一个坑:SSR地狱
Claude信心满满地说:"我们需要SSR来提升SEO!"
然后,我的Git历史变成了这样:
c8784a4 fix(ssr): 修复中文页面SSR完整解决方案
34a8277 fix(ssr): 修复服务器端与客户端缓存键不匹配导致/servers页面无数据问题
9cd66c2 fix(ssr): 修复URL重建时locale重复的问题
086bd3f fix: 修复Vercel部署中SSR入口文件动态发现问题
03f58b2 fix: 修复 Vercel 部署中的 SSR 模块加载问题
eb0d714 fix(ssr): 修复SSR模板加载和slug提取问题
6dd1382 debug: 添加服务器详情页面SSR路由的详细调试日志
最痛苦的Bug:中文页面SSR问题,足足花了3天才解决:
// Claude的第一版代码
function needsSSR(pathname) {
return pathname.includes('/servers'); // 看起来没问题?
}
// 实际运行:/zh-CN/servers?locale=zh-CN
// 结果:包含查询参数,判断失败,中文页面全部变成CSR
// 修复了5次后的最终版本
function needsSSR(pathname, searchParams) {
// 1. 处理Vercel的URL重写
if (searchParams.get('locale') && searchParams.get('slug')) {
const locale = searchParams.get('locale');
const slug = searchParams.get('slug');
pathname = `/${locale}/servers/${slug}`;
}
// 2. 移除查询参数再判断
const cleanPath = pathname.split('?')[0];
// 3. 检查是否需要SSR
return SSR_ROUTES.some(route => cleanPath.includes(route));
}
我:Claude,你不是说SSR很简单吗?
Claude:呃...我没想到Vercel会这样处理URL重写...
3.2 数据层设计:看似完美的陷阱
// hooks/useUnifiedData.ts
export const useServersPaginated = (
page: number,
limit: number,
sortBy: string,
sortOrder: 'asc' | 'desc',
filters?: SearchFilters
) => {
return useQuery({
queryKey: ['servers', page, limit, sortBy, sortOrder, filters],
queryFn: async () => {
const supabase = createClient();
let query = supabase.from('servers').select('*', { count: 'exact' });
// 应用筛选
if (filters?.category) {
query = query.eq('category_id', filters.category);
}
// 分页
const start = (page - 1) * limit;
query = query.range(start, start + limit - 1);
// 排序
query = query.order(sortBy, { ascending: sortOrder === 'asc' });
const { data, error, count } = await query;
if (error) throw error;
return {
servers: data || [],
totalCount: count || 0,
currentPage: page,
totalPages: Math.ceil((count || 0) / limit)
};
}
});
};
3.3 第二个坑:批量查询引发的"无限循环地狱"
Claude:"我们来优化一下性能,用批量查询!"
结果:
// Claude的"优化"代码
const VoteButton = ({ serverId }) => {
const { data } = useQuery({
queryKey: ['votes', serverId],
queryFn: () => batchQueryVotes([serverId]) // 批量查询单个??
});
};
// 页面上有20个VoteButton
// 结果:每个按钮都触发一次批量查询
// 20个组件 × 批量查询 = 疯狂的数据库请求
Git记录再次沦陷:
384f1a4 fix(投票): 清理遗留的用户投票查询并优化批量查询
d6364d3 fix(批量查询): 修复批量查询逻辑避免重复请求
964f00e feat(投票组件): 添加调试日志以跟踪投票状态
9479ab4 fix(voting): 在getUserVote中添加调试日志
最终解决方案(在崩溃边缘想出来的):
// 使用单例模式管理批量查询
class BatchQueryManager {
private queue: Set<string> = new Set();
private timer: NodeJS.Timeout | null = null;
addToQueue(serverId: string) {
this.queue.add(serverId);
this.scheduleFlush();
}
private scheduleFlush() {
if (this.timer) return;
this.timer = setTimeout(() => {
const ids = Array.from(this.queue);
this.queue.clear();
this.timer = null;
// 真正的批量查询
batchQueryVotes(ids);
}, 50); // 50ms内的请求合并
}
}
3.4 第三个坑:认证状态的"薛定谔的猫"
用户登录状态检测本应该很简单,但是...
// Claude的第一版
const useAuth = () => {
const { user } = useUser(); // Clerk hook
return { isAuthenticated: !!user };
};
// 问题:用户已登录,但user在首次渲染时是null
// 结果:闪烁问题,用户看到"请登录"然后突然变成"已登录"
相关的Git提交:
c3184a9 fix(auth): 重构认证状态检测逻辑,移除受限状态
bc5ce10 feat(认证): 添加安全组件和认证状态检测
167dec3 fix(hooks): 使useFavoritesSync在没有ClerkProvider时安全使用
最搞笑的Bug:收藏同步导致的"幽灵收藏"
// 场景:用户未登录时收藏了5个项目,然后登录
// Claude的同步逻辑
useEffect(() => {
if (user && localFavorites.length > 0) {
// 同步本地收藏到云端
syncToCloud(localFavorites);
clearLocalFavorites(); // 清空本地
}
}, [user]);
// 问题:如果同步失败怎么办?
// 结果:用户的收藏消失了!"我的收藏呢???"
修复后的版本(加了一堆保护逻辑):
const syncFavorites = async () => {
try {
// 1. 先备份
const backup = [...localFavorites];
// 2. 尝试同步
const results = await syncToCloud(localFavorites);
// 3. 验证同步结果
const successCount = results.filter(r => r.success).length;
// 4. 只有全部成功才清空本地
if (successCount === localFavorites.length) {
clearLocalFavorites();
} else {
// 恢复失败的项目
console.error('部分收藏同步失败,保留本地数据');
}
} catch (error) {
// 5. 出错不清空,下次再试
console.error('同步失败,保留本地收藏');
}
};
3.5 性能优化:从"龟速"到"可以接受"
首页加载时间:8秒 → 3秒 → 800ms
优化过程中的"惊喜":
// Claude:"我们一次性加载所有数据吧!"
const { data: servers } = useServers(); // 加载2000+条数据
// 我:为什么这么慢?
// Claude:让我加个loading...
// 我:不是,我是问为什么要加载所有数据?
// Claude:哦...让我们改成分页
最有趣的优化:Logo图片路径问题
97dda1d fix(Header): 将logo图片路径从变量改为静态路径
e11ada1 fix(Layout): 修复Header组件中logo图片的引用路径和样式
// Claude的动态路径(在生产环境失败)
<img src={`${import.meta.env.BASE_URL}logo.png`} />
// 尝试修复1:使用相对路径(还是失败)
<img src="../assets/logo.png" />
// 尝试修复2:使用public路径(终于成功)
<img src="/logo.png" />
// Claude:"原来Vite的静态资源处理是这样的..."
// 我:"你刚才不是很自信吗?"
3.6 国际化实现:七种语言的"巴别塔"
Claude:"支持7种语言很简单!"
实际情况:
9123c83 feat(i18n): 添加多语言404页面支持并优化动画效果
cb52045 feat(页脚): 添加多语言联系方式模块
最离谱的Bug:语言切换导致的"精神分裂"
// 用户报告:"我切换到中文后,有些地方还是英文"
// Claude的代码
const translation = {
'zh-CN': {
home: '首页',
servers: '服务器',
// 忘记翻译的部分...
'server.status.active': 'Active', // 😅
'server.status.deprecated': 'Deprecated' // 😅
}
};
// 更离谱的是混合语言
<div>
{t('welcome')} {/* 中文:欢迎 */}
to MCP Hub! {/* 英文硬编码 */}
</div>
4. Supabase集成
4.1 数据库设计
-- 服务器表
CREATE TABLE servers (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description JSONB, -- 多语言描述
repository_url TEXT,
stats JSONB, -- 包含stars, forks等
category_id TEXT REFERENCES categories(id),
tags TEXT[],
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 用户收藏表
CREATE TABLE user_favorites (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id TEXT NOT NULL, -- Clerk用户ID
server_id TEXT REFERENCES servers(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, server_id)
);
-- 创建索引优化查询
CREATE INDEX idx_servers_category ON servers(category_id);
CREATE INDEX idx_servers_search ON servers USING gin(to_tsvector('english', name || ' ' || description));
5. 部署噩梦:Vercel说"不"
5.1 部署地狱的开始
Git提交历史揭示了真相:
086bd3f fix: 修复Vercel部署中SSR入口文件动态发现问题
13647d0 fix: 在build:vercel脚本中添加清理和重新安装依赖的步骤
03f58b2 fix: 修复 Vercel 部署中的 SSR 模块加载问题
00c6b78 fix: 修复Vercel部署后预渲染页面无法正确提供的问题
1d14e06 fix: 修复预渲染页面样式缺失问题
4b5474c fix: 修复 Vercel 部署后 SSR 预渲染页面无法正确展示的问题
aad1933 fix(vercel): 更新vercel配置和静态文件处理逻辑
最崩溃的时刻:部署成功但页面全白
// 本地:完美运行 ✅
// Vercel:白屏 ❌
// 调试了2小时后发现...
// Claude忘记告诉我Vercel的环境变量需要手动添加
console.log(process.env.VITE_SUPABASE_URL); // undefined 😭
5.2 "简化"SSR的诞生
在修复了无数SSR问题后,我做了一个决定:
// 原本的"完美"SSR(1000+行代码)
// 简化后的SSR(300行代码)
// Claude:"但是这样会损失一些功能..."
// 我:"能用就行!"
// 结果:
// - 代码复杂度降低70%
// - 部署成功率从30%提升到95%
// - 我的头发保住了
6. 开发效率对比:理想 vs 现实
6.1 预期 vs 实际
| 开发阶段 | 预期时间 | 实际时间 | 备注 |
|---|---|---|---|
| 架构设计 | 2小时 | 2小时 | ✅ Claude确实很快 |
| 基础功能 | 10小时 | 20小时 | ❌ 调试AI的代码花了一半时间 |
| SSR实现 | 4小时 | 15小时 | 💀 参见上面的Git历史 |
| Bug修复 | 5小时 | 25小时 | 😭 AI挖的坑,自己填 |
| 部署上线 | 2小时 | 8小时 | 🤯 "本地能跑"≠"线上能跑" |
| 总计 | 23小时 | 70小时 | 效率提升:-204% 😂 |
6.2 真实的时间分配
写代码:30%
调试AI写的代码:40%
查文档理解AI的代码:20%
重写AI的代码:10%
7. 血泪教训总结
7.1 AI的真实面目
优点(是真的):
- ✅ 代码写得快(虽然不一定对)
- ✅ 知识面很广(虽然有时候会搞混)
- ✅ 不会累(但会让你累)
- ✅ 态度很好(即使写出bug也很有礼貌)
缺点(血泪经验):
- ❌ 过度自信("这很简单"→ 15小时debug)
- ❌ 爱炫技(简单需求复杂化)
- ❌ 健忘(同样的错误能犯3次)
- ❌ 不懂部署("本地能跑就行")
7.2 使用AI的正确姿势
// ❌ 错误示范
我:帮我写个完整的网站
Claude:好的!(然后给你一个需要debug 30小时的项目)
// ✅ 正确示范
我:帮我写个分页组件,要简单的,能用就行
Claude:好的!(给你一个真的能用的组件)
7.3 我学到的真理
- AI的代码要当草稿看,不是成品
- 简单 > 完美,特别是AI建议加功能时
- 本地测试 + 生产测试 + 祈祷 = 部署流程
- Git commit记录是你调试时的救命稻草
- AI说"这很简单"时,预留3倍时间
7.4 意外的收获
虽然过程很痛苦,但是:
- 学会了如何快速调试未知代码
- 对各种框架的坑了如指掌
- Git使用技能MAX
- 心态变好了(什么bug没见过)
8. 项目最终成果
经历了无数个"fix:"提交后:
- ✅ 网站终于上线了
- ✅ 功能都实现了(虽然和最初设想不太一样)
- ✅ 性能还不错(简化后的)
- ✅ 代码可维护(重写后的)
- ⚠️ 头发少了一些
- ❓ 不确定是否还会用AI写代码(claude code太贵了)
9. 写在最后
给想尝试AI编程的朋友们:
- 要有心理准备,AI是工具不是魔法
- 保持怀疑,特别是AI说"很简单"的时候
- 学会阅读和调试代码比让AI写代码更重要
- 记得备份,记得commit,记得保存
- 保持幽默感,你会需要的
最后的最后:
这个项目让我明白了一个道理:
"AI不会取代程序员,但会取代不会调试AI代码的程序员。"
现在,我既会写代码,又会调试AI的代码,我是不是进化了?🤔
P.S. 这篇文章是我自己写的,没让Claude帮忙。因为我怕它又给我加什么"优化"...
看看AI写的网站:http://magicmcp.net
技术交流:欢迎在评论区讨论AI辅助开发的经验和问题。
浙公网安备 33010602011771号