t3code:把 T3 Stack 从概念变成可落地的全栈工程模板

发布时间:2026/10/27 23:04:33
t3code:把 T3 Stack 从概念变成可落地的全栈工程模板 先别急着下结论我第一次在技术群里看到“t3code”这个项目名时也以为只是又一个蹭 T3 Stack 热度的玩具。但真正把它拆开跑了几遍之后我发现它其实是把“T3 技术栈”从概念变成可运行代码的一套极其实用的全栈模板。t3code 解决的是每一个想用 Next.js TypeScript tRPC Prisma Tailwind 从零起项目的人都会撞上的那堵墙单看每个框架都懂合在一起就不知道文件该放哪、上下文怎么传、权限怎么拦。这篇文章就把我自己从依赖这个项目到改造这个项目的完整过程写下来包括选型逻辑、实操步骤、踩过的坑以及最后为什么把它重构得越来越轻。1. t3code的整体设计与思路拆解1.1 名字背后的技术主张t3code 这个名字拆开看一半是 T3一半是 code。T3 指的是 T3 Stack一套主打“全栈 TypeScript”和“端到端类型安全”的现代应用技术组合核心成员包括 Next.js、TypeScript、Tailwind CSS、tRPC、Prisma通常还会搭配 NextAuth.js 处理认证。而 code 在这里不是单纯的“代码文件”它代表一个更实际的目标把技术选型变成可以直接复制、直接运行、直接改造的工程骨架。很多文章都在聊 T3 Stack 的理念但缺少一份能落地的参考实现t3code 想补的就是这个位置。我一开始接触 T3 Stack 时最大的困惑是这些技术明明各司其职为什么合在一起会让人头大。Next.js 负责页面和 API 路由TypeScript 负责类型约束tRPC 让前后端像调用本地函数一样通信Prisma 负责数据库建模和迁移Tailwind 负责样式NextAuth 负责登录会话。听起来分工很清楚可真要动手组装时光是一个“如何在 tRPC context 里正确注入当前用户”就能卡住大半天。t3code 通过一组约定俗成的目录结构和配置把这些衔接问题提前解决掉让使用者把精力放到业务上。1.2 从痛点出发的项目定位做新项目最大的隐性成本不是写代码而是搭架子。每一轮新技术、新版本发布后网上教程各讲各的有的用 App Router有的还在用 Pages Router有的把 Prisma 放在lib里有的放在server/db里。这些差异在五分钟的 demo 里看不出问题等你要加登录、加权限、加数据库关联字段时坑就一个接一个冒出来。t3code 的定位就是当这个“已经验证过的架子”它内置了一套经过多次实战打磨的文件组织方式团队直接在这个骨架里写业务而不是每次都在项目初始化阶段重新讨论目录规范。它适合三类人。第一是全栈开发者想快速把想法变成可部署的产品原型第二是刚接触 T3 Stack 的初学者需要一个能边跑边学的完整参考工程第三是前端团队想在一个项目里统一 TypeScript 类型安全和 API 调用规范。注意它不是一个“开箱即用的低代码平台”你仍然需要写代码只是不需要再纠结那些重复了无数遍的基建配置。1.3 几条设计原则我在维护 t3code 的过程中逐渐总结出几条设计原则这些原则直接影响了项目的每一次提交。第一是默认安全TypeScript 严格模式打开所有输入用 Zod 做校验涉及用户数据的接口默认走受保护路由不给偷懒留口子。第二是最小依赖能用框架原生能力解决就不引第三方库甚至组件库都可以不要因为模板的目标是提供业务起点而不是把 node_modules 变得又大又丑。第三是可裁剪每个功能模块尽量独立成目录比如认证相关放src/server/auth数据库入口放src/server/db不想要某块功能时删目录再移除配置即可。第四是环境隔离本地、测试、生产的数据库和密钥通过环境变量分开治理不允许在代码里写死连接串。这些原则听起来很朴素但它们共同保证了 t3code 的复杂度是“线性增长”的。新加一个页面不用改好几处跳跃的点新加一张表只要执行迁移就能从 Prisma Client 里获得完整类型。如果你之前用过那种为了展示技术栈而堆了十几个库的脚手架应该能体会这种克制的必要性。2. 核心选型与底层原理解读2.1 Next.js作为全栈容器选 Next.js 作为整个应用的外壳不是因为它是 React 框架里最流行的而是因为它天然适合“前后端一体”的业务形态。你可能也会问为什么不用 Vite 加一个独立的 Node 服务因为在 T3 Stack 的场景里大部分业务是 CRUD 加页面渲染单独拆一个后端服务只会增加部署复杂度。Next.js 的路由系统既提供前端页面又提供api接口路由刚好可以让 tRPC 挂载在同一进程里前后端共享一套 TypeScript 类型。更关键的是Next.js 支持服务端渲染和静态生成这对内容型页面非常友好Vite 在纯客户端场景更顺手但要处理 SSR 就得自己再接渲染服务多出很多脏活。2.2 tRPC取消前后端契约维护说到 tRPC 就不得不提一个长期存在的痛点前后端联调时接口文档总是滞后于代码。REST 需要定义 URL、方法、参数和返回结构GraphQL 需要维护 schema 和 resolver 的对应关系这些都是在“类型系统之外”额外建立的一套契约。tRPC 的思路非常直接后端路由和前端调用之间共享 TypeScript 类型只要后端router定义了 procedure前端立即能拿到对应函数的完整入参和返回值类型。可以这样类比在传统方案里前端和后端是两个互相不认识的邻居沟通时必须通过中介递纸条、翻译格式而 tRPC 让两边像住在一个屋檐下的家人想找对方就直接喊一声不需要中间层。当然这个快感的代价是“类型安全只在 TypeScript 编译期生效”所以它通常要和 Zod 配合在运行时把不可信的客户端输入再校验一遍。这也是 t3code 把 Zod 作为默认依赖的原因。2.3 Prisma数据库模型即代码Prisma 在这套组合里的角色很特别它比普通的 ORM 更强调“声明式建模”。你在prisma/schema.prisma里写清楚模型和关系Prisma 负责两件事生成迁移 SQL并生成一个类型安全度极高的查询客户端。也就是说你在代码里写prisma.post.findMany()时编辑器会直接提示Post有哪些字段关联关系怎么取不用再靠脑子记住数据库列名。这对全栈开发极其友好因为后端代码和数据库结构的偏差会在编译期就被 TypeScript 发现而不是等到线上环境报一个column not found。有一点值得注意Prisma 能解决的场景主要是 CRUD 密集型应用。如果查询极其复杂、需要重度使用窗口函数或动态表名这层抽象会增加绕路成本。但 t3code 的目标是业务系统90% 的场景用 Prisma 都能顺畅覆盖剩下的可以手写 SQL 绕过 Prisma 执行它有$queryRaw这样的逃生舱。2.4 Tailwind 与 NextAuth 的作用Tailwind 的选型逻辑很简单新项目刚开始时我们不想花太多精力维护模块化 CSS 或 styled-components 的样式组织。Tailwind 的原子类在页面快速迭代阶段非常高效它不强制你提前抽象组件而是让样式跟着 JSX 结构走后期再重构也不心疼。搭配 t3code 的默认配置Tailwind 会自动扫描src下的 TypeScript 和 TSX 文件实时生成用到的样式类。NextAuth.js 则负责认证这条最容易被低估的链路。它支持几十种 OAuth 平台而且自带 JWT 和数据库 session 两种模式。当和 Prisma 一起使用时只需配置PrismaAdapter注册、登录、登录态查询的底层表结构都由 NextAuth 自动管理。你唯一要操心的是在NEXTAUTH_SECRET里填一个足够随机的密钥再在 Provider 控制台配好回调地址。这些看起来很琐碎但 t3code 已经把默认配置写好剩下的就是填空。3. 实操过程把一个功能从零跑通3.1 初始化 t3code 项目实战环节我会以一个最简单的“用户发文章”功能为例把 t3code 从空工程跑到线上。第一步当然是初始化项目。如果你在 GitHub 上克隆或下载 t3code 仓库也可以直接用官方脚手架生成一个 T3 Stack 项目再覆盖改造。初始化命令如下npm create t3-applatest my-t3code-app cd my-t3code-app npm install官方脚手架会让你交互式选择模块建议把 Next.js、TypeScript、Tailwind CSS、tRPC、Prisma、NextAuth.js 全部勾上。生成完成后最重要的目录结构如下prisma/ schema.prisma src/ app/ api/trpc/[trpc]/ route.ts server/ api/ root.ts routers/ post.ts auth.ts db.ts trpc/ react.tsx server.ts每个文件各司其职src/server/db.ts创建 Prisma Client 实例src/server/auth.ts构造 NextAuth 配置并导出 session 处理逻辑src/trpc/server.ts负责创建服务端上下文和 procedure 基类src/trpc/react.tsx提供前端 React Hooks。理解这些依赖关系后你往里面加业务就会非常顺。3.2 定义数据模型并完成首次迁移接下来先改数据库模型。我给prisma/schema.prisma增加一个Post模型并把它和User建立外键关系。为了演示方便下面的代码省略了 NextAuth 自动生成的Account、Session等模型实际仓库里它们会原样保留model User { id String id default(cuid()) name String? email String? unique posts Post[] } model Post { id String id default(cuid()) title String content String authorId String author User relation(fields: [authorId], references: [id], onDelete: Cascade) createdAt DateTime default(now()) updatedAt DateTime updatedAt }模型定义完成后执行首次迁移npx prisma migrate dev --name init这里我特意用了cuid()而不是自增id。因为自增数字会暴露业务总量而且分布式环境下生成容易冲突CUID 是随机的字符串 ID在本地和多个实例里各自生成也不必担心碰撞。跑完迁移后Prisma Client 会自动在 node_modules 里生成对应的 TypeScript 类型。你会发现官方脚手架通常还会提示你运行npx prisma generate实际上 migrate dev 已经帮你做了这一步。3.3 编写 tRPC 路由与鉴权数据库模型有了现在开始写后端查询接口。在 T3 Stack 的约定中所有后端接口都集中在router里。修改src/server/api/routers/post.tsimport { z } from zod; import { createTRPCRouter, publicProcedure, protectedProcedure } from ~/server/api/trpc; export const postRouter createTRPCRouter({ list: publicProcedure.query(async ({ ctx }) { return ctx.prisma.post.findMany({ orderBy: { createdAt: desc }, }); }), create: protectedProcedure .input( z.object({ title: z.string().min(1), content: z.string().min(1), }) ) .mutation(async ({ ctx, input }) { return ctx.prisma.post.create({ data: { title: input.title, content: input.content, authorId: ctx.session.user.id, }, }); }), });这是我个人非常喜欢的一个设计列表接口用publicProcedure创建文章接口用protectedProcedure。两者的区别在于protected 会先检查 session如果请求未登录会直接抛出一个未授权错误前端useMutation的回调里能清晰拿到异常状态。它比你在每个接口里手工判断session要省很多重复代码。输入校验用 Zod意味着 title 和 content 至少有一个字符空字符串会在进入业务逻辑之前被拦截。3.4 前端页面与数据流后端接口写完前端页面只需几行代码就能联调。t3code 使用 tRPC 提供的 React Hooks不需要额外配 SWR 或 React Query。下面是一个展示文章列表的组件use client; import { api } from ~/trpc/react; export function PostList() { const { data: posts, isPending } api.post.list.useQuery(); if (isPending) return p加载中.../p; return ( ul {posts?.map((post) ( li key{post.id} {post.title} by {post.author.name} /li ))} /ul ); }注意 App Router 的约束tRPC Hooks 依赖 React Context所以组件必须注明use client。理论上你也可以在 Server Component 里直接用 tRPC server 端调用但涉及状态变更和缓存失效还是走客户端 Hooks 更直观。表单提交的写操作同样简单const utils api.useUtils(); const createPost api.post.create.useMutation({ onSuccess: () { utils.post.list.invalidate(); }, });onSuccess里手动使list查询失效这是 React Query 风格的缓存管理方式等下次查询时自动重新拉取最新列表。整个过程不需要写 fetch、不需要关心 URL 拼接前后端类型都是同步的字段拼错会在编译期立刻报出来。3.5 环境变量、认证与部署本地跑通只是开始部署时最容易出问题的其实是环境变量。t3code 默认会自动加载.env核心变量就几个DATABASE_URLpostgresql://user:passwordlocalhost:5432/mydb NEXTAUTH_SECRET一个足够随机的密钥 NEXTAUTH_URLhttp://localhost:3000如果你接的是 GitHub OAuth还要设置真正的GITHUB_CLIENT_ID和GITHUB_CLIENT_SECRET。部署到 Vercel 时NEXTAUTH_URL必须是线上正式域名而不是 localhost。数据库迁移方面建议在构建前执行npx prisma migrate deploy它会按迁移文件的历史顺序执行所有未应用的迁移不会因为构建实例不同而重复创建表。部署完成后t3code 里那几个核心模块就完整跑通了。4. 常见问题与排查技巧实录4.1 登录跳转报 redirect_uri 错误这是我在群里被问得最多的问题。现象是本地跑 t3code点击 GitHub 登录浏览器跳到/api/auth/callback/github?code...时报错提示 redirect_uri 不匹配。绝大多数原因只有一个GitHub OAuth App 回调地址里填的http://localhost:3000/api/auth/callback/github然而你实际访问的是http://127.0.0.1:3000或者NEXTAUTH_URL配置的是其他地址。解决办法是让两种写法保持一致最好在 OAuth 控制台同时登记 localhost 和 127.0.0.1 两个回调地址。NEXTAUTH_SECRET 不能随便填个短字符串建议用openssl rand -base64 32生成预防线上会话被猜解。4.2 Prisma 迁移后表不同步开发途中你最可能犯的错误是改完schema.prisma后没有跑迁移而是手动去数据库工具里加了一个字段。结果应用跑起来Prisma Client 还按旧类型查询直接报column does not exist。t3code 的要求很明确开发阶段任何 schema 变更都走npx prisma migrate dev它会自动生成一个新迁移文件并同步本地数据库CI 和部署阶段使用npx prisma migrate deploy不要在生产环境手动执行dev模式。如果你只是临时对拍一下模型想快速更新本地库结构可以用npx prisma db push但它不会生成迁移历史不适合多人协作场景。4.3 tRPC 请求一直 loading 不返回如果前端页面卡在“加载中”先去浏览器开发者工具的 Network 面板确认请求是否发出。若是请求根本没出现通常是组件没有包在TRPCReactProvider里或者组件被错误地当成 Server Component 使用了 Hooks。若是请求发出但一直 pending最常见的原因是src/app/api/trpc/[trpc]/route.ts缺失或路径写错。App Router 对目录结构要求非常严格文件名里的[trpc]必须和实际调用的路径一致。还有一种情况是浏览器控制台报跨域错误同一域名下不会出现但如果你在另一个端口直接调 t3code 的接口就会遇到tRPC 默认没有开放 CORS在 API Route 里手动处理Access-Control-Allow-Origin前不建议生产跨域。4.4 Tailwind 样式在某页完全不生效样式问题排查优先级如下先看tailwind.config.ts里的content是否覆盖到对应文件再看 className 是否有拼写错误最后考虑是否用了字符串拼接动态类名比如bg-${color}-500。Tailwind 在构建时会静态扫描源码扫描不到的内容不会生成对应 CSS。t3code 默认的 content 配置包含所有 TS/TSX 文件所以正常的文件不会漏但如果你把类名藏在src/data下的.ts配置里且没有加入 content 扫描范围就会出现“明明写了bg-red-500却没有任何样式”的诡异现象。解决方案是增加 content 路径或者使用 safelist 显式声明需要保留的类名。4.5 类型报错该不该慌端到端类型安全带来的一个现象是后端接口改一个字段类型前端所有引用点立刻标红。这不是 bug而是 tRPC 正常工作。很多刚开始用 t3code 的人会被编译器的红色提示吓到实际上你只需要遵循它引导的方向把类型补对就行。我自己的习惯是每改完一个 router立刻跑一次npx tsc --noEmit把类型错误控制在最小范围。此外注意 Zod input 的类型推断和前端表单组件里的字段名保持一致否则 mutation 的入参类型也会报错。这类错误通常 10 分钟内就能解决但确实能拦住一大批抄作业抄到一半就放弃的初学者。5. 维护 t3code 之后的几点真实体会5.1 脚手架最忌讳贪多t3code 早期版本不只包含认证、CRUD我还尝试过把多租户、文件上传、定时任务全部内置最后发现模板越来越重、新用户根本不知道从哪里开始看。后来我痛下决心砍掉大部分模块只保留用户和文章两条完整链路反而收到了更多反馈。模板的本质不是“功能合集”而是“最小可理解的起点”。你要让使用者第一次跑起来时能在半小时内弄清楚每个目录为什么存在而不是淹没在几十个隐式封装里。5.2 安全默认值才是真正的护城河在这个项目里给我留下最深印象的不是某个花哨组件而是protectedProcedure加 Zod 输入校验这个组合。它们让写错数据、绕过鉴权这些低级 bug 变得越来越难因为默认情况下接口不会对外部输入保持宽松。这种“默认安全”的心态后来被我带进了日常编码先限制输入、再写业务逻辑、对外暴露最小权限。看起来是写模板时的顺手设计实际上能省掉大量线上事故。5.3 下一步的扩展建议如果你也想维护一个类似 t3code 的全栈模板我的建议非常简单先写一个能跑通的 Todo 功能然后拆出认证、数据库访问、接口调用三层最后才考虑 abstractions。能在三步内解决的事情不要提前提取复杂设计。如果你直接拿来改最舒服的路径是先把prisma/schema.prisma里的模型改成自己的业务表跑一次迁移接着改src/server/api/routers下的 router。跑通一条端到端链路后你对 t3code 的理解会比看十篇架构文章都更有底。我自己还在持续往里面补充部署样例和多环境配置但这都是功能扩展核心骨架已经足够稳定值得你花一个下午上手。

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询