从 0 到上线:我用 Next.js 16 给孩子手搓了一套三科学习软件

81 阅读5分钟

从 0 到上线:我用 Next.js 16 给孩子手搓了一套三科学习软件

一、缘起

一段真实的背景:孩子初一,英语长期在及格线下,一提英语就抗拒。常规办法试了不少,报班的事也认真想过,最后的判断是:对一个已经"打开书满眼都是不会"的孩子,再加练习册只会加重防御。

于是从九月中旬开始动手,国庆假期集中冲刺,给她做了一个只服务一个人的学习系统——三科(英语 / 语文 / 数学)、手机优先的 PWA、自托管在服务器上。它现在每天真的在被使用。这可能是它和大多数 side project 最大的区别:有一个"用户"在用真实数据每天检验它。

这篇文章不讲鸡汤,只讲工程:技术选型、核心设计.

二、系统长什么样

学生端四个一级页——今日 / 错题 / 训练 / 我的;家长端三个一级页——看板 / 孩子 / 我的。核心闭环是:

拍照识卷 → 错题归因 → 变式练习 → 收复 → 原子归队进复习队列

此外有两个"偏体验"的模块:全屏的游戏化场景"今夜梦境"(地标即任务,走完全图=做完今天的事),以及学习陪伴声(场景噪声 / 网络电台 / 自制音乐)。形态上是 PWA + 安卓壳。 在这里插入图片描述 在这里插入图片描述

三、技术选型

层选择理由
框架Next.js 16(App Router + Turbopack)一个人维护,前后端同仓;服务端组件直接读库
语言TypeScript(strict)数据模型字段极多,没有类型不敢改
样式Tailwind v4CSS 变量双主题(浅色 / 深色),组件零改动
数据库SQLite(libsql)+ Drizzle ORM单实例单家庭;备份=复制文件;明确不引入 MySQL
测试Vitest + Playwright(4 套配置)纯逻辑单测 + 真实浏览器 E2E
部署Docker + Nginx 一键脚本持久数据全部落一个工作区目录
客户端PWA + Capacitor网页永远最新;壳只解决锁屏播放、更新、分享这类系统能力

选型关键词是一个人的可维护性:没有微服务、没有消息队列、没有 Redis。所有"高级"的东西都往后放,先保证今天能用、出问题能修。

四、五个核心设计

1. 原子与调度:围绕"可被调度的最小单位"建模

学习内容被抽象成原子:词 / 短语 / 句型。每个原子(词库 2905 条)都由 FSRS 调度,题面按类型分级——词走"认 → 填空 → 拼写"全阶梯;短语可认、可挖空、拼写只做关键词补全;句型只做结构辨认与槽位补全。

一个经验:算法不是重点,"每天真的会回来"才是重点。所以调度之上还有产品约束——每日处方约 15 分钟,做得完比做得多重要。

2. 今日处方:小剂量、有终点、情绪护栏

  • 必做只有两件事:复习 + 新词;
  • 错题"收复"每天固定两笔——是一天两笔的定投,不是一张看不到头的清单;
  • 最近两天连续"累 / 不想学"→ 次日自动减量;
  • 不做不惩罚,答错不扣分(失败不回收任何已得)。

3. AI 管线:能不用 AI 的地方,绝不用 AI

拍照识卷走视觉模型(结构化输出 + 题目坐标),生成内容走文本模型,但外面套了三层保险:词汇闸门、结构化 Schema、确定性校验。核心原则一句话——AI 输出永远按不可信输入处理。这部分展开会很长,我单独写了一篇:《把 LLM 关进笼子:一个教育产品的 AI 质量工程实践》。

4. 内容管线:教材包即数据

  • 英语覆盖 3~9 年级三个教材版本,共 41 册内容包;
  • 语文部编版 14 册,3899 条原子(含古诗文 1247 条);
  • 数学北师大版 14 册,814 个技能点(含前置 DAG);
  • 全部是 JSON 内容包 + 幂等种子脚本:内容文件进版本库,结构校验进测试(册内唯一性、跨册一致性、数学示例题走确定性求值复验)。

5. 多学科:契约先行,零迁移扩展

后来加语文、数学,没有改英语的任何一张表。做法是:学科注册表 + 各科自建原子表 + 统一的"收复"契约(UI 层不写 if (subject === 'zh') 之类的判断)。加一科的成本 = 写内容 + 注册表加一行。

五、工程化与运维

  • 测试:759 个单测 / 260 个 E2E(截至发文),E2E 分四套配置(主套件、认证、子路径、TTS mock),全部在系统临时目录跑,避免污染工作区;
  • 日志:结构化 JSON → Elasticsearch → Grafana;所有日志字段在注册表登记,用 TypeScript AST 扫描全部调用点强制校验——写错字段名直接构建失败;
  • 部署:./deploy/start.sh <工作区>,容器启动自动执行迁移 + 内容种子(幂等),不建任何默认口令账号;
  • 备份:SQLite 的 VACUUM INTO + 照片打包,挂 cron。

在这里插入图片描述

在这里插入图片描述

七、成本、隐私与边界

  • AI 调用都有幂等"认领"(例如家长周报每科每周最多生成一次),TTS 用内容寻址缓存——同一段文本重复朗读不再重复付费;
  • 日志脱敏,不记录请求体、口令、验证码;孩子的照片按学生归属校验,无权限一律 404;
  • 边界感很重要:它不是"提分神器",目标是让孩子愿意坐下来。方向用学校考试分数检验,但从不对孩子承诺分数。

八、后记

这套系统现在自托管在 01study.cn,每天给孩子用。如果你也有一个在某科上卡住的孩子,我的建议不是"你也去写一个",而是:先把"逼他学"换成"设计一个他能完成的起点"——技术只是手段之一。


本文为原创内容,技术细节来自真实项目。评论区欢迎交流。