t3code:基于T3 Stack的TypeScript全栈应用启动模板实战

发布时间:2026/10/26 17:10:35
t3code:基于T3 Stack的TypeScript全栈应用启动模板实战

前言:t3code,一个让我绕开重复劳动的启动项目

如果只用一句大白话介绍t3code,我会说:这是一个基于 T3 Stack 全家桶的全栈项目启动骨架,名字里的"t3"取自现代全栈开发圈里越来越常见的 T3 Stack 技术组合——Next.js + TypeScript + tRPC + Tailwind CSS,而"code"则是我对这份代码资产的定位:不断沉淀、可以直接复用的业务代码基础库。

我自己维护这个项目已经有半年多了。最初是因为团队里每开一个新产品都要从零搭一遍用户认证、数据库模型、接口层和前端页面,极度浪费时间;后来越搭越顺手,索性把经验固化成了一套模板。如果你正在做 Web 全栈开发,或者想从零搭建一个带用户体系的完整应用,但苦于不知道怎么把这些工具粘在一起,这篇文章就是写给你看的。下面是这段时间里,我在 t3code 这个项目上的完整复盘、踩坑记录和可以直接抄作业的实操路径。

1. 先把这个代号拆明白:t3code 到底在解决什么

很多项目代号其实藏着设计意图。t3code 这个名字有两个来源:一是技术组合的浓缩,"T3"指向当前社区热度很高的 T3 Stack;二是"Code"强调代码资产化。合起来,它代表一种主张:用一套省心的全栈工具链,把可复用的核心逻辑沉淀成代码,而不是每次开工都从空目录开始。

1.1 "T3"和"code"的组合不是随便起的

"T3"最早来自 create-t3-app 这个脚手架项目,核心是四件套:

  • Next.js:React 框架,承担页面渲染、API 路由和服务端能力;
  • TypeScript:全链路静态类型;
  • tRPC:让前端直接"调用"后端函数,不需要手写 REST 接口文档;
  • Tailwind CSS:原子化 CSS,快速写界面样式。

我看到很多人对 T3 Stack 有个误解,觉得它只是"工具拼盘"。但真正用过之后会发现,它的价值不在于多,而在于类型安全是贯穿前后端的。你在前端调用一个接口,参数不对、返回类型不对,编辑器里直接就报错了,根本不用等到运行时去翻网络请求。这个体验一旦习惯,再回到手写接口文档、手动维护类型定义的开发方式,会非常痛苦。

至于"code"部分,我把 t3code 定位成三层代码资产:第一层是防止重复劳动的启动模板;第二层是团队约定俗成的目录规范;第三层是认证、权限、CRUD 等通用模块的"标准答案"。很多项目要么是文档说了大堆理论,要么是模板只有空壳,t3code 试图把两者打通。

1.2 全栈开发里的重复劳动,才是这个项目想消灭的东西

先聊一个高频场景:你要做一个带登录功能的 Web 应用,假设技术选型是传统的 React + Express + MongoDB。流程大概是:

  1. 初始化前端项目,配置路由和状态管理;
  2. 初始化后端项目,搭 Express 中间件;
  3. 设计用户表、写注册登录接口;
  4. 前端写 API 调用封装,处理 loading 和错误;
  5. 手动把接口返回数据定义成 TypeScript interface;
  6. 部署时还要处理跨域、环境变量、构建脚本。

六个步骤里,真正属于业务本身的可能只有一步。t3code 要做的就是把这个默认流程全部自动化、规范化:你拿到手的不再是"空白项目",而是一个已经打通认证、数据库、前后端接口、类型推导的应用雏形。

有人会问,这跟直接用 Next.js 脚手架然后自己搭有什么区别?区别在于:create-next-app 不会帮你接好 tRPC 的类型推导,也不会帮你预置 Prisma 数据模型和 NextAuth 配置。t3code 把这些环节的坑提前都踩了一遍,你在基础上做业务扩展即可。

1.3 这个项目适合谁

动手维护 t3code 之前,我先判断了这个项目适合的使用者画像,这决定了里面应该放什么、不必要放什么:

  • 适合有一定 React 基础,想学习完整全栈链路的人;
  • 适合小团队快速验证产品 MVP,不想过早拆分前后端仓库的人;
  • 适合老是被"类型不统一、接口文档过期、环境配置乱"折磨的人。

如果你只是想写一个静态展示页,不需要用户系统也不需要数据库,那 t3code 对你来说太重了。搞清楚这个前提,后面所有设计才有意义。这也是我在项目首页 README 里第一行就写明白的事情。

