t3code:基于T3 Stack的全栈脚手架与代码生成实战指南

发布时间:2026/10/26 8:40:10
t3code:基于T3 Stack的全栈脚手架与代码生成实战指南 1. 项目概述t3code 是什么把t3code拆开来看t3指的是社区里已经很成熟的 T3 技术栈Next.js、TypeScript、Tailwind、tRPC再加上 Prisma 和 NextAuth 这两根常用支柱code才是重点——它既是一套工程规范也是一台帮你把业务代码“生成”出来的工具。我自己的理解就是这是一套以 T3 技术栈为核心的全栈项目脚手架同时随附一套能按模块自动生成 CRUD 代码、页面、组件和路由封装的生产力工具。这套东西解决什么场景很简单开新项目时最怕的不是写业务逻辑而是花两个多小时在项目根目录里反复配置next.config、tailwind.config、tsconfig.json、Prisma 初始化、NextAuth 接入、tRPC 路由挂载。哪怕你已经熟练到背得出大部分配置项每次重复仍然是对耐心的消耗。t3code把这部分前置工作收拢成一条npx t3codelatest new my-app命令交互式选择技术偏好后就能得到一个结构清晰、类型链路完整、能直接pnpm dev跑起来的项目骨架。更进阶的价值在于“模块生成器”。工程初始化只是把篮子编好真正的内容还得靠业务模块往里填。t3code允许你用generate子命令一次生成一个完整业务模块比如博客、商品、账单、工单生成范围包括 Prisma 数据模型、tRPC Router、Zod 输入校验、列表页、详情页、表单组件和对应客户端 hooks。生成的代码风格统一、命名一致还能自动完成 router 注册这类最容易遗漏的接线工作。所以它比普通的 GitHub 模板更聪明相当于从“下模板”升级到了“按需生成”。这篇内容主要写给三类人准备用 Next.js 做全栈项目的新手想把团队多项目基线统一起来的工程化负责人以及正在学 T3 Stack 并希望理解“类型安全如何穿透全链路”的进阶者。我会把技术选型的逻辑、初始化实操、模块生成器的用法、常见故障排查全部过一遍尽量把我踩过的坑和总结出来的习惯也一并写清楚。看完之后你至少能在一小时内把一个带数据库、带认证、带若干个可操作业务模块的应用跑起来。1.1 项目背景与目标T3 技术栈在开发者社区里流行核心不是因为它用了多前沿的技术而是它非常“克制”。它不强制你用服务端渲染、不逼你上 monorepo、也不要求你在微服务和单体之间做复杂的架构抉择。它希望用最少的概念完成“前端页面 后端逻辑 数据库读写”的闭环并且让 TypeScript 的类型穿过每一层。但现实是很多人即便认同 T3 技术栈的理念真正上手时还是会在“配置”这道坎上卡住。比如 Next.js 14 的 App Router 和 Server Component 的边界在哪里、use client该加在哪个组件、tRPC 的 route handler 如何挂到app/api/trpc/[trpc]/route.ts、NextAuth 的 session 如何注入到 tRPC 的 context 里。这些如果靠查文档硬啃半天就搭进去了如果靠复制别人的模板又容易继承一箩筐无关代码和历史包袱。t3code项目的目标就是把这一整条链路的最佳实践固化下来并且用命令行把“约定优于配置”落地。你不需要理解每一步底层原理也能得到经过了大量真实项目验证的骨架。这不是让你放弃理解原理而是让工具替你解决确定性部分把脑力花在真正有挑战的业务设计上。1.2 为什么叫 t3code这个名字其实点出了两层含义。第一层是技术栈t3就是 T3 Stack 的简称意味着生成的工程默认围绕 Next.js TypeScript Tailwind tRPC 这一组合来构建。第二层是生产力工具code不仅指项目代码也暗示“生成代码”的能力。我一直觉得一个好的脚手架应该做到两件事一是开箱即用二是长期可维护。t3code用固定模板保证前者用生成器和配置文件保证后者。它不像那些把成百上千个 feature 全塞进去的重量级框架而是尽量保持精简、可替换、可理解。项目里任何一层你都可以抽出来换掉——比如不想用 Prisma 可以换成 Drizzle初始化时就有对应的选项。这符合 T3 技术栈的精神不做大而全的“全家桶”只做小而实用的组合拳。1.3 适合谁来用我实际用下来的感受是t3code在下面几个场景里尤其顺手。独立开发者做 MVP 或内部工具时没有比“一条命令起项目 一条命令建模块”更爽的体验。产品想法刚冒出来最怕被环境配置淹没t3code切掉了这一层负担你可以直接把数据库模型写出来把页面搭出来集中精力验证业务是否成立。小团队做项目统一时t3code的固定目录结构和命名规范能减少很多无意义的讨论。以前我们团队几个人各建各的项目连 router 文件放哪个目录都能吵一轮后来统一了生成基线新人看代码上手快代码评审的重点也从“风格争论”转移到了“业务逻辑是否正确”。进阶学习者也适合拿它当参考资料。因为生成出来的代码几乎没有注释、没有多余的示例业务完全是一套按照官方推荐方式组合起来的代码库很适合用来对照文档看“最佳实践长什么样”。如果你正在理解 tRPC 如何与 NextAuth 协同、Prisma client 单例怎么在 Next.js 开发环境下避免热重载时重复实例化直接读生成的代码会比看分散的文档更快。2. 技术选型解析为什么是这条技术路径2.1 T3 技术栈的核心优势先说组合里的四个成员。Next.js 负责应用框架、路由和渲染策略它的 App Router 让 Server Component 和 Server Actions 这些新能力有了用武之地TypeScript 提供全链路静态类型这是整套组合的基石Tailwind 做样式它的 utility class 方案和组件化开发天然适配tRPC 则替代了传统 REST API 层让前端直接“调用”后端函数最大程度复用类型。这一套组合的优势在于“类型穿透”。传统开发模式下数据库表结构、后端 API 的定义、前端请求的返回类型至少有三套需要手工维护的类型声明。改一个字段名后端改了前端忘了跑起来才在控制台看到undefined或者某个data.map is not a function的报错。而 T3 组合里类型从数据库到页面的路径几乎是一条直线Prisma schema 定义模型 →prisma generate生成类型 → tRPC router 返回该类型 → 前端useQuery直接推导出返回值类型。中间不存在手工对齐的环节。有人可能会问为什么不用 REST OpenAPI 生成客户端类型也不是不行但你需要额外配置 openapi-generator、维护 DTO 层还要处理请求参数和响应包装的序列化规则复杂度高出不少。对于团队内部服务或中等规模应用tRPC 这条“少一层抽象”的路径明显更顺滑。而且 tRPC 不是不能做多端复用只要保持 transceiver 按照同样的模式注册也能服务于多个前端应用。2.2 手写配置与 t3code 模板的实际差异每次介绍脚手架总有人坚持“手写一遍能更懂原理”。我不否认这一点但要看场景。如果你只做一个项目手动从头配确实能学到东西。如果你要做五个、十个项目手动配置的边际成本就非常可怕了。关键的差异在下面几个维度。目录结构上t3code把约定直接写死在模板里。src/server/db.ts就是数据库实例src/server/api/trpc.ts就是 tRPC 初始化文件src/trpc/server.ts负责服务端类型调用src/trpc/react.tsx负责客户端 hooks。任何人打开项目不用看文档也能猜出文件的职责。依赖选型上t3code锁定的是一套经过兼容性测试的版本集合。它不会给你塞一个 Next.js 15 配一个 tRPC 10那种版本错位产生的坑往往是新手最迷茫的。初始化过程中也能选择性去掉用不到的依赖项目更干净pnpm install更快。环境变量上模板自带.env.example把DATABASE_URL、AUTH_SECRET、AUTH_URL这些最基础的变量提前备好省得跑起来之后 500 报错才意识到少了环境变量。运行时还会用env.mjs做一次校验变量缺失会给出明确报错。这个设计我特别喜欢因为它把“配置错误”提前到了进程启动阶段而不是等到某个接口被调用时才暴露。2.3 端到端类型安全的落地逻辑端到端类型安全听起来玄拆开后其实就几条硬约束。第一数据库层的类型由 Prisma 负责prisma generate之后所有模型都有完整的 TypeScript 类型第二应用层接口由 tRPC 负责每个 query 和 mutation 的输入用 Zod 校验输出类型直接从返回值推导第三前端消费层由 React Query 配合 tRPC 客户端生成 hooks类型从 server 自动流到组件里。这个链路里最难理解的部分或者说最容易找不到北的部分就是 tRPC 的类型是如何从server传到client的。实际上它靠的是AppRouter这个抽象类型。在根路由文件里你把所有子 router 聚合起来然后在前端createTRPCReactAppRouter()时传入这个泛型。编译器见了这个泛型就自动知道了远端有哪些 query、哪些 mutation、参数类型是什么、返回类型是什么。有人担心类型安全绑得太紧会降低灵活性。我的观点是要分清“灵活性”和“混乱”。如果你的系统真的需要跨语言微服务、需要和第三方开放平台对接那一层 REST/OpenAPI 本身就是必要的tRPC 不适合硬塞进去。但如果是内部全栈应用类型安全不会拖慢你反而能让你在改 schema 时更放心因为编译错误会像探针一样扫出所有使用点。3. 实操过程从安装到跑起一个 demo3.1 环境准备与安装我用t3code在 Windows、macOS、Linux 上都跑过工程最推荐的环境是 Node 20 LTS pnpm。Node 18 也能跑但跑 Next.js 14 时会有一些实验警告不至于不能用只是日志看着不干净。如果当前 Node 版本太老或者太新建议用nvm切到 20。t3code本身不需要全局安装直接通过npx调用即可这样不会污染全局环境也能保证每次使用都是最新版npx t3codelatest new my-app执行后会进入交互式问答需要回答下面几个问题提示项可选值我的建议应用类型App Router / Pages Router新项目一律 App Router认证方案NextAuth / 无认证 / 自定义 JWT带登录业务选 NextAuth数据库适配器Prisma / Drizzle / 无 ORM关系型数据选 Prisma样式方案Tailwind / CSS Modules默认 Tailwind包管理器npm / pnpm / yarn / bun团队项目推荐 pnpm如果本机没有pnpm可以通过corepack enable启用或者直接选 npm两种方式生成的代码没有区别。我之所以更推荐pnpm是因为它在多项目场景下会复用全局缓存磁盘占用低安装速度也稳定。3.2 初始化项目实战我实际用一个叫做my-app的项目来演示整个流程。初始化时选择 App Router、NextAuth、Prisma、Tailwind、pnpm生成后的目录结构大致如下my-app/ ├── prisma/ │ └── schema.prisma ├── src/ │ ├── app/ │ │ ├── api/trpc/[trpc]/route.ts │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── globals.css │ ├── server/ │ │ ├── api/ │ │ │ ├── root.ts │ │ │ ├── trpc.ts │ │ │ └── routers/ │ │ │ └── post.ts │ │ ├── auth.ts │ │ └── db.ts │ ├── trpc/ │ │ ├── client.ts │ │ ├── react.tsx │ │ └── server.ts │ ├── env.mjs │ └── middleware.ts ├── .env.example ├── .eslintrc.cjs ├── next.config.mjs ├── package.json └── tsconfig.json注意.env.example里有几个关键变量需要填写# Prisma DATABASE_URLpostgresql://postgres:passwordlocalhost:5432/my_app?schemapublic # NextAuth AUTH_SECRETchange-me-to-a-random-32-char-string AUTH_URLhttp://localhost:3000复制成.env之后把数据库连接串改成你自己的。如果没有本地 Postgres可以用 Docker 快速起一个docker run --name my-app-db \ -e POSTGRES_PASSWORDpassword \ -p 5432:5432 \ -d postgres:16然后安装依赖并启动pnpm install pnpm db:push pnpm dev这里解释一个细节pnpm db:push是直接把 Prisma schema 推送到数据库适合初期快速验证。如果之后需要维护迁移历史改用pnpm prisma migrate dev --name init。3.3 数据库模型与 Prisma 的实际连接模板里默认提供的模型分两部分一是 NextAuth 所需的User、Account、Session模型二是给业务示例用的Post模型。你可以打开prisma/schema.prisma看看:generator client { provider prisma-client-js } datasource db { provider postgresql url env(DATABASE_URL) } model User { id String id default(cuid()) name String? email String? unique emailVerified DateTime? image String? accounts Account[] sessions Session[] posts Post[] } model Post { id String id default(cuid()) title String content String? published Boolean default(false) authorId String author User relation(fields: [authorId], references: [id]) createdAt DateTime default(now()) updatedAt DateTime updatedAt }这个 schema 的核心值得多看两眼。User和Post是一对多关系posts数组和author关系字段必须成对出现updatedAt会在每次更新时自动写入当前时间你不用在业务代码里手工维护。新增字段后跑一下pnpm prisma migrate dev --name add_description生成迁移文件并同步数据库。我第一次用这种模式时最不习惯的是改完 schema 之后必须重新prisma generate类型才会更新。如果你正在编辑器里写代码prisma client 的类型不会自动刷新必须跑命令。t3code在package.json里的postinstall脚本中一般会自动执行prisma generate所以装完依赖那一刻类型就绪。但后续手动改 schema 时别忘了这一步。3.4 跑通第一个 tRPC API 流程初始化成功后src/server/api/routers/post.ts会预置一个简单的postRouter里面包含一个createmutation 和一个listquery。它的作用不仅仅是示例还是展示“类型安全链路”的最小闭环。import { z } from zod; import { createTRPCRouter, publicProcedure } from ~/server/api/trpc; import { db } from ~/server/db; export const postRouter createTRPCRouter({ list: publicProcedure.query(async () { return db.post.findMany({ orderBy: { createdAt: desc }, take: 20, }); }), create: publicProcedure .input(z.object({ title: z.string().min(1), content: z.string().optional() })) .mutation(async ({ input }) { return db.post.create({ data: { title: input.title, content: input.content }, }); }), });前端想要调用它只需要在客户端组件里用api.post.list.useQuery()类型全部自动推断use client; import { api } from ~/trpc/react; export function PostList() { const { data: posts, refetch } api.post.list.useQuery(); const createPost api.post.create.useMutation({ onSuccess: () refetch(), }); return ( ul {posts?.map((post) ( li key{post.id}{post.title}/li ))} /ul ); }跑完这个流程你就已经把整套全栈闭环打通了。不用写 fetch、不用定义 API 路径也不用在前后端之间手写类型这个体验一旦适应真的回不去传统 REST 那套繁琐的开发方式。4. 核心功能实操代码生成与工程扩展4.1 用 t3code 快速生成业务模块项目跑通之后关键的提效时刻来了。假设我要做“博客”模块执行pnpm t3code generate module blog它通常会问你是fullstack还是api-only选fullstack会生成整套前后端代码。生成器自动完成下面这些事在schema.prisma中加入Blog模型关系会自动挂到User上生成一个迁移文件但不会自行执行需要你手动prisma migrate dev生成src/server/api/routers/blog.ts包含 list、getById、create、update、remove 五个过程在src/server/api/root.ts中自动注册blogRouter在src/app/blog下生成列表页、详情页、新建/编辑页生成对应的客户端组件比如BlogForm、BlogList、删除按钮等生成出的blog.ts路由大致长这样import { z } from zod; import { createTRPCRouter, protectedProcedure, publicProcedure } from ~/server/api/trpc; export const blogRouter createTRPCRouter({ list: publicProcedure.query(async ({ ctx }) { return ctx.db.blog.findMany(); }), create: protectedProcedure .input(z.object({ title: z.string().min(1), body: z.string().min(1), })) .mutation(async ({ ctx, input }) { return ctx.db.blog.create({ data: { title: input.title, body: input.body, authorId: ctx.session.user.id, }, }); }), });这个过程最大的价值是“权限默认被处理了”。create用了protectedProcedure没有登录的用户根本无法调用你不需要再写任何手动 session 判断。当然这只是一个合理的默认值如果你希望某个接口允许匿名访问改成publicProcedure即可。4.2 配置项速查表t3code支持在项目根目录放一份t3code.config.ts来定制生成行为。我一般会这样写一份最小配置import { defineConfig } from t3code; export default defineConfig({ database: { orm: prisma, provider: postgresql, }, auth: { provider: next-auth, }, naming: { routerFile: camelCase, componentFile: kebab-case, }, crud: [list, getById, create, update, remove], reactQuery: true, });配置项作用补充说明database.orm选择 ORM 类型目前默认 Prisma预留 Drizzle 接口auth.provider选择认证方案影响生成路由时是否使用 protectedProcedurenaming.routerFilerouter 文件命名风格建议camelCasenaming.componentFile组件文件命名风格建议kebab-casecrud生成哪些 tRPC 过程可以只生成list和getByIdreactQuery是否生成客户端 hooks关闭后只生成 server 类型我建议团队把这份配置纳入代码审查范围而不是让它成为“某个人电脑上的魔法文件”。配置文件一旦定下来生成的代码风格就锚定了之后每次generate都会保持一致。4.3 自定义模板内置模板覆盖不了团队特定场景时t3code允许自定义 Handlebars 模板。把模板放到.t3code/templates目录然后在生成时指定模板名即可。比如我们团队所有删除按钮都要求带二次确认那我就可以写一个remove-button.hbsexport function {{name}}RemoveButton({ id }: { id: string }) { const remove{{name}} api.{{module}}.remove.useMutation(); return ( button onClick{() { if (window.confirm(确定删除这条记录)) { remove{{name}}.mutate({ id }); } }} 删除 /button ); }然后生成时这样指定pnpm t3code generate component BlogRemoveButton --template remove-button这里有个容易踩的坑模板变量名的转换规则。模块名blog传入{{name}}时生成器默认会帮你转成Blog用于组件名转成blog用于变量名。但如果你在模板里直接使用{{name}}而不做命名约束很容易生成blogRemoveButton这种不够规范的结果。我的建议是自定义模板里明确用{{pascalName}}、{{camelName}}这类预置变量而不是依赖手写大小写。4.4 构建与部署阶段的注意事项t3code生成的工程在本地跑通后部署到服务器前有几个点要检查。先看看.env里是否设置了NEXTAUTH_URL或AUTH_URL在服务器上要改成实际域名不要用localhost。数据库连接串也一样生产环境要用云数据库的连接地址并确保端口能从服务器访问。再看构建命令。通常执行pnpm build如果构建过程中报“Page not found”或者“Module not found: Cant resolve /xxx”十有八九是tsconfig.json里的paths配置缺失或者baseUrl不对。t3code默认配好了/*指向src/*但如果有人手工删了或改动了问题就来了。最后是静态资源路径。如果你把应用部署到子路径下比如https://example.com/app需要在next.config.mjs里配置basePath: /app否则页面资源和 API 请求路径都会不对。5. 常见问题与排查技巧实录5.1 App Router 与 Pages Router 选错后的迁移成本初始化时很容易一时冲动选 Pages Router因为它看起来更“稳定”。但跑到中途想用 Server Component、next/image新特性时就会后悔。我在一个项目里被迫迁移过那一次收获是Pages Router 的getServerSideProps逻辑迁移到 App Router 并不简单你得把每个页面拆成 Server Component 子组件数据获取函数要么放进 Server Component 里直接查询要么用 Server Actions 包装。所以我的建议很明确新项目直接选 App Router。Pages Router 兼容 Sequelize 那样很多旧生态但现在 Next.js 的核心演进都在 App Router 上潮流很明确。如果已经生成错了还是早点重建项目目录搬过去越大改越痛。5.2 tRPC 类型更新不及时这是t3code项目里遇到频率最高的问题改了router.ts中的 query 返回类型前端useQuery的类型不变甚至编辑器还报旧错误。多数情况不是代码的问题而是 Next.js 和 React Query 的类型缓存没刷新。我的标准排障三步rm -rf .next rm -rf node_modules/.cache pnpm dev如果还不行检查tsconfig.json中的include是否覆盖了src目录以及tsc是否用的是 Next.js 自带的构建流程。tRPC 的类型推导依赖root.ts里AppRouter的类型聚合偶尔会因为 IDE 服务没有重载而滞后重启 TypeScript 服务一般就能解决。5.3 Prisma 引擎和 Node 版本不匹配我踩过一个比较隐蔽的坑本地 Node 版本太新比如 23运行prisma generate时会出现 “unexpected data” 或引擎崩溃。Prisma 对 Node 版本要求比较保守LTS 永远是最稳的选择。这时候用nvm use 20切回 LTS 再重新prisma generate就好。有人会尝试设置PRISMA_ENGINES_MIRROR之类的环境变量去手动指定引擎源但这属于临时方案且不一定能解决版本兼容问题。我更建议从根上统一团队 Node 版本配合.nvmrc文件大家在同一版本下协作。5.4 Docker 化部署时的数据库连接串问题t3code生成的模板开发时直接用localhost连接数据库但如果把应用也容器化容器里的localhost指的是容器自身不是宿主机。这时候数据库连接串要写成宿主机 IP或者在同一个docker-compose.yml里用服务名互访。例如DATABASE_URLpostgresql://postgres:passworddb:5432/my_app这里的db是 compose 里 PostgreSQL 服务的名称。另一个常见问题是容器启动时数据库还没就绪导致prisma migrate失败。一种处理方式是在启动脚本里循环检测数据库可用性或者用t3code模板里推荐的 migrate start 流程让应用启动前先跑迁移。5.5 依赖安装慢或源异常用 npm 安装全栈项目时常常会遇到依赖安装极慢或者某个包拉不下来。我一般先用pnpm它解析依赖更快而且在同一台机器上多项目共享缓存。如果网络延迟还是很高可以查一下当前 registry 配置pnpm config get registry npm config get registry如果指向了不稳定的公共源可以考虑切换到更快的源但我不建议在package.json里写死 registry那会让其他同事克隆后一脸困惑。提醒不要在package.json里手写一堆resolutions去强制升级某个子依赖的版本除非你非常清楚它在供应链里所有依赖方的兼容性。我曾为修一个漏洞强行固定某个 Next.js 子依赖版本结果把 SWC 相关的包弄崩了构建直接失败。让依赖保持官方版本组合是更稳妥的选择。6. 我的实操经验与使用建议从第一次用t3code到现在它已经成了我几个个人项目和团队项目的默认起点。我最大的体会是它解决的不是“怎么写代码”的问题而是“别把时间浪费在重复搭建”的问题。真正有创造性的部分仍然在自己手里数据模型怎么设计、业务规则怎么划分、权限边界怎么定这些一点都不会被工具替代。如果让我给团队建议我会强调三点。第一把t3code.config.ts和schema.prisma看成项目里最重要的源文件业务代码反而是派生产物。源文件稳定生成的代码才稳定。第二自定义模板不要一上来就追求大而全先固化两三个真正高频的模块用顺手了再逐步增加别把生成器变成第二个需要维护的巨型系统。第三版本升级一定要看t3code的更新记录Next.js、tRPC、Prisma 这几个依赖升级节奏不一致升级前不确认兼容性生成出来的代码可能就“跑得起来但类型全乱套”。最后分享一个小技巧t3code generate module支持通过--crud list,getById这种参数来精准控制生成范围不需要每次都生成全套。比如某些只读业务模块我只想要列表和详情就不让它生成一堆表单省得之后还要删。这个选项在配置上也对应crud字段团队成员可以按模块类型各取所需。工具用顺手的标志就是“你几乎忘了它的存在”t3code对我来说已经到这个状态了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询