资讯动态

基于T3 Stack的代码片段管理工具实战:tRPC、Prisma与SQLite的取舍

发布时间:2026/10/26 8:52:25 来源:尧图企业网站定制
今天想聊一个我最近折腾的个人项目——t3code。先说清楚它是什么一个基于 T3 Stack 搭建的代码片段管理工具专门用来解决“代码写完了回头却找不到”的痛点。你可能也有过这种经历某个算法、某段配置、某个临时脚本当时觉得不过就是随手一写结果几周后想复用时只能靠记忆在多个仓库、笔记软件甚至聊天记录里翻找。t3code 做的就是把这些零散的代码碎片统一收进一个可以全文检索、打标签、一键分享的“私人代码库”里。这个项目适合两类人一类是像我一样经常跨项目写代码的开发者另一类是想了解 Next.js、TypeScript、Tailwind CSS、tRPC、Prisma 这套现代前端全栈技术栈怎么实际落地的朋友。如果以技术选型为切入点这篇文章几乎可以当作 T3 Stack 的实战笔记来读——包括为什么选 tRPC 而不选 REST、为什么用 SQLite 而不上 PostgreSQL、以及 CodeMirror 6 和 Next.js 配合时踩过的那些坑。我会把每一处“为什么这样做”讲透也会把排障过程原文呈现保证不只有结论还有计算机背后的取舍逻辑。1. 项目想法起源从一次“找不到代码”的尴尬说起1.1 真实痛点我的代码碎片全散落在五个地方最早有 t3code 这个念头是在去年底帮同事排查一个历史遗留 bug。当时我记得自己以前写过一段处理时间序列的 Python 函数逻辑很简单就是滑动窗口去重加插值。可我翻遍了手头的项目愣是没找着。后来才想起那段代码是当时在某个爬虫脚本里临时写的而那个脚本已经随着服务器清理被删了。你能想象那种感觉吗明明是自己亲手写的代码却像从来没存在过一样。从那以后我开始留意自己的“代码资产”分布状态结果把我自己吓了一跳一部分在 GitHub 私有仓库里一部分在本地某个角落的 .txt 文件中还有一部分在笔记软件、微信收藏夹乃至邮件草稿箱里。每次找东西都要开好几个软件用 grep 或者系统搜索一遍效率极低。更麻烦的是在笔记软件里粘贴代码语法高亮和缩进格式经常丢失复制出来的代码缺行少段根本没法直接运行。所以我需要的东西很明确一个能存代码、能搜索、能打标签、能快速复制的工具。它不能太笨重像 Jupyter Notebook 那种一开就要等半分钟的不行也不能太简陋光能存文本却没法检索的不行。我理想中的工具应该是“数据库 搜索引擎 编辑器”三位一体的最好还支持自定义元数据让我可以按语言、按标签、按时间过滤。1.2 产品定位做一个专供自己使用的“代码归档库”想清楚了需求第一步就是定义范围。t3code 没有做成一个面向大众的产品它首先是一个解决我自己问题的工具。所以很多功能都带有强烈的个人偏好编辑器支持 Vim 键位添加标签不需要进详情页直接在列表页按快捷键弹出输入框搜索框默认聚焦输入即搜不需要点击任何按钮。在这个定位下我砍掉了非常多“看起来应该有”的功能。比如多人协作、评论、权限管理、浏览统计——这些在通用代码托管平台类似 GitLab、GitHub Gist上常见的能力对单用户工具来说反而是负担。我希望每一次打开 t3code 的时候页面加载足够快操作路径足够短从“想到代码”到“看到代码”不超过三秒。这个目标听起来简单其实在技术实现上会引导你做很多决策下面我会慢慢拆解。2. 技术选型T3 Stack 带给我的取舍2.1 为什么是 tRPC 而不是 REST 或 GraphQLT3 Stack 最吸引我的一点就是 tRPC。如果你对 tRPC 还不熟悉可以把它理解成“不需要写 API 文档的远程函数调用”。传统 REST API 的开发流程是后端先定义路由再写控制器再定义请求和响应类型前端再为每个 endpoint 封装 request 函数。遇到字段变更前后端要同步改好几处。即便用 Swagger 自动生成文档也解决不了类型在不同层之间不一致的问题。tRPC 的思路完全不同。它让你把一个后端函数直接暴露为可调用 procedure前端通过 React Query 的 hooks 去调用这个 procedure而所有输入输出类型都是 TypeScript 自动推导的。举例来说我在 t3code 的后端定义了一个获取标签列表的 procedure前端立刻就能通过trpc.tag.list.useQuery()拿到带完整类型提示的结果对象完全不用手写任何 dto、interface、request 或者 response 类型。这里我要解释一个很容易忽略的点tRPC 的价值不仅仅在于“少写代码”更在于它的端到端类型安全。只要后端某个字段改名前端编译直接就报错而不是等到运行时才暴露。对于单仓库的前后端项目来说这种即时反馈能省下大量联调时间。当然tRPC 要求前后端代码在同一个仓库这个约束对 t3code 这种单体应用完全不是问题反而让项目结构紧凑了不少。相比之下GraphQL 也很强大但它需要维护一套 schema、解析器还有缓存策略对一个个人工具来说“过重了”。2.2 数据层设计Prisma SQLite 的理由与 Schema 细节数据层我用的是 Prisma SQLite。普里斯马Prisma是 Node.js 生态里非常好用的 ORM它对 TypeScript 的支持堪称完美——模型定义即类型定义查询结果都是强类型的。至于为什么选择 SQLite 而不是 PostgreSQL我的逻辑很简单t3code 是个人工具数据规模撑死几十万条SQLite 的性能完全足够而且 SQLite 是一个文件备份只需拷贝一个文件迁移也极其方便。如果哪天真需要 PostgreSQL普里斯马只需要改一行datasource配置再跑一次迁移命令就能切换前期没必要给自己上高维度的运维负担。Schema 设计上我保持了最小化但足够扩展的模型。核心表有三张Snippet代码片段、Tag标签、SnippetTag多对多关联表。Snippet表包括 id、title、content、description、language、favorite、createdAt、updatedAt 这几个字段Tag表包括 id、name、slugSnippetTag表则记录 snippetId 和 tagId 的对应关系这样以后想做个“标签云”或者“按标签统计”就非常简单了。这里有个值得记录的踩坑经验最初我贪图方便直接把标签设计成Snippet表里的一个String[]字段存成逗号分隔的字符串。确实实现起来很快但到后面当我需要“所有标签列表”和“标签联想输入”时就不得不频繁地做全表扫描、字符串拆分和前端去重代码又脏又慢。后来我痛定思痛在项目早期就花半小时拆成关联表之后所有功能都变得顺畅了。如果你也在做一个带标签体系的工具我强烈建议你一开始就认真设计多对多关系别偷懒。3. 核心功能拆解与实现细节3.1 编辑器选型CodeMirror 6 的三重理由t3code 的代码编辑器我选用了 CodeMirror 6而不是 Monaco 或 Ace。原因有三个方面。第一CodeMirror 6 的模块化设计很优秀你只需要引入自己需要的语言包、快捷键和自动补全插件其余部分都会被 tree-shaking 掉最终打包体积比 Monaco 小得多。第二它对移动端的适配比另外两个编辑器友好在平板上打开 t3code 也能正常书写。第三CodeMirror 6 的 ViewPlugin 机制非常灵活我可以很方便地注册自定义命令比如我绑定了一个“CmdEnter 保存并关闭”的快捷键。创建片段的交互我设计成了两段式。第一步是填写元数据包括标题、语言、描述、标签第二步是聚焦到代码正文区。保存时前端把所有数据通过 tRPC 的 mutation 发到后端普里斯马在事务里同时创建 snippet 和 tag 关联。为了让操作更跟手我用 Rabbit Hole 中常见的乐观更新optimistic update策略——用户按下保存的瞬间列表立即出现新条目如果后台返回失败再回滚并提示错误。实际体验下来这种方法让 t3code 的保存操作几乎无感知完全不需要 loading 动画。语法高亮方面我也花了不少心思。CodeMirror 6 的语言组件是按需加载的我只针对 JavaScript、TypeScript、Python、Go、SQL 这几个高频语言做了预加载。其他语言则通过 CDN 动态引入对应的 language 包。因为 t3code 全站采用暗色主题我还定制了自己的 highlighter 样式确保代码高亮色与整体 UI 风格统一而不是像某些编辑器那样在暗底上弹出亮得刺眼的默认配色。3.2 全文搜索从 LIKE 查询到 FTS5 全文索引的升级路线搜索是 t3code 里被使用最多的功能也是我性能优化最在意的部分。我的目标是“即输即搜”——每次击键后结果列表都要实时刷新。第一版我用的是 Prisma 的contains条件对 title、description、content 三个字段做模糊查询。在小数据量下表现还行但当条目超过五千条时每次搜索都会有几百毫秒的延迟明显达不到我的“三秒内看到代码”的目标。后来我把注意力放到了 SQLite 自带的全文搜索扩展 FTS5 上。FTS5 用法很简单——我先建了一个虚拟表snippet_fts把标题、描述、正文这几个字段写入索引。查询时用MATCH语法速度比LIKE快出数量级。不过在中文分词上 FTS5 默认不支持直接搜中文会出现“一搜一个空”的状况。解决方案是做一个简单的 bigram 分词器把连续的汉字切成两个字符一组存入索引。例如“滑动窗口”会被切成“滑动”“动窗”“窗口”三组这样在搜索“窗口”时就能匹配到。虽然不如专业的 NLP 分词器聪明但对付代码注释级别的中文文本完全够用。如果你也要在个人项目里做全文搜索我建议先看看 SQLite FTS5而不是一上来就引入 Elasticsearch 或 Meilisearch。尤其是单机应用场景FTS5 的集成成本极低、性能极佳还不用额外启动一个服务进程。我甚至见过一些人用 FTS5 给本地知识库做全文检索效果完全不输商业软件。3.3 在线运行与分享把“运行”做得轻到极致很多代码片段工具都想提供“在线运行”能力但真正的沙箱环境成本高到离谱要维护容器集群、安全隔离、资源限制。t3code 没有去碰这个泥潭而是用两个轻量方案迂回实现。第一Python 代码在后端通过一个带超时限制和系统调用拦截的受限执行器运行只支持纯计算型代码并明确禁止了文件读写和网络请求。第二JavaScript 代码直接在浏览器端用new Function执行天然没有宿主 API 权限。这两套方案加起来代码量不到 200 行却覆盖了日常 90% 以上的验证场景。分享功能同样做得很克制。每段代码都有一个短链接点击后进入一个只读页面用 highlight.js 做静态高亮展示。没有评论、没有点赞、没有浏览量统计。这不是因为我懒而是我意识到工具一旦试图满足所有人的所有需求就会变得又慢又复杂。分享的本质只是“把代码发出去给别人看”把这个动作做好就够了。这其实也是 t3code 的整体哲学——小切口、深打磨不贪多。4. 部署与性能优化实录4.1 Vercel 部署时遇到的两个典型问题t3code 的前后端都跑在 Next.js 上所以部署我首选了 Vercel。但实际部署过程并不是“一键完成”的有两个坑值得记录下来。第一个坑是 tRPC 的 WebSocket 长连接配置。Vercel 的无服务器函数默认不支持保持长连接而 tRPC 某些实时场景需要 WebSocket 订阅。解决办法是把前端的 tRPC link 从subscriptionLink换成httpBatchLink也就是所有请求都走 HTTP 轮询或批量请求。对于单用户工具来说本来就完全没有实时推送需求根本不用启动独立的 WebSocket 服务器。第二个坑是环境变量的同步问题。本地开发时.env文件里写好了 DATABASE_URL 和访问令牌一切正常。可一旦推到 Vercel 线上环境就必须在项目配置面板里一步步添加这些变量。我当时疏忽了只给 preview 环境配置了变量生产环境一直报 500。排查了半天才发现是 environment 没选对真是典型的“一行配置引发的线上事故”。所以如果你也用 Vercel 部署 Next.js 项目养成习惯先去 Settings 的 Environment Variables 页面检查当前部署分支对应的环境变量再去看代码逻辑。4.2 前端包体优化动态加载与本地字体Next.js 默认是服务端渲染但 t3code 里有大量页面是纯交互界面根本不需要在服务端渲染。我用next/dynamic把所有带编辑器、代码高亮、Markdown 渲染的组件都改成了动态加载并显式设置ssr: false。这样做的好处是首屏 HTML 体积直接减半原本几兆的 JavaScript bundle 被拆成了按需加载的分片CodeMirror 相关的代码不再阻塞首屏。另一个优化点是字体和图标。我用next/font加载了本地字体文件而不是从 Google Fonts 等外部 CDN 拉取避免了额外的 DNS 查询和连接开销。图标则挑选了十几个常用 SVG 直接内联到组件里没有额外安装一个图标库。你可能觉得这些细节无关痛痒但每个页面少几十 KB累积起来的加载速度差异是可以感知的。最终 t3code 的 Lighthouse 性能评分在 95 分以上大部分靠的就是这些不起眼的“抠门”。5. 常见问题与排查技巧实录5.1 tRPC 缓存未失效导致的前端表现滞后在用 t3code 开发“按标签筛选”功能时我遇到过一个特别隐蔽的问题。前端调用trpc.snippet.list.useQuery({ tagIds })但当我切换筛选标签时列表数据却一直不更新。排查了两天最后才意识到是 tRPC 的 query key 机制导致的问题React Query 会依据传给useQuery的输入参数自动生成缓存 key如果你的代码没把tagIds作为参数传入前端就会从缓存里读取旧数据根本不会发起新请求。解决方案很简单——不论查询条件是否参与实际筛选都要把全部参数传给 tRPC 的input。比如即使 tagIds 为空数组也应该显式传{ tagIds: [] }。这样 React Query 的 key 就会随着 tagIds 的变化而变化向后端发请求的条件也被正确触发。这是我踩过最“反直觉”的一次坑希望你能少走弯路。5.2 Prisma 并发创建标签时的唯一约束冲突一个容易被忽略的并发场景是用户快速创建多个片段并给它们打上同一个新标签时前端每提交一个片段都会执行一个“确保标签存在”的操作。普里斯马的upsert命令在并发情况下会偶发报错unique constraint failed: Tag.name。原因在于两个请求同时发现标签不存在然后同时尝试插入其中一个就会撞到唯一索引。我最终采用的稳定方案是先把所有标签一次性传给一个专门的ensureTagsmutation让后端在一个事务批次里创建缺失的标签再在提交 snippet 时只做关联操作。虽然多了一次网络请求但数据一致性得到了保障再也没出现过这个报错。如果你需要类似逻辑切记不要盲目依赖upsert在极高并发下的正确性。5.3 CodeMirror 6 与 Next.js 严格模式下的焦点丢失Next.js 新版本默认开启了 React 严格模式也就是开发环境下组件会额外执行一次 mount。结果在 t3code 里CodeMirror 6 的编辑器出现了一个典型问题每次输入一个字符后光标都会跳到末尾甚至直接失去焦点。排查后发现是因为编辑器在每次 React 渲染时都会重新创建编辑器实例而不是复用同一个实例。原因是把编辑器初始化写在组件主体内而不是放在useEffect里。正确做法是把 editor view 的创建放到useEffect中并使用ref保存实例在依赖数组为空时只创建一次。清理函数里再调用view.destroy()。这样在严格模式的双重 mount 测试下编辑器也能稳定地持有同一个实例输入焦点再也不会丢失了。这个问题看起来是 CodeMirror 特有的但背后的思路——“外部实例化对象必须挂载在 React 生命周期之外”——适用于一切和 React 配合的第三方类库。6. 一些作为收尾的个人体会最后再说说我真实使用 t3code 的感受。它并没有成为我朋友圈里那种“光鲜的 demo”更像一个私人的技术归档却很切实际地改变了我的工作习惯。现在每周末我会花十几分钟把自己本周遇到的典型问题、写过的临时脚本、阅读源码时发现的精妙写法整理成片段存进 t3code打上标签。三个月下来我意外发现自己攒下了一个比记忆可靠得多的知识库——尤其在面对“我以前是不是写过类似代码”这类问题时只需要搜索一下答案就在眼前。如果你也想搭一个类似工具我的建议是别一开始就想做成一个完整的“系统”从最痛的那个需求出发做一个小而趁手的工具。技术选型上T3 Stack 确实能帮你少操很多心但更关键的是交互细节和长期维护的意愿。t3code 的代码量其实不大但每次把它打磨得更顺手一些的时候我都会觉得时间花得很值。如果你也在做自己的代码碎片管理方案欢迎分享你的思路我们可以一起把这块领域做得更好用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