2. 技术选型复盘:为什么紧紧咬住这套组合不放

很多文章一上来就放代码,却不说清楚"为什么选择这套方案"。我反而觉得选型才是项目能不能长期维护的关键。

2.1 T3 Stack 的四个关键词,各自承担什么角色

先逐个拆解。

Next.js 是最核心的骨架。它同时承担前端渲染和后端 API 能力,pages/api 或 app/api 目录可以直接写接口。对于小团队来说,一个应用里既有页面又有接口,心智负担会小很多。

TypeScript 是所有类型安全的基石。t3code 里的"类型安全"分三层:

  • Prisma 根据数据库表结构生成类型;
  • tRPC 根据后端路由过程生成调用类型;
  • 前端直接拿到这些派生类型,不需要手动维护接口类型。

tRPC 是 t3code 最亮眼的环节。它本质上是一种远程过程调用框架,你在后端写一个函数"getUserById",前端就可以像调用本地函数一样调用它。比起 REST + 手动类型定义,tRPC 最大的优势是"一处定义,全局生效"。你改了一个接口的返回结构,前端调用处立刻高亮报错,这在 REST 世界里基本做不到。

Tailwind CSS 承担样式部分。它不追求设计系统的复杂度,而是提供一套低心智负担的原子类,让开发者把精力放在布局和交互上。

这四样东西合起来的使用体验,说直白点:后端改了数据结构,前端页面立刻报错;前端改了一个 props 类型,后端引用也会同步提示。这种感觉不是几个工具排列组合就能给的,而是深度耦合后的整体收益。

2.2 备选方案对比:为什么不是 NestJS、Nuxt 或常规前后端分离

选型时我专门列了一张对比表,最后才坚定地用 T3 Stack。

方案优点主要成本结论
Next.js + Express + REST生态成熟,文档多需要维护两套代码和类型定义;接口沟通成本高小团队效率偏低
NestJS + React 前后端分离后端架构严谨,适合大型系统学习曲线陡,初始化成本高t3code 定位不是大型系统
Nuxt + 模块化方案Vue 生态,回车快与团队 React 技术栈不一致不适用于当前团队
T3 Stack(t3code 选它)类型安全贯穿全链路,起步快需要适应 tRPC 思维最适合快速构建全栈应用

这张表不是想说 REST 不行,而是说对于 t3code 这样追求"开箱即用"的个人项目,T3 Stack 的性价比确实高。

2.3 关于选型的一些实在话

我也得说实话:T3 Stack 不是万能的。如果业务复杂度非常高,比如你需要消息队列、微服务拆分、多团队并行开发,t3code 的模板显然不适合直接长成巨型项目。它的设计边界在"单体全栈应用"这个范围内,超过这个范围,该上后端框架还是要上。

但边界清楚反而是好事。项目维护者在 README 里会把适用范围写出来,避免让新手把模板硬套到大项目上。我自己也踩过这个坑,早期硬是在 t3code 上堆业务,结果越来越难维护。后来痛定思痛,把通用部分抽象成"可复用模块",把业务部分全拆出去。

3. 从零搭建 t3code:实操过程记录

这个章节是全文的重头戏。我会按照我自己从空目录开始搭建的顺序,把每一步的关键动作、为什么这么做、有哪些容易出错的点都写出来。

3.1 初始化与依赖安装

T3 Stack 官方脚手架是 create-t3-app。执行:

npm create t3-app@latest t3code

交互式命令行会问你选择哪些模块。我的建议是:第一轮就全选——Next.js、TypeScript、tRPC、Prisma、NextAuth、Tailwind,后面想再加模块反而麻烦,比如 Prisma 和 NextAuth 最好像这样从一开始就接好,否则后面手动接的时候要改很多文件。

初始化完成后进入项目,先确认依赖安装正常:

cd t3code npm install

这里有个很多新手都遇到过的问题:node 版本太旧会导致 Next.js 安装失败或者启动报错。建议先把 Node.js 升到 18 以上,最好 20。我的经验是直接用 nvm 管理,nvm install 20和nvm use 20两条命令搞定,避免后续无谓的兼容性问题。

打开目录一看,脚手架已经帮我们建好了非常干净的目录,和那种只给你一个 package.json 的模板完全不同。你立刻就能感受到,这个框架的意图是让你把专注力放在业务代码上。

3.2 目录结构与约定

t3code 的目录结构里,最值得关注的是这几个目录:

t3code/ ├── prisma/ # 数据库模型与迁移文件 │ ├── schema.prisma │ └── migrations/ ├── src/ │ ├── pages/ # Next.js 页面路由 │ ├── server/ # 服务端逻辑:tRPC、DB 客户端 │ │ ├── api/ │ │ ├── db.ts │ │ └── auth.ts │ └── styles/ └── .env

