T3 Stack实战:用create-t3-app构建端到端类型安全的全栈应用
如果你经常在 GitHub 上刷全栈 TypeScript 项目,大概率见过 T3 Stack、create-t3-app 这类名字。t3code 背后其实是同一套思路:用一组确定性的技术组合,把页面渲染、API 路由、数据库访问、身份认证和类型校验全部串在 TypeScript 里,让一个 Web 项目从脚手架到业务代码都保持端到端类型安全。
这篇文章我按实际落地顺序拆一遍:它解决什么问题、需要什么环境、怎么创建项目、怎么接数据库和认证、报错时先查哪里。适合正在选型全栈脚手架、或者已经用 Next.js 但觉得前后端接口类型维护太累的开发者。先给结论:如果你要做一个内部系统、全栈应用,或者接口数量多且希望减少联调成本,这套方案很值得试;如果只是做一个简单落地页,它反而偏重。
1. t3code 到底是什么,先搞懂它解决什么问题
1.1 它不是单一框架,而是一组固定搭配
很多人第一次看到 T3 Stack 会问:这又是一个新框架吗?不是。它更像一个经过验证的技术组合方案,最常见的搭配是:
- Next.js 负责页面、路由和服务端渲染
- TypeScript 负责类型系统
- Tailwind CSS 负责样式
- tRPC 负责前后端接口调用
- Prisma 或 Drizzle 负责数据库访问
- NextAuth.js 负责登录认证
- Zod 负责输入校验
- t3-env 负责环境变量的类型安全和运行时校验
t3code 这类项目生成工具的价值,不只是一条命令创建项目,而是把上面这些工具的初始化配置一次性处理好。你不需要再花半天去配路径别名、接口层、环境变量校验、ESLint 和 Prettier。生成出来的项目结构清晰,很多文件带着注释,前端、API、数据库之间已经连通,可以直接开始写业务。
我见过不少团队从零搭 Next.js 项目,前三天基本都在做重复劳动:配 TS 编译选项、搭 API 层、修 CORS、写类型定义。用这套脚手架,这些步骤直接被跳过了。
1.2 最核心的卖点:端到端类型安全
普通前后端分离项目里,前端调接口时经常要手写 interface 或类型定义。后端返回的字段名改一下,前端编译期完全不知道,只有运行时才发现页面空白或数据是 undefined。
tRPC 在这个组合里的作用,就是让后端 router 里定义的输入输出类型,被前端调用时直接继承。后端把 post.create 的入参定义为 { title: string, content?: string },前端调用 api.post.create.useMutation() 时,传参自动带类型提示,传错字段、缺字段都会在编译期报错。
后端改了一个字段名,前端对应调用处立刻变红。这就是“端到端类型安全”的实际体验,它不是概念,是你每天写代码都能感知到的东西。
我建议入手前先想清楚这个收益是否匹配你的项目:如果是内部管理后台、全栈 Saas、数据密集型应用,收益非常明显;如果是给第三方开放的公开 API,那更适合 REST + OpenAPI,而不是 tRPC。
2. 开始之前,先把环境条件确认清楚
2.1 Node.js 版本和包管理器
create-t3-app 对 Node.js 版本有明确要求,建议使用 Node.js 18 及以上,最好直接用 20 LTS。版本太低,Next.js 和 tRPC 启动时会报各种奇怪的错;版本太高,个别依赖的兼容性也可能出问题。
先执行:
确认版本没问题之后,再确认包管理器。npm、pnpm、yarn、bun 都可以用,但我个人建议 pnpm。原因很简单:这套工具链依赖树不小,pnpm 安装速度快,磁盘占用也低。如果你不想换,npm 也完全能跑,不用纠结。
2.2 数据库怎么准备最快
create-t3-app 初始化时会让你选 ORM:Prisma 或 Drizzle。无论选哪个,都要有一个可连接的数据库。
如果你本机没装任何数据库,最简单的做法是先用 SQLite。它不需要单独启动服务,就是一个本地文件,适合把流程先跑通。
如果你打算用 PostgreSQL,我用得最多的本地启动方式是 Docker:
这样连接串就是:
这里容易踩的坑是:数据库连接串写错、端口被占用、Docker 容器没启动。启动报错时先按这个顺序查,不要一上来就怀疑代码。
2.3 环境变量文件为什么这么重要
生成的项目会提供 .env.example,你需要把它复制成 .env 再填值。很多人忽略这一步,直接 npm run dev,然后看到环境变量校验报错。
这个校验来自 t3-env + Zod,是刻意设计的。它的作用是把 process.env 里的字符串统一变成有类型的常量,并且在启动时检查必填项是否存在。比如 DATABASE_URL 没填,项目直接拒绝启动,而不是等请求数据库时才报错。
处理办法很简单:
然后打开 .env,把每一项都看一遍,确认连接串、密钥、回调地址没有缺。
3. 从零创建 t3code 项目,完整实操流程
3.1 交互式初始化怎么选
创建项目只需要一条命令:
执行之后会进入交互式选择,常见的选项包括:
- TypeScript:默认选上,这也是整套方案的前提
- Tailwind CSS:需要写界面的就选
- tRPC:需要接口层就选
- Prisma / Drizzle:需要数据库就选一个
- NextAuth.js:需要登录认证就选
- 是否使用 App Router:新项目建议直接 App Router
学习阶段怎么选?我建议全部选上。这样你能在一套项目里同时看到页面、接口、数据库、认证是怎么串联的。虽然看起来复杂,但生成的项目结构是“用法示范”,你会更快理解每一层的作用。
正式项目怎么选?按需选。如果项目根本不需要用户体系和数据库,就不要硬加 NextAuth 和 Prisma。脚手架工具存在的意义是减少复杂度,不是增加复杂度。
3.2 生成之后先做三件事
项目生成后不要急着写业务,先按顺序做三件事:
第 3 步很多人会跳过。如果用的是 SQLite,prisma db push 会生成一个 prisma/dev.db 文件;如果用的是 PostgreSQL,它会把 schema 同步到数据库。
做完这三步,再启动:
浏览器打开 http://localhost:3000,看到默认首页就说明链路通了。这里不要急着调样式、不要加功能,先确认开发服务器能稳定跑起来。
3.3 生成后的目录结构先看懂哪里
create-t3-app 生成的项目结构清晰,我建议先看这几个位置:
| 路径 | 作用 |
|---|---|
src/server/api/trpc.ts |
tRPC 的初始化,包括上下文和公共 procedure |
src/server/api/root.ts |
所有业务 router 的汇总入口 |
src/server/api/routers/ |
每个业务模块的接口定义放这里 |
src/trpc/react.tsx |
前端调用 tRPC 的 Hooks Client |
src/app/api/trpc/[trpc]/route.ts |
tRPC 在 App Router 下的 HTTP 入口 |
src/env.js |
环境变量的类型和校验定义 |
prisma/schema.prisma |
数据库表结构定义 |
你不需要全部理解才能开始,但至少要分清两件事:后端接口定义在 server/api 下,前端调用方式从 ~/trpc/react 导入。后面大概率踩的坑,都是在这两个区域之间互相找不到对应关系。
4. 端到端类型安全是怎么跑通的
4.1 先用 Prisma 定义一张表
要感受端到端类型安全,最好的方式不是读文档,而是自己加一张表。我用一个简单的 Post 表举例:
改完 prisma/schema.prisma 后执行:
这个命令会把表结构同步到数据库。注意,db push 适合开发环境快速同步;正式环境里更推荐用 prisma migrate dev 生成迁移文件,方便版本管理。
4.2 在 tRPC router 里加接口
在 src/server/api/routers/post.ts 里定义两个接口:一个查询列表,一个创建记录。
然后把 router 注册到 src/server/api/root.ts:
这一步很关键。input 用 Zod 定义,意味着非法参数在真正执行数据库操作之前就被拦截了;getAll 的返回类型会被自动推断,前端不需要手写任何接口类型。
4.3 前端用 Hooks 直接调用
页面组件里这样用:
你可以自己试一下:把 postRouter 里 getAll 的返回字段改一下,比如在 findMany 中去掉 content,前端 posts 的类型会立刻变化。再把 create 的入参改成必填 content,前端调用处会立刻报类型错误。
这种联动体验,就是端到端类型安全的核心。它把接口定义、参数校验、类型继承都收敛在一起,前后端不再是两套割裂的类型。
5. 数据库和认证的接入细节
5.1 Prisma 和 Drizzle 怎么选
create-t3-app 同时支持 Prisma 和 Drizzle,两个 ORM 我都跑过,简单对比一下:
| 维度 | Prisma | Drizzle |
|---|---|---|
| Schema 定义 | 独立的 schema.prisma 文件 |
TypeScript 代码 |
| 迁移工具 | prisma migrate |
drizzle-kit |
| 学习门槛 | 低,概念直观 | 中等,贴近 SQL |
| 类型生成 | prisma generate 自动生成 |
类型在代码里,需要 sqlite 或 postgres 驱动配合 |
| 调试体验 | studio 可视化查看 | 命令行工具为主 |
新手我更建议 Prisma,原因只有一个:文档多、报错信息友好、studio 能直接看数据。等你对数据库访问很熟了,再尝试 Drizzle 也不迟。选型这件事没有绝对优劣,重点是不要在同一个项目里混用两套 ORM。
5.2 接 NextAuth 和保护接口
创建项目时如果勾选了 NextAuth.js,它会生成 src/server/auth.ts。你需要在 .env 里填认证相关的变量,比如:
然后用一个 tRPC 的 protect 中间件,让未登录用户无法访问某些接口:
之后对需要登录的接口,把 publicProcedure 换成 protectedProcedure 即可。未登录请求返回 401,前端可以根据这个错误码跳转登录页。
注意,这个保护是接口层面的,不是页面层面的。页面路由守卫还需要单独处理,比如在布局组件里判断 session 状态。不要以为保护了接口就等于保护了页面。
6. 常见报错和排查顺序
6.1 环境变量校验失败
现象:启动时直接报 Invalid environment variables,后面跟着缺失的变量名。
排查顺序:
- 先确认
.env文件是否存在。 - 再确认变量名是否和
.env.example完全一致,大小写、下划线都不能差。 - 最后确认值本身是否合法,比如
DATABASE_URL的协议、用户名、密码、端口。
这类问题最常见的原因就是 cp .env.example .env 之后忘了填值,或者本地数据库连接串复制错了。
6.2 数据库连接失败
现象:启动正常,但访问接口时报数据库连接错误,比如 Can't reach database server。
排查顺序:
- 查数据库进程是否运行。SQLite 只要文件存在即可;PostgreSQL 要看容器或本机服务状态。
- 查连接串里的端口是否有冲突。
- 查 Prisma schema 里的 provider 是否和连接串匹配,比如 schema 写
postgresql,连接串却是mysql就会报错。 - 改完 schema 后有没有重新执行
prisma generate或prisma db push。
这里不要一上来就重装依赖。多数时候是数据库连接串、端口、进程状态的问题。
6.3 启动成功但接口 404 或页面白屏
现象:页面能打开,但 tRPC 请求返回 404,或者页面渲染空白。
排查顺序:
- 先确认
src/app/api/trpc/[trpc]/route.ts是否存在。这个文件是 App Router 下 tRPC 的 HTTP 入口,删了或改名都会导致 404。 - 再确认
appRouter是否真的包含了对应 router,只定义文件但没注册到root.ts,前端调用也会失败。 - 然后看浏览器 Network 面板里具体请求路径和错误信息。
- 最后看 DevTools 控制台完整堆栈,而不是只看第一行。
6.4 依赖版本变化带来的坑
这类项目对版本比较敏感。Next.js 大版本更新后,App Router 的请求参数处理方式会变;Tailwind 从 v3 到 v4 的配置方式也有明显变化。遇到莫名其妙的问题时,先看 package.json 里的版本号,再对照错误信息和官方升级说明。
我的建议是:跑通第一遍时,用生成项目时锁定的版本,不要第一时间把所有依赖都升到最新。最稳妥的做法是先在一个稳定组合上把业务写完,再单独规划依赖升级。
7. 从 Demo 到生产,边界和优化建议
7.1 什么时候不适合用这套方案
T3 Stack 好用,但边界要清楚。
- 对外提供公开 API,给第三方开发者使用,不适合。外部用户不需要也不应该使用你的 tRPC 客户端,这种场景更适合 REST + OpenAPI 文档。
- 前后端团队完全分离,且前端是 React Native 或小程序。tRPC 支持 HTTP 调用,但类型共享的收益要打折扣,跨端场景需要额外确认。
- 项目只有三五个页面,没有用户系统、没有数据写操作。用这套组合反而增加心智负担,直接用 Next.js + TypeScript 就够了。
判断标准可以浓缩成一句话:你是否需要“同一套类型贯穿前后端”?需要就用,不需要就不必硬上。
7.2 部署时要注意的点
部署方面,最省事的方案是 Vercel + 托管数据库。代码推到 Git 仓库,Vercel 导入项目,把 .env 里的变量填到 Vercel 环境变量面板,构建部署就完成了。
要注意三点:
- 不要把
.env提交到 Git 仓库。 - 生产环境数据库要单独建,不要用开发库。
- 认证配置里的回调地址要改成生产域名。
如果项目里有定时任务、长任务、批量同步,不要全部塞在 Next.js 的 serverless 函数里。这类任务应该有独立的 worker 进程或者任务队列,否则会遇到执行超时和冷启动问题。
7.3 我建议的落地顺序
最后说一下我自己的习惯,也是踩过很多次坑之后摸索出来的顺序:
先把单条流程跑稳:创建项目、连数据库、跑通一个 tRPC 查询和一个 mutation。然后再加认证、再加更多业务模块。不要第一天就把所有功能、所有页面、所有依赖全部铺开。一个项目能不能长期维护,看得不是第一天搭了多少东西,而是出问题时能不能快速定位。
这套方案真正落地时,最该盯住的不是功能列表,而是三件事:环境变量是否受控、数据库迁移是否规范、接口失败时日志是否可读。这三件事做好,后面写业务会顺畅很多。