
用了大半年的 t3code我从一开始当个临时脚手架用到后来把它直接写进自己的项目初始化流程里这个过程挺值得聊聊的。如果你最近一直在刷 TypeScript 全栈相关的内容应该对 T3 Stack 不陌生——Next.js 加 TypeScript、Tailwind CSS、tRPC 这一套组合。t3code 在我看来就是这套组合的加速器它不只帮你把项目骨架搭好还能在你后续写路由、建模型、做页面的时候持续输出代码等于把搭架子和填砖头这两件事一起干了。这篇文章不搞花活就是把我从安装到落地、再到生产环境排坑的完整过程拆开来讲。每个命令、每段配置、每次踩坑我都会说清楚当时为什么这么选以及后来又是怎么解决的。如果你想找一个能真正提高全栈开发效率的工具或者已经在用 create-t3-app 但觉得后续开发还是太重复这篇应该能给你不少可以直接照抄的操作。1. T3 栈的痛点脚手架只完成了 20%剩下 80% 的重复劳动没人管先聊聊我为什么从 create-t3-app 切换到 t3code。不是 create-t3-app 不好恰恰相反它把 T3 Stack 的初始化做得非常干净。但问题在于它管的事太少了——只负责把你从零带到项目能跑起来这个状态之后的开发过程几乎是空白。1.1 脚手架时代的局限性用 create-t3-app 初始化一个项目你会在终端里看到几个经典问题用哪个包管理器、要不要 Tailwind、要不要 tRPC、要不要 NextAuth、数据库用哪个——全部选完项目骨架就有了。这一步确实爽跑通npm run dev只要几秒钟。可真正进入开发阶段后你会发现每加一个业务模块你都得手动重复一套流程先在 Prisma schema 里加模型再跑 migration然后去server/api/routers下面建一个 tRPC router 文件定义 input 的 Zod schema、写 query 和 mutation最后回到前端页面里把api引用挂上。一天下来大部分时间不是在写业务逻辑而是在做这些高度模板化的胶水工作。这些工作每一件单独看都不难但加起来非常磨人而且特别容易出错——比如忘记给 router 挂到 appRouter 里或者 input 类型对不上类型检查直接给你报一排红。T3 栈的定位是简单、类型安全、可扩展但这个理念在开发效率上一直没有特别好的工具来落地。t3code 就是冲着这个空子来的——它把从模型定义到API 路由再到前端调用这一整条链路的样板代码自动化了。1.2 t3code 的实际定位我一开始以为 t3code 只是 create-t3-app 的马甲用了几次才发现不是一回事。它更像一个持续生成的脚手架项目初始化只是它的一个子命令更常用的是后面那几个generate命令——你只需要描述清楚要什么它就把对应的代码文件生成到正确的位置。打个比方create-t3-app 相当于给你一块已经划好线的地皮水电都通了但房子得自己一砖一瓦砌。t3code 更像是给你一个模板浇筑机你想好房间长什么样按下按钮墙体、门窗、管线槽位自动成型。你剩下的工作是装修和软装——也就是写真正的业务逻辑。对这个工具感兴趣的我建议先搞清楚它的边界它帮你写的是项目中高度重复的声明式代码比如模型定义、路由结构、CRUD 接口至于订单金额超过阈值走优惠逻辑这类业务判断它做不了也不该做。想清楚这一点你对它的期待就不会跑偏。2. 安装环节最容易翻车的几个点Node 版本与包管理器选择安装这一步看起来简单实际上我见过不少人在最开始就卡住。t3code 对运行环境有硬性要求而且不同的安装方式后续行为差别挺大。2.1 Node 版本要求与检查方法t3code 生成的代码基于 Next.js 15 和 Tailwind CSS 4这两个库对 Node 版本有明确要求。实测下来Node 20.19 或 22.12 才能保证所有生成代码在编译层面不出问题。如果你用的是 Node 18项目能创建但装依赖时可能报ERR_UNSUPPORTED_NODE_MODULES_VERSION或者在 dev server 启动时报 WebSocket 相关的错误。我建议在安装前先确认版本终端里跑一下node -v如果版本低于要求最省事的方案是用nvm切一个 LTS 版本。不要想着我的 Node 18 跑别的项目都没问题——别的项目没问题是它们没有用到 Next.js 15 的新特性不代表 t3code 生成的项目也能侥幸通过。2.2 全局安装还是 npx 临时调用t3code 提供两种使用方式这个选择直接影响你日常的工作流。全局安装的优点是命令短输入快npm install -g t3code t3code init my-app用npx则是每次都拉取最新版本npx t3codelatest init my-app我个人后来改成了全局安装。因为 t3code 不是一次性工具我几乎每天都会用到generate系列命令全局装好之后这些命令执行更快也省得每次等 npx 去检查 registry。全局安装唯一要注意的是版本更新——它更新速度不慢建议隔一两周手动升一次级npm update -g t3code2.3 用 npm 还是 pnpm这个我要多说两句。t3code 在初始化交互界面里会让你选包管理器。如果你没有特别的偏好推荐选 pnpm。原因不只是安装速度快更重要的是 Next.js 15 和 Tailwind CSS 4 对依赖的链接方式比较敏感pnpm 的隔离式 node_modules 结构在这些新版本生态下反而更稳不会出现幽灵依赖导致的类型错误。当然这不代表 npm 不能选。我另一个项目的团队成员全用 npm跑起来也没问题。但要注意一点选定包管理器之后尽量别中途切换。t3code 生成的 lock 文件package-lock.json 或 pnpm-lock.yaml是按你初始化的选择生成的如果一个人用 pnpm 初始化另一个人用 npm 装依赖最直接的后果是node_modules结构不一致轻则启动变慢重则 Tailwind 的 PostCSS 插件报错。在一个团队里这是个容易忽略的隐性约定。# 安装依赖在项目根目录执行 pnpm install首次安装一般需要 30 到 90 秒取决于网络状况。这一步走完项目的基本运行条件就具备了大半。3. 从零生成一个完整项目交互选项与目录结构的深度解读初始化项目是 t3code 最直观的功能但如果你只是机械地回答它的每一步提问可能会错过一些重要选项的含义。这里我用一次真实的初始化流程展开讲。3.1 初始化命令与每个选项的实际影响创建一个新项目命令很简单t3code init store-demo接下来它会问你几个问题下面是我这次实测的选择和对应影响包管理器我选了 pnpm。前面说过原因。数据库选择选项里有 SQLite、PostgreSQL、MySQL。这一步决定 t3code 往项目里放什么 ORM 配置和依赖。SQLite 适合本地玩或者原型验证PostgreSQL 适合要上生产环境的项目。注意它不会自动帮你建数据库只是生成连接配置和 schema 示例。启用主键策略这个比较有意思是它新增的选项问你是用自增 ID 还是 CUID。建议直接选 CUID因为它生成的 ID 在分布式场景和对外暴露 API 时都有更好的安全性不容易被遍历猜测。认证方案可选 none、NextAuth、自定义 JWT。选 NextAuth 会同时生成server/auth.ts和对应的sessionprocedure 封装。启用 AI 辅助生成这是 t3code 区别于传统脚手架的一个功能。开启后你在后续的generate命令里可以描述得更随意比如创建一个带分页的产品列表查询它会根据描述生成更贴合意图的代码而不是死板地套模板。全部确认后它会开始拉模板、装依赖并在结束后提示你进入项目目录启动。这个过程大概两分钟但比 create-t3-app 多了一个细节——它会在关键文件顶部写入一行注释标明这个文件是由 t3code 生成的提醒你后续如果手改要注意哪些区域是受管区域。3.2 生成后的目录结构哪些文件可以改哪些不建议碰项目跑起来之后我建议先别急着写代码打开src/server/api/routers看一眼。t3code 在这里生成的 router 文件结构非常规整每个 router 里都预置了三个示例 proceduregetAll、getById、create。这三个示例就是后续所有 CRUD 代码的模板。目录结构大致如下store-demo/ ├── prisma/ │ └── schema.prisma # 数据库模型t3code 会维护这里 ├── src/ │ ├── server/ │ │ ├── api/ │ │ │ ├── root.ts # 汇总所有 router 的地方手动加 router 容易漏 │ │ │ └── routers/ # 每个业务模块一个文件 │ │ ├── db.ts # Prisma Client 实例 │ │ └── auth.ts # 认证配置如果启用了 │ ├── components/ # React 组件 │ └── app/ # Next.js App Router 路由 └── package.json有一个关键文件需要注意src/server/api/root.ts。第一次用的时候我在这个文件上踩了坑。手动在routers目录下新建了一个coupon.ts写好之后发现前端怎么调用都是 404查了半天才意识到没有把它挂载到root.ts的appRouter里。t3code 的generate命令会自动处理这个挂载动作但如果你选择手写这就是第一个要踩的坑。3.3 生成后第一件事验证基线正常我每次初始化完不会急着往上加模块而是先跑三件套pnpm dev # 启动开发服务器 pnpm typecheck # 类型检查 pnpm lint # 检查代码规范确保在没写任何业务代码之前项目本身是干净的。这样后面如果出了问题能确定是不是自己加的代码引入的。这是个非常基础但有效的习惯。三个命令都通过后项目基线就是正常的可以开始用generate命令让它干活了。4. 持续生成能力的真正价值tRPC 路由与 Prisma 模型怎么被自动化t3code 的generate命令是我切换过来的核心原因。它把我在前面说的那些重复劳动压缩成了一条命令。但要想用好它你需要理解它在背后到底做了什么——否则生成出来的代码一旦不符合预期你都不知道从哪儿下手改。4.1 一次路由生成背后的完整链路假设我要给一个商城项目增加优惠券模块。传统的做法是手动做四件事写 Prisma 模型、写 Zod schema、写 tRPC router、注册到 appRouter。用 t3code 只需要一条命令t3code generate route coupon它会自动做下面这几件事在prisma/schema.prisma里追加一个Coupon模型字段会在交互中问你要不要加基础字段name、code、discount、expiresAt 等在routers/coupon.ts里生成完整的 router包含getAll、getById、create、update、delete五个 procedure为每个 mutation/query 生成对应的 Zod input schema更新root.ts把couponRouter挂载进去。这四步以前手动做至少十分钟现在一条命令几十秒完成而且不会出现忘了挂 router这类手误。生成完你打开coupon.ts会发现getAll这个 query 已经自带分页和排序参数了。这是我特别满意的一点——它默认的落点很务实不是只有一个干巴巴的findMany。4.2 生成代码的质量评估它是生成还是拼装可能有人会问这种生成出来的代码质量到底行不行我用了一段之后的看法是它给出的是一个质量优良的起点而不是最终形态。以生成的createprocedure 为例它长这样export const create protectedProcedure .input( createCouponSchema, ) .mutation(async ({ ctx, input }) { return ctx.db.coupon.create({ data: input, }); });你会发现它默认用了protectedProcedure——也就是说这个接口需要登录才能调。这个默认行为在大多数业务场景下是安全的但反过来讲如果你的优惠券创建接口本来就是面向管理后台的那你需要在middleware里再加上管理员角色校验。我的意思是生成的代码安全底线很高但业务权限出生后你要自己动手补。4.3 生成查询页面时的模板规则除了路由之外t3code 还能生成前端页面命令是t3code generate page product/list这个命令会在src/app/(routes)/product/list/page.tsx生成一个服务端组件里面预设了用api.product.getAll取数据并渲染表格的逻辑。比较贴心的是它生成的表格组件支持 columns 配置你只要声明列名和数据字段的映射关系表格就能跑起来。不过我这里要提醒一个经验生成的页面代码更适合当起点而不是直接上线用的成品。因为让模板预测你想要的 UI 细节几乎不可能——它生成的表格有排序、有状态但没有空态提示弹窗这类产品层面的东西。所以我的用法是页面文件生成后把保存下来的 API 调用和数据类型定义往自己的组件里搬UI 层还是自己写。这样做的好处是类型定义和 API 封装这种容易出错的部分由生成器保证一致性纯 UI 的部分保留自己的自由度。4.4 模型变更同步机制改完 Prisma 后的联动日常开发中光靠一条命令不够。比如你有一个coupon模型用了几天之后发现需要一个maxUsageCount字段。这个时候你不需要重新生成整个路由正确的做法是t3code update model coupon这个命令会重新读取schema.prisma里的Coupon模型把maxUsageCount字段增量合并到已有的 Zod input 和 create/update procedure 里它不会动你手写的业务逻辑。这是它设计上比较成熟的地方更新的定位是增量修补而不是推翻重建。用它之前切记先手动改好schema.prisma因为它是唯一的数据源——先改模型再同步代码这个顺序别反。5. 实际项目中集成第三方服务的完整配置数据库与认证模块工具生成骨架只是第一步真正让项目变得能用的是接入基础设施。这里我把数据库和认证这两个最常碰到的部分说透。5.1 PostgreSQL 连接配置与迁移流程我最初为了省事选了 SQLite后来项目要部署到线上只好迁到 PostgreSQL。这里直接说迁移后的配置经验。打开prisma/schema.prisma把 provider 改成 postgresql然后修改.envDATABASE_URLpostgresql://user:passwordlocalhost:5432/store_demo?schemapublic注意一点t3code 初始化时生成的 schema 里模型名是带引号的比如model Coupon。如果你的模型名用了驼峰务必在 Prisma 里用map和map把数据库里的表名、字段名映射成自己想要的格式。否则 PostgreSQL 默认的双引号小写规则会让你的表名变成带引号的驼峰后续用 SQL 直接查表时很容易被坑。这是我在生产库上踩过的实实在在的教训。配置改完执行迁移pnpm prisma migrate dev --name init迁移完成后建议跑一次t3code sync命令它会重新扫描 Prisma schema把数据库的最新结构同步到代码层的类型定义里。这个同步步骤很重要能避免出现数据库有字段、但 tRPC 的 input 类型里没有的错位情况。5.2 接入 NextAuth 时 t3code 的半自动模式如果你初始化的时候选了启用认证t3code 会在src/server/auth.ts里生成 NextAuth 的配置骨架。这个骨架包含了 GitHub 和 Google Provider 的占位配置你只需要填CLIENT_ID和CLIENT_SECRET。但真正要注意的是它把 session 接入 tRPC 的方式。打开src/server/api/trpc.ts你会看到createTRPCContext已经默认从 NextAuth 的getServerSession里读取 session然后把 session 填进 context。这带来的直接好处是你在任何 procedure 里都能直接用ctx.session.user不需要再手动从请求头解析 token。export const protectedProcedure t.procedure.use( ({ ctx, next }) { if (!ctx.session?.user) { throw new TRPCError({ code: UNAUTHORIZED }); } return next({ ctx: { session: { ...ctx.session, user: ctx.session.user }, }, }); }, );这段代码就是 t3code 生成的protectedProcedure的核心。它做的事很明确如果 session 里没有 user直接抛 UNAUTHORIZED 错误有 user 才继续往下走。生成的代码没什么黑魔法但把认证集成到 RPC 层这件事它替你做了省得你自己去设计上下文注入逻辑。5.3 第三方包与原生成代码的冲突处理接第三方服务的时候最容易出问题的点是代码生成器对db的单例管理。t3code 生成的src/server/db.ts是一个全局单例 PrismaClientconst globalForPrisma globalThis as unknown as { prisma: PrismaClient | undefined; }; export const db globalForPrisma.prisma ?? new PrismaClient(); if (process.env.NODE_ENV ! production) { globalForPrisma.prisma db; }这个全局单例模式是从 Next.js 官方实践里继承过来的目的是防止开发环境下热更新导致多个 PrismaClient 实例。但如果你引入了一个需要访问 Prisma 的第三方库比如某个事务队列库它可能会自己再 new 一个 PrismaClient导致连接数翻倍。应对方案统一用src/server/db.ts里导出的db实例去注入第三方库不要让它自己实例化。这个经验是接一个定时任务调度库时踩出来的当时数据库连接数莫名其妙涨了一倍有余排查半天才发现是两个实例在各自建连接。6. 生产环境里的疑难杂症从类型丢失到 Edge Runtime 运行失败把 t3code 生成的项目推到生产环境你会遇到一些开发环境完全感知不到的问题。这一节直接列出我在部署中踩过的坑和排查思路希望给你省点时间。6.1 类型断言失效与 T4 编译警告第一个坑出现在部署流水线里跑pnpm build的时候。生成的代码里有一处as断言在本地pnpm dev和pnpm typecheck下完全正常但一上 CI 就报 T4 编译警告大意是断言成立的可能性很大但不完全保证。问题的根源是 t3code 生成 Zod input schema 时对可选字段使用了.optional()但对应的 TypeScript 类型在某个边缘分支里没有完全对齐导致断言不完全严格。排查过程是这样的先执行pnpm typecheck锁定具体文件和行号打开对应的 input schema 文件确认maxUsageCount这类可空字段的定义把.optional()改成.nullish()并调整类型为number | null重新执行pnpm build警告消失。这种问题不是 t3code 独有的任何生成代码加手改就可能造成类型层面的细微偏差。排查思路比具体操作更重要先锁定行号再查 schema 定义最后调类型边界三步走。6.2 开发环境热更新失效一个隐蔽的配置坑有一次我在开发环境改了schema.prisma然后跑t3code update model发现页面怎么刷新都不更新数据。当时第一反应是生成器把它自己的缓存搞坏了排查了半天最后发现根因跟 t3code 一点关系都没有——是我自己的next.config.ts里设置了output: standalone这个配置对热更新的监听目录有影响导致 Prisma Client 变更没有触发重新编译。这个案例给我的教训是排查问题的时候先怀疑自己手写的配置再怀疑工具的固有行为。t3code 再强也只是生成代码而你手写的next.config.ts直接影响 Next.js 的运行时行为。正确的排查顺序应该从自己的配置文件开始别一上来就报 bug。6.3 Edge Runtime 下 Prisma 不可用的深度分析这个坑是我觉得最值得分享的因为它涉及架构层面的理解。t3code 生成的项目默认使用 Node.js Runtime但如果你手动给某个路由加了export const runtime edge;那么只要这个路由组件里直接或间接触发了 Prisma 查询运行时就必然报错。原因在于 Prisma Client 依赖 Node.js 的二进制库在 Edge RuntimeV8 隔离环境里没法执行。排查思路是这样的首先确认页面是否加了 Edge 指令其次看页面是否调用了 server action 或 tRPC query再看这些调用链上是否引用了 Prisma。我遇到的情况是项目里一个 public 页面为了提高 TTFB 手贱加了 Edge runtime结果它用的 tRPC 调用链路里包含了一个走 Prisma 的 procedure部署上去之后这个页面直接 500。解决方案很简单把给普通页面加 Edge runtime 的想法彻底放下。Next.js 的 Edge Runtime 更适合特判类场景如 A/B testing 分流不适合跑数据库业务。凡是需要查数据库的路由统一使用 Node.js Runtime这是生产环境配置里最重要的一条边界。到这里从安装、初始化、代码生成到生产环境排坑这条线已经全走完了。最后再分享一个小习惯我每次用 t3code 生成完代码都会亲自把生成的 router 文件逐行读一遍不跳读。生成器能帮你把代码写出来但只有你自己读过你才真正知道这段代码接下来该怎么改、改哪里。它不是替你写代码而是帮你省掉打字的体力活剩下的脑力活还是得自己干。