我会额外约定一条团队规范:所有与业务无关的通用工具函数放在src/server下,页面相关的数据请求尽量不直接写 fetch,而是通过 tRPC 的封装去调。这样做的原因是,tRPC 调用天然带着类型推导,直接写 fetch 反而破坏了这种一致性。如果你接手了这个项目,请一定遵守这个约定,不要图省事绕开 tRPC,不然类型安全的关键收益就丢失了。

3.3 数据模型设计:这是最容易返工的地方

数据库模型是 t3code 中最需要前期想清楚的部分。我常用的原则是:先用 Prisma Schema 定义业务实体,再通过迁移命令同步到数据库。定义一个用户模型大概长这样:

model User { id String @id @default(cuid()) name String? email String @unique emailVerified DateTime? image String? password String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }

注意几个细节:

  • id用cuid()而不是自增数字,方便分布式环境下的数据合并,也不用担心遍历泄露用户量;
  • email加@unique约束,这是认证系统的基本要求;
  • password字段我故意标成可选,因为 t3code 默认用 NextAuth 的 OAuth 方案,本地注册场景才需要密码。

写完 schema 后,执行:

npx prisma migrate dev --name init

这条命令会做两件事:创建迁移文件,并把最新模型同步到本地数据库。如果数据库连接配置没写好,它会直接报错。此时去检查.env里的DATABASE_URL,本地开发我一般用 SQLite 起步,生产换成 PostgreSQL,所以先设置为:

DATABASE_URL="file:./dev.db"

SQLite 的好处是零配置,不用本地装数据库,对新手极其友好。但要注意,PostgreSQL 和 SQLite 在 Prisma 里的某些查法有差异,比如涉及到数组类型、全文检索等功能,后面要换库的话得留个心。

3.4 认证模块落地:NextAuth 配置的注意事项

t3code 的认证模块使用 NextAuth。初始化时脚手架已经放好了src/server/auth.ts和src/pages/api/auth/[...nextauth].ts,你要做的只是在其中填入你的 OAuth 提供商配置。

以 GitHub 为例,先在 GitHub 开发者设置里创建 OAuth App,获得 Client ID 和 Client Secret,然后写入.env:

GITHUB_CLIENT_ID=你的ID GITHUB_CLIENT_SECRET=你的密钥 NEXTAUTH_SECRET=任意生成的一串随机字符串 NEXTAUTH_URL=http://localhost:3000

NEXTAUTH_SECRET的坑比较多。很多人本地开发时随便填一个值能跑,上了生产环境就时不时要重新登录,就是因为 secret 不一致。我的操作方法是用openssl rand -base64 32生成一个真正随机的串,然后在所有环境里保持一致。

NextAuth 的[...nextauth].ts默认配置长这样:

import NextAuth from "next-auth"; import GitHub from "next-auth/providers/github"; import { authOptions } from "~/server/auth"; export default NextAuth(authOptions);

真实的逻辑封装在src/server/auth.ts里,包括把用户写进数据库的 Prisma adapter。我发现大多数人第一次配 NextAuth 时会漏了 adapter,导致用户信息只存在会话里、数据库里没有对应记录。在 t3code 里,脚手架已经帮你装好并把 adapter 接进 Prisma 了,你只需要确认数据库里能正确写入用户即可。

3.5 业务接口与类型安全:tRPC 路由怎么设计

t3code 中,后端接口不是"接口",而是一簇可被调用的函数。这些函数按代码路径划分为router。我先把一个业务模块拆成三块:userRouter管用户信息,postRouter管内容发布,adminRouter管后台管理。

写一个简单的用户查询过程:

import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; import { z } from "zod"; export const userRouter = createTRPCRouter({ getById: publicProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) => { return ctx.db.user.findUnique({ where: { id: input.id }, select: { id: true, name: true, email: true, image: true }, }); }), });

这里有个非常关键的点:zod的 input 校验不只是运行时的,它还承担了类型推导。前端调用getById时,传入的参数必须是{ id: string },返回的数据类型也由后端推导出来,不需要引入任何额外的接口类型声明。

在 api 根路由里把这些子路由合并:

export const appRouter = createTRPCRouter({ user: userRouter, post: postRouter, admin: adminRouter, }); export type AppRouter = typeof appRouter;

这个AppRouter类型会导出给前端使用,让 tRPC 的客户端调用具备完整的类型提示。

