T3Code:T3 Stack全栈项目脚手架的设计与实现
发布时间:2026/10/27 16:07:07 锦皓数字建站

这两年做全栈项目我最大的感受是开新项目的成本大头不在写业务代码而在搭骨架。装依赖、配 TS 路径、搭 tRPC 路由、初始化 Prisma、写好 Tailwind 的主题变量……这一套下来快的也要半小时慢的能折腾一上午。尤其团队里每个成员的习惯还不一样A 喜欢把 API 写一个文件里B 习惯按 feature 分组最后合并代码时全靠人工对齐光 review 就要浪费大量时间。T3Code 就是为解决这个问题做的。它是一个命令行工具用来快速生成标准化的 T3 Stack 项目骨架——直接帮你把 Next.js、Tailwind、tRPC、Prisma、TypeScript 这些技术栈全部初始化好再按统一规范生成功能模块的 CRUD 代码。简单说你只要告诉它“我要做一个带用户和通知中心的项目”它就能把项目目录、数据库模型、API 路由、前端页面骨架都给你铺好。这篇文章就聊聊 T3Code 的设计思路、核心实现和实际使用中的注意事项给准备自己写脚手架或者想在团队内统一工程规范的朋友做个参考。1. 整体设计与思路拆解动手写 T3Code 之前我最先想清楚的问题不是“怎么写代码”而是“这个工具到底要解决谁的什么问题”。方向没定好后面全是返工。1.1 痛点每次开新项目重复劳动太多我统计过自己过去一年的项目启动时间新建一个全栈应用从create-next-app开始到能跑通第一个带数据库读写的前后端联调页面平均花 45 分钟。这 45 分钟里真正写业务的时间大概只有 10 分钟其余全在配置——比如 Next.js 的 server 端配置、tRPC 的 router 结构、Prisma 的 datasource 和 model、TS 的路径别名、ESLint 规则等等。更要命的是团队场景。我自己手写没问题但三个人同时开同样的模块写出来的目录结构、命名风格、错误处理方式一定五花八门。有人喜欢userRouter有人写成user.routes.ts有人把所有模型塞进一个 schema.prisma有人拆成十几个文件。代码 review 的时候大量精力花在“这个文件该放哪”而不是“这个逻辑对不对”上。所以 T3Code 的定位很明确不做业务只做规范和骨架。它把技术栈的初始化和项目结构的规范化固化成模板让每个新项目从第一行代码起就长成一个样子。1.2 定义与核心能力T3Code 到底做什么T3Code 是一个 Node.js 环境下运行的命令行工具核心流程分三步读取用户输入的项目名、模块列表和功能选项。根据输入从内置模板引擎生成对应的目录结构和文件内容。自动安装依赖、初始化数据库 schema、输出后续操作指引。它最核心的卖点不是“一套模板”而是“可组合的模块化生成”。你不需要每次生成整个项目也可以单独在已有项目里跑t3code add post只新增一个 post 功能模块——包括 Prisma model、tRPC router、页面组件、API 类型导出一次生成全部接好。1.3 适合谁用解决什么问题我的目标用户有两类。第一类是个人开发者尤其是刚接触 T3 技术栈的人。T3Code 生成的代码是规范且可直接运行的照着读代码就能理解 Next.js 和 tRPC 怎么配合比看文档一个一个粘配置要快得多。第二类是团队用一个强制统一的脚手架来消除风格分叉新成员进组后不需要再问“我们项目结构是啥”跑一遍t3code init就知道了。也正因为想同时服务这两类人我在设计时坚持了一个原则生成的代码必须“完全可读可改”不能像很多代码生成器那样生成一堆让人看不懂的封装层。T3Code 生成的代码应当接近手写水平甚至比手写更整齐。否则就是先生成垃圾再手动清理完全没有意义。2. 架构设计与技术选型定完目标下一步是选技术。这个阶段我踩了不少坑选型结果和最初设想差别挺大逐个说明。2.1 底层的 T3 Stack 为什么值得用T3Code 生成的底层项目默认基于 T3 Stack 的标准组合Next.jsApp Router TypeScript Tailwind CSS tRPC Prisma。这套组合不是随便凑的它的核心逻辑是“端到端类型安全”。举个例子在传统前后端分离项目里前端调接口拿到的数据是一个“约定”的类型后端改了字段前端编译时毫无察觉直到运行时报错或者页面白屏。而 tRPC 的做法是在服务端定义 query 和 mutation前端直接 import 这个 router 的类型来调用一旦服务端返回结构变了前端代码立刻编译不过错误直接暴露在开发期的编辑器里。T3Code 生成的项目把这个优势贯彻到每个模块数据库 model 由 Prisma 定义tRPC router 依赖 Prisma 的 client 类型前端页面又从 tRPC 的类型里拿到返回数据。一条链路下来类型是通的改一处全链路的类型检查都会提醒你。这对团队协作来说是巨大的收益——接口文档都省了一大半因为类型本身就是最好的文档。2.2 CLI 工具的技术选型模板引擎与交互库T3Code 自己是用 TypeScript 写的运行在 Node.js 环境。几个关键依赖commander解析命令行参数处理init、add、list这些子命令。prompts做交互式提问比如问“项目放在哪个目录”“要生成哪些模块”。handlebars模板引擎用{{variable}}语法把用户输入注入到代码模板里。prettier生成完代码后做一次统一格式化避免模板里缩进不一致。最开始我考虑过直接复制静态模板目录但很快就发现不够灵活。比如同一个 Prisma model用户选“包含软删除”和“不包含软删除”生成的字段就差一个deletedAt。如果用静态模板就得准备两套目录要是选项多了模板数量爆炸。换成 handlebars 之后模板里可以写条件判断和循环一个模板文件同时覆盖多种情况。其实也考虑过用ejs或者mustache它们的语法更简单但对复杂逻辑支持不够。handlebars 的 helper 机制很合适——我可以自定义{{camelCase name}}、{{pascalCase name}}这样的 helper在模板里直接做命名转换不需要在代码里到处处理字符串大小写。2.3 命名规范与目录结构的取舍这一块是团队协作的核心也是 T3Code 和普通脚手架最大的区别。我定了一套严格的命名规范并在生成器层面强制实施文件名统一用kebab-case比如user-list.tsx、create-user.ts。函数和变量用camelCase组件和 Prisma model 用PascalCase。tRPC router 文件统一叫feature.router.ts且每个 feature 一个目录。目录按 feature 聚合不再按文件类型聚合。很多人会问为什么不按components/、pages/、api/这种按类型分层早期项目确实这么组织但在功能不断膨胀后每次改一个功能要同时打开四五个目录跳来跳去很累。按 feature 聚合后一个通知中心相关的所有代码——数据库模型、API 接口、页面组件、工具函数——都在features/notification/下改起来顺心很多。这个改动是 T3Code 被团队接受的关键。没有一个现成脚手架这么组织但这是我认为大型项目后期维护最舒服的结构。T3Code 把这种结构固定下来免去了每次新项目都跟团队成员解释一遍“为什么这么放”的烦恼。3. 核心实现与实操要点聊完设计思想来说具体实现。这一章是干货密集区有大量可以直接参考的代码思路。3.1 命令设计init 和 add 的交互流程T3Code 对外暴露两个主要命令t3code init my-project t3code add notificationinit命令的完整处理流程是这样的检查目标目录是否存在为空还是已有文件。通过 prompts 问几个关键问题包管理器用 npm 还是 pnpm、是否需要用户认证模块、数据库用 SQLite 还是 PostgreSQL。调用模板引擎生成基础项目和用户选中的额外模块。在所有.env、配置文件里替换成用户输入的内容。运行npm install完成后打印下一步操作指引。有一个细节值得说生成器的交互问题不能太多。我第一版设计了 12 个问题用户还没看到生成结果就先被问烦了。后来砍到最多 5 个其余依赖默认值用户可以在生成后手动改。这个经验也分享给你们——CLI 工具的交互设计克制比丰富重要。add命令则简单很多只有一个必要参数模块名。它的流程是读取当前项目的结构判断是 T3Code 生成的标准项目。解析模块名自动生成 PascalCase、camelCase、kebab-case 三种命名。生成 Prisma model、tRPC router、页面组件、导航菜单更新。提醒用户运行npx prisma migrate dev来同步数据库。3.2 代码生成模板的编写思路模板是 T3Code 的心脏。我把模板文件放在templates/目录下每个功能模块一套核心文件有这几个templates/module/ model.prisma.hbs # Prisma model 片段 router.ts.hbs # tRPC router schema.ts.hbs # zod 输入校验 component.tsx.hbs # 前端页面组件 menu.ts.hbs # 导航菜单片段以 tRPC router 的模板为例简化后的代码长这样import { z } from zod; import { createTRPCRouter, publicProcedure } from /server/api/trpc; import { {{pascalCase name}}Schema } from ./schema; export const {{camelCase name}}Router createTRPCRouter({ list: publicProcedure.query(async ({ ctx }) { return ctx.db.{{camelCase name}}.findMany({ orderBy: { createdAt: desc }, }); }), create: publicProcedure .input({{pascalCase name}}Schema) .mutation(async ({ ctx, input }) { return ctx.db.{{camelCase name}}.create({ data: input, }); }), getById: publicProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) { return ctx.db.{{camelCase name}}.findUnique({ where: { id: input.id }, }); }), });你注意{{pascalCase name}}和{{camelCase name}}这两个占位符。用户输入notification时handlebars helper 会自动生成Notification和notification模板里不用手动写大小写转换逻辑。这一步看起来小但实际体验差别巨大——没有 helper 的话你得在代码里写一堆toPascalCase()的字符串处理容易漏掉边界情况。Prisma model 的模板会更有意思它需要在用户已有的 schema 里自动插入新 model。我的做法是生成一个片段文件然后由 Node 脚本读取主 schema 文件找到model关键字的结尾位置插入片段。为了避免重复插入我在 model 顶部加了一段注释标记// T3CODE_MODEL:notification下次生成时先检查这个标记是否存在。3.3 配置注入与依赖管理生成完代码文件还有一个关键步骤是配置注入。Next.js 项目里有几个文件必须同步更新package.json新增模块如果带了额外依赖需要自动加进去。src/server/api/root.ts把生成的 router 挂载到根 router 上。src/server/db/schema.prisma追加 model 定义。src/config/nav.ts在侧边导航里加入新模块入口。这四件事单独做都不难难的是不能出错。比如root.ts里的挂载代码我最初是用字符串替换找到export const appRouter createTRPCRouter({这一行在后面插入新 router。但后来发现这段代码在不同版本模板里格式不同字符串匹配容易漏。最后改用抽象语法树解析——用 TypeScript 编译器 API 读文件、改 AST、再输出代码。这样无论代码格式怎么变AST 层面总是能精确定位插入位置。这也是我想重点说的经验代码自动生成的底线是“生成结果必须能编译通过”。如果生成出来的代码因为漏了一个 import 或者格式不对导致编译报错用户的信任感会瞬间崩掉。宁可多花时间在 AST 处理和 prettier 格式化上也不能输出半成品。4. 完整实操从零生成一个通知中心项目理论说了一堆实际操作来看一遍。我用 T3Code 从零生成一个带用户认证和通知中心的项目记录完整的操作过程。4.1 安装与初始化T3Code 已经发布到 npm registry全局安装npm install -g t3code/cli安装完成后运行初始化命令t3code init my-app这时终端会出现交互提示选择包管理器我选 pnpm、数据库类型选 SQLite方便本地开发、是否包含用户认证选包含。选择完成后终端输出生成进度✔ 项目已创建: my-app ✔ 已生成用户认证模块 ✔ 已安装依赖 (耗时 42s) 下一步: cd my-app npx prisma migrate dev --name init npm run dev整个过程 1 分钟左右。生成后的目录结构是这样的my-app/ prisma/ schema.prisma src/ app/ (auth)/ login/page.tsx register/page.tsx (dashboard)/ layout.tsx page.tsx features/ auth/ router.ts schema.ts components/login-form.tsx components/register-form.tsx notification/ router.ts schema.ts components/notification-list.tsx server/ api/ root.ts trpc.ts db.ts trpc/ server.ts client.ts4.2 生成第一个功能模块基础项目就绪后我来加一个“公告公告”模块。公告的核心字段是标题、正文、发布时间、状态不需要关联到用户逻辑很简单。在项目目录下运行t3code add announcementT3Code 会解析当前项目结构再生成 4 个文件features/announcement/model.prisma、features/announcement/router.ts、features/announcement/schema.ts、features/announcement/components/announcement-list.tsx同时更新了root.ts、导航菜单和schema.prisma。打开router.ts看到的是带有list、create、update、delete四个基础操作的完整 router。我用编辑器打开model.prisma看到模板自动生成的 modelmodel Announcement { id String id default(cuid()) title String content String status String default(draft) createdAt DateTime default(now()) updatedAt DateTime updatedAt }基本符合预期。这里的status字段是模板默认加上去的它会在前端组件里映射成一个下拉选择框草稿、已发布、已下线。如果你不需要这字段删掉再加迁移即可。4.3 运行与验证按提示执行数据库迁移npx prisma migrate dev --name add_announcement看到迁移成功信息后启动开发服务器npm run dev浏览器打开http://localhost:3000左侧导航栏已经出现“公告管理”点进去能看到公告列表的占位页面右上角有“新建公告”按钮。打开浏览器控制台切到 Network访问页面时发出的 tRPC 请求路径是/api/trpc/announcement.list说明 router 已经成功挂载。这一整套流程跑下来从安装到看到一个能增删改查的模块页面耗时不超过 3 分钟。这个速度是 T3Code 存在的最大价值——把内耗降到最低把时间留给真正需要动脑的业务逻辑。5. 常见问题与排查技巧实录任何工具都有自己的坑T3Code 也一样。这里把我在开发和使用过程中遇到的高频问题整理出来按排查顺序排列。5.1 几个容易踩的坑问题 1生成后数据库迁移报外键错误这通常是模块之间存在关联时出的问题。比如我先生成user模块再生成notification而 notification 里有一条userId String外键指向User。如果 Prisma schema 里Usermodel 不存在因为用户初始化时没选 auth 模块迁移自然会失败。解决办法是模板生成时做依赖检查当生成一个包含外键字段的模块时先检查主表 model 是否已在 schema 中不存在则自动追加基础 model或直接终止并提示用户先添加依赖模块。现在 T3Code 里已经内置了这两种处理但第一次写的时候没考虑周全被这个坑绊了一下午。问题 2AST 插入后代码丢了 import用 TypeScript AST 往root.ts里插入 router 引用时如果没有同步处理 import 语句生成结果就会在未引用的状态下使用变量。编译器打开就报红。解决办法是在 AST 操作时先收集所有 import 声明新增 router 时把import { announcementRouter } from /features/announcement/router自动追加到 import 块末尾。这一步能用抽象语法树一次搞定但要注意配置里moduleResolution必须匹配项目的 tsconfig。问题 3pnpm 安装依赖特别慢pnpm 在首次安装时会做硬链接项目多了以后全局 store 会占用不少磁盘。T3Code 在init时用pnpm会明显比npm慢如果你只是快速体验工具可以先用 npm。不过考虑到 pnpm 后续增量安装优势很大我建议在正式项目里坚持用 pnpm。磁盘上多占几 GB 完全值得。5.2 排查顺序与调试命令遇到 T3Code 相关问题我建议按这个顺序排查先看终端错误信息——是 CLI 本身报错还是生成后项目编译报错。这两个问题的处理方式完全不同。如果是生成后的项目编译报错先跑npx prisma generate确认 Prisma Client 是最新的。很多 tRPC 类型报错归根到底都是 Prisma Client 没重新生成。如果 tRPC 路由找不到检查root.ts里 router 的 key 是否和前端调用一致。T3Code 统一用feature.list这种命名手动改过 router 名称就容易对不上。如果页面样式丢失检查 Tailwind 的 content 配置是否包含src/features/**/*.tsx。新生成的组件如果没被 Tailwind 扫描到类名就不会生效。还有一个通用调试技巧生成代码后立刻在项目根目录跑一次npm run typecheck。T3Code 生成的代码是标准的理论上 typecheck 一定能过如果没过优先看提示信息里的文件和行号那通常比看运行时错误更直观。5.3 团队协作中的版本管理技巧最后聊点实战中最重要的东西T3Code 在团队里怎么维护。由于每位成员的本地环境不同我们约定代码模板的改动走代码评审而且必须有 fixture 验证。T3Code 项目里有几个固定的测试 fixture每个模板文件的改动都会在 CI 里触发一次“生成项目 → 跑 typecheck → 跑测试”的完整流程。只有这套流程过了改动才能合并。对于使用方我建议在项目里记录生成器版本。做法是生成项目时在根目录生成一个.t3code.json配置文件记录 CLI 版本和生成时选项。以后升级 T3Code 要重新生成代码时工具会读取这个文件自动了解项目结构避免把已有的二次开发覆盖掉。6. 从 T3Code 这个工具里我学到的三件事T3Code 写到今天代码量不大但它教会我的远不止代码生成器本身。三个体会分享给大家。第一个体会是“模板比脚本重要约定比配置重要”。最初我想把 T3Code 做成一个“全能工厂”接了各种参数结果用户反而迷茫。后来不断做减法把几个高频场景做成固定模板才真正有人愿意天天用。好的脚手架应该像一张好的表单——只问你真正需要的信息剩下的都帮你填好默认值。第二个体会是“生成的代码必须能被人读懂”。很多代码生成器喜欢生成一堆封装层、抽象工厂看着高级但实际上手修改时痛苦得要命。我坚持让 T3Code 生成的代码都是平铺直叙的写法一个功能一个 router、一个页面对应一个组件文件谁打开都能看懂。代码生成器的终极目标不是减少代码量而是减少理解成本。第三个体会是“工具是文化的载体”。T3Code 最初只是我一个人写的小脚本后来团队里所有人都在用再后来我加的命名规范、目录结构也潜移默化地影响了每个人写代码的习惯。统一脚手架不只是为了效率更是为了让大家在协作时少一点“他怎么这样写”的吐槽多一点“这个放这我懂”的默契。如果你也在带团队不妨从统一脚手架开始整顿工程秩序收益会比你想象的大。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。