一个人用AI一天上线全栈网站:给6岁儿子的英语启蒙,我踩了这7个坑

一个人 + 一个 AI 编程助手,从产品文档到前后端代码、从服务器部署到测试修 bug,一天时间上线了一个能真实使用的全栈网站。
不是 demo,不是玩具:现在每天真的有一个 6 岁小朋友在上面学英语。
事情是这样的。我一直想给儿子做英语启蒙,市面上的启蒙 App 要么订阅贵、要么内容不受控、要么动画多到孩子光看热闹。干脆自己做一个——反正现在有 AI 编程助手,做不做得成,试一晚上就知道。
结果这一试,从晚上写到第二天傍晚,网站上线了:english.chemppt.cn,亲子英语启蒙,52 周课程设计,闪卡、听音指图、字母寻宝、角色扮演小剧场,前后端分离,云同步,手机自适应。

先上速览表,技术细节和踩坑在后面:
| 项目 | 实际情况 |
|---|---|
| 前端 | Next.js 16 + React 19 + Tailwind v4,全静态导出 |
| 后端 | Go (gin) + PostgreSQL 16 |
| 部署 | 腾讯云 <IP>,Nginx 反代 + systemd + acme.sh 证书 |
| 工期 | 09-26 晚 → 09-27 傍晚,约一天 |
| 人力 | 我 + AI,全程没有手写一行代码 |