我个人的使用习惯是:每个业务模块一个文件,文件里只放业务逻辑,不放页面逻辑。这样当你删掉一个业务模块时,只要删除对应的 router 文件并改一行根路由配置即可,不会有四处散落的接口代码找不干净。

3.6 前端消费与页面拼接:从后端到 UI 的完整链路

后端定义好了,前端怎么用是另一个关键环节。t3code 在src/utils/api.ts里封装了一个 React hooks 化的 tRPC client。典型用法:

import { api } from "~/utils/api"; export default function UserProfile({ userId }: { userId: string }) { const { data, isLoading, error } = api.user.getById.useQuery({ id: userId }); if (isLoading) return <div>加载中...</div>; if (error) return <div>出错了:{error.message}</div>; return ( <div> <h1>{data?.name ?? "未命名用户"}</h1> <p>{data?.email}</p> </div> ); }

你不需要写 fetch,不需要处理 loading 状态和错误状态的代码模板,也不需要把后端类型手动复制到前端。useQuery一调用,一切由 tRPC 的 React 集成自动管理。

这里我要重点提一个经验:不要把所有查询都塞进一个组件里。t3code 的页面倾向于"页面组件只负责路由级别的数据获取,子组件再按需触发自身的查询"。这样做的原因很朴素:避免一次进入页面就发起十几个请求,白白消耗带宽和时间;同时各组件独立管理自己的请求状态,互不干扰。

4. 部署上线与后续运维:t3code 从开发机到服务器

开发环境跑通只是开始。真正让我觉得 t3code 达到"可用"标准的,是它能够在生产环境平稳运行。这一章讲部署相关的实操。

4.1 环境变量与配置管理

生产环境的配置和本地完全不同。我强烈建议用一套 .env.生产 模板来维护差异。

配置项本地开发生产环境
DATABASE_URLfile:./dev.dbPostgreSQL 连接串
NEXTAUTH_URLhttp://localhost:3000https://正式域名
NEXTAUTH_SECRET随便生成的串高强度的随机串
第三方 OAuth 密钥测试专用正式应用专用

有个特别容易忽略的坑:第三方 OAuth 应用里配置的回调地址必须和生产环境域名一致。很多人在本地用 GitHub 登录好好的,部署后一直提示"回调地址不匹配",结果查了半天才发现是第三方平台上漏改了地址。这个动作要在部署计划里专门留一项。

Secret 管理上,我不建议把生产密钥直接写进代码仓库。哪怕托管在私有仓库,一旦有合作者加入或仓库被人复制,都会泄露。t3code 的 CI/CD 流程里,环境变量是从部署平台的 Secret 存储中注入的,本地开发用.env文件,仓库只保留.env.example模板。这个规范你接手后一定要保留。

4.2 数据库迁移与备份

生产数据库最怕的就是"直接改 schema 不动迁移文件"。t3code 里我专门约定了一个部署流程:

  1. 本地改完schema.prisma;
  2. 执行npx prisma migrate dev --name 业务描述生成迁移文件;
  3. 代码评审时,迁移文件必须一起提交并评审;
  4. 部署前备份生产数据库;
  5. 执行npx prisma migrate deploy应用迁移。

我最开始犯过一个错误:在生产环境直接执行prisma db push,结果因为数据库有一行数据不满足新约束,导致数据被清理。后来虽然通过备份恢复,但也浪费了不少时间。所以在 t3code 里,迁移必须走migrate deploy,不允许手滑执行 db push。这个约束已经写进团队的部署文档里了。

4.3 构建与部署

t3code 的构建流程比较标准,核心命令是:

npm run build npx prisma generate

记得在部署前先执行 Prisma generate,否则生成的客户端代码和最新的 schema 不一致,接口会出现奇怪的报错。我一般把这步写进 CI 的构建脚本里,保证每次都是自动完成的。

如果你用的是 Vercel 之类的托管平台,它会自动识别 Next.js 项目的构建命令,部署非常轻松。我自己更习惯用 Docker 部署到自有服务器,这样可以统一运行环境。一个基础的多阶段构建 Dockerfile 大概长这样:

FROM node:20-alpine AS base WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npx prisma generate RUN npm run build FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV production COPY --from=base /app/.next ./.next COPY --from=base /app/node_modules ./node_modules COPY --from=base /app/package.json ./package.json EXPOSE 3000 CMD ["npm", "run", "start"]

这个 Dockerfile 的精髓在于构建阶段和运行阶段分离,最终镜像只包含运行所需的最小文件,体积小、启动快。生产跑起来之后,记得确认环境变量是否完整。最容易漏的是NEXTAUTH_SECRET和数据库的连接串,缺少任何一个都会导致应用启动时报错。

