前阵子有个朋友准备开新项目,跑过来问我脚手架怎么选。他说团队里有人有React背景但没正经写过TypeScript,又要一两周内拿出一个带登录、带数据库的可用版本。我几乎没犹豫,直接甩给他create-t3-app,也就是社区里常说的t3code。这个脚手架我用了一年多,前后做了好几个全栈项目,今天这篇就把我对它的理解、实际初始化流程、以及踩过的坑一次性写清楚,给正准备上车的朋友做个参考。
T3 Code本质上不是一个框架,而是一套技术栈组合加一个官方脚手架工具。它默认帮你把Next.js、TypeScript、Tailwind CSS、Prisma、NextAuth、tRPC这些工具串在一块,所有配置都提前调好,交互式命令行选一选,几十秒就能生成一个前后端类型完全打通的全栈项目。这年头写全栈应用,最烦的不是功能逻辑,而是类型不一致、配置东拼西凑、数据库和前端各说各话,T3 Code解决的恰恰是这三件事。
适合什么人用?如果你是独立开发者、小团队,或者想快速验证产品原型,这套组合非常能打。它默认帮你做对的决策,不需要你在工程化上耗费太多精力,更不会让你在项目初期就陷入“应该用哪个状态管理库”这种无休止的争论。
1. T3代码到底是什么:从名字到设计哲学
1.1 t3code、T3 Stack和create-t3-app的关系
先说名字。T3 Stack最早是Theo发起的,T3这个缩写可以理解为“Three Ts”,指的是TypeScript、Tailwind CSS和tRPC这三个以T开头的核心工具。不过在社区传播过程中,大家习惯把整套基于Next.js的全栈初始化方案都叫T3 Code,搜索的时候你会发现有的文章写t3code,有的写create-t3-app,其实指向的都是同一个东西——一个帮你把T3技术栈跑起来的脚手架工具。
这个命名上的小混乱并不影响使用。你只需要记住:T3 Code = 文档友善的说法;create-t3-app = 你实际要执行的命令。两者是同一套方案的称呼。
1.2 这套方案的设计理念
T3 Code有一句很有名的话,叫“type-safe and simple”,翻译过来就是“全类型安全且简单”。它不是要把所有工具强行捆绑成一套ORM框架,而是强调“可增量采用”——你既可以全套使用,也可以只用其中某一个组件。这个设计理念非常务实,因为全栈开发最大的痛点从来不是某个细节写不出来,而是整个链路上类型系统彼此割裂:数据库字段改了,后端接口不知道;后端接口改了,前端调用的地方全部崩溃。
T3 Code的思路是从三个层面消灭这种割裂:
- 数据模型、ORM和数据库之间通过Prisma schema统一类型;
- 后端API和前端调用之间通过tRPC自动推导类型;
- 全栈语言统一用TypeScript,从编辑器到浏览器、到Node层、到数据库类型定义,所有环节共享一套类型心智模型。
在实际体验上,最直观的感受是:改一个接口返回结构,前端页面会立刻出现红色波浪线提示,而不是等到运行时接口报错才反应过来。这种“编译期发现所有错误”的体验,一旦你习惯了就回不去了。
2. 核心组件逐个拆解:为什么是这几个工具
2.1 TypeScript是全栈的粘合剂
很多朋友对TypeScript的态度是:能用就行,类型能省就省。但如果你做的是需要长期维护的项目,这套想法会拖垮你。TypeScript让代码从“口头约定”变成了“纸质合同”。接口返回什么、数据库字段是什么、组件props是什么,到处都有明确的类型定义。重构的时候,你不用满脑子记住哪里调用了这个函数,改完类型定义,编辑器会自动帮你标记出所有需要改的地方。
在T3 Code里,TypeScript不是可有可无的装饰,而是整个方案的骨架。tRPC调用的类型推导依赖它,Prisma生成的数据库类型依赖它,Next.js的页面props类型检查也依赖它。所以脚手架初始化的时候把TypeScript作为默认必选项,没有任何商量余地。
2.2 Tailwind CSS:样式的选型很聪明
前端框架最容易被忽视的一环是样式方案。Tailwind CSS选得很有眼光,因为它在开发体验上非常直接:你不需要切到CSS文件,不需要为class命名绞尽脑汁,直接在JSX里写响应式类名就行。配合组件库shadcn/ui,基础表单、按钮、卡片都能直接拿来复用。
我个人更看重Tailwind的另一个优势:它不会限制你写组件的方式。做产品初期最常见的需求就是改样式,Tailwind允许你在不重写组件结构的前提下,快速调整间距、颜色、字重,这种灵活性在全栈快速迭代场景下非常宝贵。
2.3 数据层三件套:Prisma、NextAuth、tRPC
这套组合是T3 Code的重头戏,三个工具各管一段:
- Prisma管数据库访问。你用
schema.prisma定义模型,然后跑一句prisma migrate,表结构就同步到数据库了。类型安全体现在:模型一变,Prisma生成的client类型立刻更新,你写查询语句的时候有自动补全。写错字段名?编辑器直接报错。 - NextAuth管认证登录。支持邮箱验证码、Google、GitHub等OAuth方案。和直接手写JWT逻辑相比,NextAuth把会话存储、CSRF防护、回调地址这些最容易出错的地方都封装好了,你只需要配置providers和数据库表。
- tRPC管前后端无缝调用。这里值得展开说:传统做法你写一个REST接口,前端要用fetch或axios发起HTTP请求,然后还要手动定义请求参数类型和返回类型。多写几个接口之后,类型对齐就成了噩梦。tRPC的逻辑完全不同——后端路由函数直接暴露给前端调用,参数类型、返回类型完全自动推导,前端代码看起来就像调用本地函数一样。
2.4 为什么不直接用其他方案
很多朋友会问:那我不如直接Next.js + Express自己拼一个?我确实这么干过,后来发现维护成本非常高。举几个实际对比:
| 对比维度 | T3 Code全栈方案 | 传统Next.js + 自建API |
|---|---|---|
| 类型同步 | tRPC自动推导,改接口前端立刻提示错误 | 手动维护接口类型,容易遗忘 |
| 数据库操作 | Prisma client类型安全,迁移有版本控制 | 手写SQL或轻量ORM,出错后排查成本高 |
| 认证登录 | NextAuth开箱即用,会话管理封装完善 | 自研实现,安全细节容易被忽略 |
| 脚手架配置 | 一体化生成,ESLint、Prettier、next config就绪 | 手动装依赖、配路径别名,耗时耗力 |
对比下来,T3 Code非常适合中小型全栈项目,尤其是需要快速上线验证需求的情况。当然如果你做的是一个超大型SPA,需要高度定制化前后端分离架构,那这套方案不一定是最优解,但至少可以作为很好的起点。
3. 实操:用create-t3-app跑通一个全栈项目
3.1 初始化命令与环境准备
准备工作很简单:Node.js版本建议18以上,自己选的包管理器就用npm或pnpm都行。然后执行:
npx create-t3-app@latest my-app回车之后会出现一个交互式选项清单,我建议初期全部回车选Yes,把完整能力先体验一遍。重点关注这几个选项的含义:
- Next.js:框架本体,选App Router还是Pages Router。新项目我建议选App Router,这是现在的默认方向。
- TypeScript:固定启用,不用纠结。
- Tailwind CSS:样式方案,建议启用。
- Prisma:ORM,后面做数据库操作必备。
- NextAuth:认证方案,做登录功能建议启用。
- tRPC:RPC调用层,T3 Code的招牌能力。
初始化完成后,进入项目目录安装依赖。注意:create-t3-app默认会把所有依赖一次性列出来,安装过程比较长是正常的,不用中途打断。
3.2 目录结构快速解读
项目生成后,你看到的第一批文件可能会有点多,但结构比传统手搭项目清晰得多。我用实际项目为例说明:
my-app/ ├── prisma/ │ └── schema.prisma # 数据库模型定义 ├── src/ │ ├── pages/ │ │ ├── api/ │ │ │ ├── auth/[...nextauth].ts │ │ │ └── trpc/[trpc].ts │ │ └── index.tsx # 首页 │ ├── server/ │ │ ├── api/ │ │ │ ├── routers/ # 业务路由 │ │ │ └── root.ts │ │ ├── auth.ts │ │ └── db.ts │ └── styles/globals.css ├── .env # 数据库连接、认证密钥 ├── package.json └── next.config.mjs值得注意的目录是src/server和src/pages/api。前者放的是所有服务端逻辑,包括数据库访问、认证配置、tRPC路由定义,后者则是Next.js的API入口文件。脚手架把“服务端代码不能打进前端bundle”这个边界用目录结构隔出来了,你只要遵循这个结构,就不会写出把数据库密码暴露到浏览器里的低级错误。
3.3 快速实现一个待办事项功能
光说理论不够,我拿一个待办事项功能来演示完整链路。先从数据库定义开始,编辑prisma/schema.prisma:
model Todo { id String @id @default(cuid()) title String done Boolean @default(false) createdAt DateTime @default(now()) }然后跑一句命令同步到数据库:
npx prisma migrate dev --name init接着用tRPC暴露数据接口,编辑src/server/api/routers/todo.ts:
import { z } from "zod"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; export const todoRouter = createTRPCRouter({ list: publicProcedure.query(({ ctx }) => { return ctx.db.todo.findMany({ orderBy: { createdAt: "desc" }, }); }), create: publicProcedure .input(z.object({ title: z.string().min(1) })) .mutation(({ ctx, input }) => { return ctx.db.todo.create({ data: { title: input.title }, }); }), });注意zod在这里做参数校验,类型定义和运行时校验同时完成,前后端都不需要再写额外的参数校验代码。然后在root.ts里把router挂载进去,前端调用就像这样:
const { data: todos, refetch } = api.todo.list.useQuery(); const createMutation = api.todo.create.useMutation({ onSuccess: () => refetch(), }); createMutation.mutate({ title: "写一篇t3code博客" });有没有感觉到?这里前台代码里没出现任何fetch、URL、接口路径、HTTP方法。所有的类型推导、自动补全、返回结构检查,都来自前一步定义的那个Python风格后端路由。第一次用的时候我是真的愣了一下:不需要联调了?确实不需要了。
3.4 环境变量与生产构建
环境变量是很多新手翻车的地方。初始化后会生成一个.env文件,通常包含DATABASE_URL、NEXTAUTH_SECRET、NEXTAUTH_URL这几个关键变量:
DATABASE_URL:数据库连接串,Prisma读取。改成你自己的数据库地址。NEXTAUTH_SECRET:认证加密用的密钥。开发环境可以随便填一个字符串,生产环境建议用openssl rand -base64 32生成。NEXTAUTH_URL:应用部署的域名地址。只在生产环境必须正确填写,本地开发一般设为http://localhost:3000。
生产部署的构建捷径是这样:先prisma migrate deploy同步所有迁移,再prisma generate重新生成client,然后next build打生产包。如果漏了migrate deploy,新表不会自动建出来,接口一跑就报关系不存在,这种错误排查起来极容易让人怀疑人生。
4. 踩坑实录与问题排查速查表
4.1 改了router但前端类型没更新
这个问题几乎每个人都遇到过。改完后端路由,跑回前端调用旧字段,编辑器死活不报错,但运行时数据就是拿不到。原因通常是tRPC的类型推导需要经过Next.js的dev server重新编译,而你这边没重启或是缓存残留。
解决方法是先重启pnpm dev,如果还不行,删除.next目录再重启。再进阶一点,你可以在package.json里加一个postinstall钩子,这样每次依赖安装完都会自动生成Prisma client和tRPC类型,省掉很多手动步骤。这是我踩了三回坑之后固定下来的习惯。
4.2 Prisma迁移冲突
团队协作时,prisma migrate dev最容易翻车的是迁移冲突。两个人新增了相同模型,或者直接改了历史迁移文件,数据库迁移表就对不上了。
处理思路:如果只是本地开发,可以直接prisma migrate reset重置整个数据库,再把本地的schema完全同步一遍。如果线上有数据,绝对不能这样干。正确的做法是先打开迁移表,找到最后一个成功的基线,然后手动把冲突的迁移清除,让Prisma从甲方角度认为当前状态和schema一致,再重新生成新的迁移。这里多提一句:不要养成改历史迁移文件的习惯,宁可新增一个迁移来调整表结构,也不要篡改已经应用过的文件。
4.3 NextAuth的OAuth回调跳转异常
配置Google或GitHub登录时,最容易出问题的就是回调地址不一致。生产环境里,Google控制台填写的回调URL是https://你的域名/api/auth/callback/google,GitHub的也一样,漏掉路径中任何一层/api/auth/callback都会报redirect_uri_mismatch。
另一个常见问题是登录成功之后不跳转。排查思路很简单:先确认NEXTAUTH_URL是否和访问域名一致。很多云平台默认提供二级域名,你通过公网IP访问或临时域名访问,回调地址就对不上,页面就会卡在登录成功但无法跳转的尴尬状态。
4.4 容易被忽视的细节:Node版本和.env前缀
T3 Code要求Node.js 18以上,老项目的Node 16在安装依赖的时候会直接报错。此外所有环境变量如果需要在浏览器端访问,必须加NEXT_PUBLIC_前缀;不带前缀的变量只会暴露在服务端。比如你在前端代码里读取process.env.NEXTAUTH_SECRET,运行时永远是undefined,这不是bug,是Next.js的安全机制。
还有个很容易踩的坑:生产环境忘记跑prisma generate。有时候在本地可以跑,部署到线上就报“PrismaClient is not configured”,大概率是构建阶段没有执行generate。所以我一直建议在postinstall里写prisma generate,一劳永逸。
5. 使用T3 Code后续还可以怎么扩展
5.1 渐进式迁移老项目
如果现有的Next.js项目已经写了不少代码,不想推倒重来,T3 Code也是可以逐步引入的。你不一定非要从零初始化,可以直接在现有项目里安装Prisma和tRPC,从一个小功能开始做类型打通。我试过在一个中等规模的博客项目里先接Prisma,再逐步把API迁移到tRPC,整个过程非常平滑。关键是tRPC可以只服务新增功能,老接口保持不动,不会造成一刀切的灾难。
5.2 搭配Monorepo组织多应用
当项目膨胀到需要拆前端和后端管理后台时,T3 Code可以放进pnpm workspace里,结合Turborepo做任务编排。这样多个应用可以共享数据库schema、共享工具函数,构建时打包缓存也快。如果一开始没规划Monorepo也不要紧,T3 Code本身就是按模块组织的,后期抽离公共代码难度不大。
5.3 关注Next.js新版本动向
T3 Code的一大好处是它会跟进Next.js的最新特性,比如Server Components、App Router。这意味着你选择它,等于选择了社区活跃、更新频繁的阵营,未来新功能接入成本较低。每次大版本发布,T3 Code团队都会及时调整脚手架的默认模板,所以长期使用不用太担心技术债。
我个人在实际项目里的体会是:T3 Code最大的价值不是帮你省掉那几分钟的脚手架搭建时间,而是帮你在项目一开始就走上一条类型安全、结构清晰、不易腐烂的技术路径。代码量可能不会变少,但调试时间和返工次数大幅下降。如果你正好准备启动一个全栈项目,不妨先用这个脚手架跑一个demo,感受一下前后端类型完全打通到底是什么体验。反正初始化只要一分钟,试错成本极低。