**一天的产出密度是真实的,但这一天里踩的坑也是真的。**下面 7 个坑,按时间顺序讲,每个都带定位过程和修法——第 2 个坑的定位方式,我觉得值得所有做运维的同行看一眼。
坑 1:图标全变成了英文单词
第一版上线自查,页面侧边栏赫然一片 cottage、style、arrow_forward。
不是被黑了,是 Material Icons 图标字体没加载。字体文件走的是 Google Fonts CDN——国内访问不到,浏览器等超时后直接把图标名当文本渲染了。
修法:把 woff2 字体下载下来自托管,删掉页面里的 Google Fonts link,@font-face 指向本地文件。图标回来了。
以为结束了?这只是上半场。
坑 2:自托管之后,图标还是英文
这是整个项目里我最想讲的一个坑。
自托管当天自查是好的,图标都正常。第二天在另一台设备上打开,又是英文。刷新、强刷、换浏览器,复现不稳定。
最后是在 Nginx 的 access log 里找到实锤的——一条字体请求记录:
GET /fonts/material-symbols-outlined.woff2 200 4001608字节 request_time: 10.288
日志不会说谎:前端的问题,一半能在 access log 里找到实锤。
真相是:Material Icons 的全量字体有 4MB,我服务器出口带宽只有 3Mbps,下载要 10 秒开外。而 CSS 里 font-display: block 只让文字空白等待 3 秒,超时后浏览器就先用普通字体把图标名显示出来——10 秒后字体到位才变回图标。我自查时字体已经缓存,所以看不到。
性能问题不会在开发者机器上复现,它只在用户的位置等你。
修法分两步。第一反应是找现成的子集方案:Google Fonts 的 icon_names 接口支持按图标名裁剪字体,把项目里实际用到的 77 个图标名传过去,生成一个 90KB 的子集字体自托管。4MB → 90KB,日志里再看这条请求,request_time 0.17s。
这里还埋了个小坑:提取图标名时第一版正则只匹配了静态写法,漏掉了三元表达式渲染的图标(比如轮播按钮的暂停态),上线后轮播一暂停又是英文。最后改成扫描组件源码里所有字符串字面量才抓全。用正则提取代码信息,一定要考虑语法变体。
坑 3:改名功能悄悄失败了,UI 一点没提示
家长中心有个「修改孩子称呼」功能,输入新名字点保存,顶栏立刻变成新名字。看起来一切正常。
直到查后端日志才发现:数据库里的名字从来没变过。
原因两层。第一层在后端:CORS 中间件的 Allow-Methods 写了 GET, POST, OPTIONS,唯独漏了 PUT。浏览器发 PUT 前先发预检请求,预检响应里没有 PUT,实际请求直接被浏览器拦下——后端只收到 OPTIONS,永远收不到 PUT。
第二层更值得警惕:前端用了乐观更新,保存后先改本地状态再异步请求云端,请求失败被 catch 吞掉,界面没有任何提示。两层各自都「合理」,叠在一起就是一个永远不会被发现的 bug。
修法:Allow-Methods 补上 PUT;同时前端把云同步失败落到状态指示器上,失败时点亮离线标记,不再静默。
AI 写代码很快,快到它会替你把失败藏起来。功能做完,日志和数据库里要再看一眼。
坑 4:修掉的演示数据,从云端爬回来了
为了上线,我把开发期用的演示数据清掉了:新用户从第 1 周、0 颗星开始,localStorage 的存储 key 也升了版本,甩掉浏览器里已有的旧存档。
结果上线当天,首页又显示第 2 周、18 颗星。
查下来是云同步策略「最后写入者胜」的正确执行结果:部署前有个浏览器还在跑旧版前端,把演示进度推上了云端;新代码只保证全新浏览器从零开始,但任何设备打开时,云端已有有效进度会被优先采纳。演示数据就这么复活了。
修法:清掉云端残留。但真正长记性的是下一个坑。
坑 5:进度清了,花园里的小花赖着不走
用户点「清除本机进度」后,星星归零了,但单词花园里演示数据留下的 2 朵小花还在——而且下次打开又被同步回来。
对后端同步接口的代码,发现它对「已找到字母」「徽章」两张表是全删再插,对「单词统计」表却是只做增量 upsert:前端提交里没有的单词,数据库里的旧记录原封不动。
同一个接口,两种同步语义,这是设计漏项。统一改成全量替换(先删后插,前端每次都提交完整状态),问题根治。
「最后写入者胜」的前提,是两边的「写入」语义完全一致。
坑 6:空罐子弹出「装满啦」
给星星罐做了个彩蛋:攒满 50 颗触发庆祝动画。实现方式是监听罐内数量,只要比上次少就判定「装满回卷了」。
清除进度的瞬间,星星从 18 掉到 0——数量下降,庆祝动画「星星罐装满啦」轰然弹出。
判断条件太宽了。改成只在「真正的满罐回卷」时计数 +1(加星星的函数里检测越过容量),庆祝层只认这个计数。语义精确了,任何原因导致的数量下降都不再误触。
坑 7:让 AI 测 AI 写的产品,先被测试工具坑了
代码修完得测。这次没有手点,而是让 AI 用浏览器自动化做了一轮全流程黑盒测试:打开页面、点按钮、答题、打卡、截图验证,模拟真实用户。
测试本身揪出了上面好几个 bug,但过程里先撞上了三个测试自己的坑:
- 网站按钮带持续动画,Playwright 的稳定性等待永远超时,最后只能读坐标用原生鼠标事件点;
- 自动化环境会吞掉
window.confirm原生弹窗——一度误报「清除进度没有二次确认」,实际弹窗一直都在,要用专门的对话框 API 去接; - 这站不少交互(答对自动翻题、原生 confirm)跟常规自动化预期相反,日志与页面状态一度对不上。
结论:AI 测 AI 写的代码是可行的,而且效率远高于手点;但自动化测试的「测试工具自身行为」也要纳入怀疑范围,否则会把工具的锅扣在产品头上。
现在的样子
一天结束时,网站有这些:8 个主题词包 86 个单词、26 个字母的寻宝游戏、6 部亲子剧本、52 周课程地图、护眼锁屏、家长中心(打印教具、成长报告、防沉迷设置)。
学习闭环也补全了:屏幕上先认 → 藏卡寻宝再练 → 打卡跟读得星 → 花园开花复习。

需要家长做的只有一件事:每周初花 5 分钟把本周字母卡打印出来藏在家里。其余的,屏幕和线下游戏自己接龙。
🎯 老张建议
- AI 把开发成本打到一天,但部署和运维的活它替不了你的判断——日志、数据库、监控该看的一样要看,这次的真凶就藏在一条 access log 里。
- 凡是「本地先改界面再同步云端」的乐观更新,必须配一个用户可见的失败提示,否则你上线的是个静默失败的功能。
- 第三方 CDN(字体、图标、统计脚本)在国内环境默认不可靠,要么自托管,要么子集化,要么做好加载失败的兜底——别赌。
💬 互动话题
- 你用 AI 编程助手做过最完整的一个项目是什么?上线了吗?
- 云同步你们用「最后写入者胜」还是版本合并?踩过语义不一致的坑吗?
- 给孩子用的产品,你会自己做还是买现成的?
🔗 关注老张
公众号:老张聊运维 小程序:观简国学 个人博客:https://www.shanwaiyun.top/