4.4 性能与监控

部署上线后不能什么都不管。t3code 里我加入了一些轻量监控:

  • 开启 Next.js 内置的实验性指标,可以在构建时看到每个路由的加载包大小;
  • 对 tRPC 的关键查询进行耗时统计,写进日志;
  • 使用第三方 APM 工具监控 API 错误率和页面错误率。

一个值得参考的经验:优先监控 tRPC 的查询耗时和数据库连接池占用,因为全栈应用的大多数性能问题都出在这两处。前端页面的慢,百分之八十是数据查询慢导致的,而不是前端代码本身慢。

我平时用 Grafana + Prometheus 收集指标,但如果你不想上重型工具,直接看 Vercel 的 Analytics 和 Postgres 的慢查询日志也能发现绝大多数问题。关键是"有监控"而不是"用什么监控"。

5. 真实踩坑记录:这些坑不亲自跑一遍很难发现

每个项目都会遇到一些奇奇怪怪的问题。把这些记录下来,比再好的理论课程都有价值。

5.1 问题速查表

问题现象根因解决办法
登录后用户没有写入数据库NextAuth 缺少 Prisma Adapter在 authOptions 中配置 adapter
页面一加载就报 fetch failedtRPC API 地址配置成了外部域名检查NEXTAUTH_URL和 API 路由前缀
部署后样式全部丢失构建时 Tailwind 没扫描到新页面检查tailwind.config.ts的 content 路径
prisma migrate dev 卡住数据库连接串不对,或迁移冲突先恢复备份,再手动删除冲突迁移
tRPC 调用出现类型不匹配前端未停止并重新生成类型重启 dev server,确保 tRPC 类型新鲜

第一个问题值得展开。很多人第一次跑 t3code,GitHub 登录成功后页面上确实显示已登录,但数据库 user 表空空的。原因就是 NextAuth 的 session 会话没问题,但没有 session strategy 接到数据库,用户记录就不会落库。在authOptions里加一个 Prisma adapter,并确保prisma/schema.prisma里有 NextAuth 对应的模型(Account、Session、VerificationToken),这个坑就解决了。

第二个问题是环境变量的大坑。我维护 t3code 的早期版本时,在某个环境里把NEXTAUTH_URL配成了网关地址而不是应用本身的地址,导致 tRPC 的请求全部打到错误地址上,报错信息非常具有迷惑性,看起来像网络断了。排查过程极其崩溃,后来用浏览器开发者工具逐条对比请求地址才发现问题。

5.2 我总结的几条铁律

这些经验是长期踩坑换来的,写下来给后面接手的人参考:

第一,永远不要把生产环境的 Secret 写进仓库。哪怕是个人项目,也要遵守这条纪律,否则日后公开项目时就是一场灾难。

第二,凡是能用类型推导解决的问题,不要手写。t3code 的核心价值就是类型安全,如果你为了省几行代码绕开 tRPC 去写自定义 fetch,那么后面的维护成本迟早会加倍奉还。

第三,数据库变更必须走迁移文件,不要在生产环境直接改表。这是我个人吃过亏的地方。一个小疏忽不仅会丢数据,还会让团队成员在同步开发环境时遇到大量冲突。

第四,加入新依赖前问自己一句:这个功能用现有技术栈能不能实现?T3 Stack 的组合已经足够覆盖绝大多数全栈应用需求,盲目堆依赖只会让项目变得臃肿。

6. 后续可以怎么扩展

t3code 目前已经稳定支撑了两个内部项目,一个做内容管理,一个做简单的团队协作工具。后续我打算做三件事:

一是加一个"模块生成器"脚本,用命令行一键生成一个包含数据模型、tRPC router 和前端页面的完整业务模块,进一步降低新增功能的成本。

二是把现有的错误处理机制升级成更统一的格式,让前端能更友好地展示业务错误,而不是只显示内部异常信息。

三是写一份更完善的文档,讲解 t3code 里的设计决策。因为代码只能告诉你"怎么做",文档才能告诉别人"为什么这样做"。文档比代码更能防止项目在协作中腐烂。

如果你准备基于 t3code 开始自己的项目,我最大的建议是:不要急着加功能,先花一天时间把脚手架生成的代码从头到尾读一遍。搞清楚它已经替你处理了多少事情,你才能在这个基础之上真正高效。坦白讲,维护这个项目最大的收获,不只是拥有了一套开箱即用的模板,而是逼着我把很多"想当然"的流程重新严谨地走了一遍。这个习惯,也让我在其他项目里受益不少。