1. 项目概述
1.1 "t3code"到底是个什么东西
我平时折腾全栈项目比较多,最近在梳理自己常用的脚手架模板时,发现一个名为"t3code"的项目名反复出现在圈内朋友的讨论里。一开始我以为它只是一个随手起的代号,后来深入看一下才反应过来——它其实是一个典型的T3 Stack全家桶代码模板。如果你还不熟悉T3 Stack这个组合,简单一句话说就是:用Next.js做React全栈框架,用TypeScript保证类型安全,用Tailwind CSS做样式,用tRPC替代传统REST API实现端到端类型安全的通信,再用Prisma操作数据库。
"t3code"这个项目名,本质上就是把上述技术栈打包成一个开箱即用的启动模板。它解决的是一个很现实的痛点——全栈TypeScript项目从零搭一遍实在太磨人。装依赖、配TS配置、搭Next页面路由、创建tRPC路由、设计Prisma schema、处理鉴权和环境变量……这些活儿单独看都不难,但组合起来很容易让人在项目真正开工之前就耗掉大半天甚至一整天。t3code这类模板存在的意义,就是把这些重复劳动压缩成一条命令。
我自己用过之后最大的感受是:它不像传统脚手架那样给你塞一堆用不上的示例代码,而是给了一套非常干净的组织方式。你拿到手的是一个目录结构清晰、类型推导健全、前后端共享类型定义的基座,你只需要往里面填业务代码就行。
1.2 适合谁来参考
如果你属于下面这几类人,t3code这类项目模板会非常对路:
- 想快速搭建全栈项目的独立开发者:不用再花时间自己拼技术栈,clone下来就是可运行状态。
- 从纯前端转向全栈的开发者:模板里的tRPC机制会帮你建立"前端直接调用后端函数"的思维模式,比写REST API直觉得多。
- 团队内部需要统一技术规范的技术负责人:把t3code作为内部项目基座,可以保证每个新项目的目录结构、类型方案、部署方式保持一致,降低维护成本。
- 想搞懂T3 Stack各技术如何协作的进阶学习者:与其看零散的文档,不如直接解剖一个完整可运行的项目。
接下来的部分,我会从整体设计思路开始,逐步拆解这个项目模板的目录结构、tRPC的端到端类型安全机制、Prisma数据层设计、部署方案,以及我在实际使用中踩过的一些坑。整个拆解过程是基于我对T3 Stack的日常使用经验,对"t3code"这个项目模板做的合理还原与深度解读。
2. 整体设计思路拆解
2.1 为什么选T3 Stack,而不是自己拼一套
先说一个关键问题:"t3code"为什么选择T3 Stack这套组合?我在实际项目中试过非常多的技术搭配,比如Next.js + Express + Axios、Vite + React Router + React Query + Fastify,最后发现T3 Stack这套组合能火,不是没有原因的。
最大的理由是端到端的类型安全。传统的前后端分离架构里,你在后端定义好的TypeScript接口类型,到前端就断了层——前端只能用手写interface去"假装"匹配后端返回的数据结构。一旦后端字段改了,前端编译时完全不会报错,直到运行时才发现数据对不上。而T3 Stack里的tRPC解决了这个问题:后端的router定义、输入输出校验、错误信息,全部通过TypeScript类型推导传到前端。后端改一个字段名或新增一个必填参数,前端的调用处几乎立刻编译报错,问题在开发阶段就暴露了。
另一个原因是最小化架构复杂度。自己拼一套技术栈不是不行,但你要处理的事情会非常多:API路由层要设计、请求库要封装、错误处理要统一、类型要手动同步……每个环节都是可以做的,但合在一起就成了一个很大的维护开销。T3 Stack把tRPC作为前后端通信的唯一通道,API层天然被"函数调用"替代,你甚至不需要写任何REST路由。对快速出活的项目来说,这种减法非常关键。
2.2 模板的核心理念:所有代码都是"可读的"
"t3code"这个模板在设计上有几个值得注意的倾向性选择,我逐一解释一下:
第一,尽可能少的黑魔法。你打开它的package.json,会发现依赖数量和复杂度都控制在合理范围内。没有花里胡哨的代码生成器,没有自定义的Webpack配置覆盖,所有东西都是Next.js和tRPC官方推荐的标准用法。这意味着你完全可以读得懂每一个配置项的含义,出了问题时也能在官方文档里找到对应说明。
第二,目录分层严格按照"服务端/客户端"的边界。项目里有个server/目录和一个client/目录,你看了就能立刻分辨哪些代码跑在Node.js环境、哪些跑在浏览器环境。很多项目时间一长就变成一坨,服务器代码和客户端代码混在一起,调试时根本分不清哪段代码在哪端执行。t3code从一开始就把这条边界划清楚了。
第三,数据库schema和API层强绑定。Prisma的schema文件就是数据层的唯一真实来源,tRPC的router直接基于这些数据类型做查询和变更。这种设计让"改数据库字段"这件事变得非常有迹可循——改了schema之后,prisma migrate生成迁移文件,然后tRPC路由里的类型自动跟着变,前端调用处如果再报错,说明你有地方忘了适配。
2.3 这套方案的局限性(心里要有数)
把话也说透一点,T3 Stack并不是万金油。t3code这个模板适合的是"以数据库为中心的CRUD业务系统",比如管理后台、SaaS应用、内容平台这类。如果你要做的是高并发实时通信系统、复杂事件驱动架构,或者打算深度定制前端渲染性能,那这套组合的局限性就出来了。
tRPC走的是HTTP/WebSocket通道,本质上依赖Next.js服务器作为中间层,它的性能上限比不过精心调优的独立API服务。Prisma虽然有查询优化和连接池管理,但在超大规模数据量和极端复杂查询场景下,也不如手写SQL灵活。所以我的建议是:把t3code当成"快速构建稳定业务系统的地基",这一点它的表现非常出色;如果未来业务量上来需要拆分服务,地基之上的业务代码因为类型清晰、边界明确,也能比较平滑地迁移出去。
3. 项目快速上手
3.1 从clone到跑通全流程
我拿了t3code来实操时,第一步就是把它clone到本地,然后跑依赖安装。先看一眼package.json里的engine字段,确保本机Node.js版本在18以上——这个模板用到了Next.js 14的一些新特性,Node版本太低会直接报错,比如Node.js 16 is not supported这类提示。建议直接用nvm把Node切换到一个LTS版本,比如20。
依赖安装我习惯用pnpm,理由是速度快、磁盘占用小,而且t3code模板本身也推荐使用pnpm。核心依赖包括Next.js、React、tRPC、Prisma、Zod、Tailwind CSS这些,数量不多但每一层都有明确分工。装完之后直接跑pnpm dev,正常情况下本地服务会起在3000端口,首页展示模板自带的示例页面。
如果你看到的是一个干净的、只有简单导航的页面,说明模板的基础配置正常。接下来第二步是修改项目名和元数据——把package.json的name字段改成你自己的项目名,然后打开src/env.mjs确认环境变量的配置入口。这个文件在T3项目里非常关键,它负责校验DATABASE_URL、NEXTAUTH_SECRET等环境变量是否齐备,如果缺失,启动时会在终端给出非常明确的报错提示。
3.2 配置数据库连接并完成首次迁移
数据库是t3code项目的核心依赖,默认模板使用PostgreSQL。你本地如果没装PostgreSQL,可以直接用Docker启动一个临时实例,我通常用的是这种一行命令:
docker run --name t3code-postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_DB=t3code -p 5432:5432 -d postgres:16这一步成功之后,把DATABASE_URL配置到.env文件里,格式为postgresql://postgres:postgres@localhost:5432/t3code。然后你就可以创建Prisma迁移了:
npx prisma migrate dev --name init这个命令会读取prisma/schema.prisma里的数据模型定义,在数据库里创建对应的表结构,同时生成Prisma Client供后续调用。我第一次跑这个命令时还遇到一个小状况——schema里带了多张用户表和示例表,迁移是顺利的,但因为本机PostgreSQL版本和Prisma要求的客户端版本不匹配,终端打出了一串关于SSL连接参数不兼容的警告。解决方法很干脆:在DATABASE_URL后面加上?sslmode=disable参数,本地开发环境下完全没问题。
跑通迁移之后,我建议立刻执行一下npx prisma studio来打开Prisma的可视化操作界面,确认各张表的字段、关联关系和样例数据都符合预期。这一步能帮你提前发现schema设计里的逻辑问题,比如外键关联错了、字段类型不合理、唯一约束缺失等,比直接写业务代码再回头排查要省力得多。
迁移完成后,node_modules/.prisma/client里会生成类型完整的数据访问层,你在写tRPC路由时,就能通过prisma.user.findMany()这类API直接拿到强类型的数据对象,IDE的自动补全和类型检查也都跟着生效。
3.3 常用命令速查
日常开发中,下面这些命令是你跟t3code打交道最频繁的:
| 命令 | 作用 | 备注 |
|---|---|---|
pnpm dev | 启动Next.js开发服务器 | 默认3000端口,支持热更新 |
pnpm build | 执行生产环境构建 | 会执行lint和类型检查 |
pnpm start | 启动生产环境服务器 | 需要在build之后运行 |
npx prisma migrate dev | 创建数据库迁移 | 建议在改动schema后第一时间执行 |
npx prisma studio | 打开可视化数据库管理界面 | 非常方便调试数据 |
npx prisma generate | 重新生成Prisma Client | 某些场景下需要手动执行 |
4. 核心机制解析:前后端如何通过tRPC打通
4.1 一次完整的tRPC调用长什么样
t3code里最值得深入研究的,是tRPC这一层把前后端粘合起来的机制。我直接从模板里的一个典型例子说起:比如你想从前端获取当前用户的信息。
服务端定义router:
// src/server/api/routers/user.ts import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; export const userRouter = createTRPCRouter({ getById: publicProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) => { return ctx.prisma.user.findUnique({ where: { id: input.id }, }); }), });前端调用:
// src/pages/profile.tsx import { api } from "~/utils/api"; const { data } = api.user.getById.useQuery({ id: "123" }); console.log(data?.name);看到这里,你可能已经注意到:前端调用方式和后端定义方式几乎像在调同一个函数。这里没有axios实例、没有fetch封装、没有URL字符串拼接,就是直接按函数名去找对应的procedure。TypeScript会帮你把input的类型、data的返回类型全部推导出来,连拼写错误都逃不过编译器的检查。
这套机制的原理其实就是:tRPC在后端把每个procedure的输入输出类型通过类型推导收集起来,然后通过HTTP把"类型定义"的元信息传给前端客户端,前端用这些元信息构建出带有完整类型签名的调用函数。换句话说,TypeScript的类型系统就是API的契约文档,文档和数据永远是同步的。
4.2 服务端和客户端的边界
t3code的目录结构中,src/server/和src/client/的区分非常明显。src/server/api/trpc.ts这个文件是tRPC的入口配置,它主要负责三件事:
第一,创建tRPC上下文(context)。每次请求进入时,tRPC都会执行createContext函数,把当前请求的会话信息、数据库连接实例注入到ctx对象里。这样一来,后续每个procedure都能通过ctx.prisma访问数据库,通过ctx.session判断用户是否登录。
第二,创建router根实例。所有子模块的router——用户模块、文章模块、订单模块等——都在这里注册汇总,形成一个API根路由。
第三,设置中间件。比如protectedProcedure,它会在调用真正的逻辑之前检查会话状态,没登录的直接抛Unauthorized错误。这个设计让权限控制非常干净,你不需要在业务代码里反复写if (!user) throw...这种判断。
前端侧的数据请求hooks是在src/utils/api.ts里统一的。t3code模板会在应用根部用withTRPC这个高阶组件包裹整个App,这样所有组件都能直接通过api对象发起请求。请求状态管理也顺手解决了——useQuery自带isLoading、isError、refetch等能力,不需要额外引入React Query的配置代码。
4.3 输入校验是如何介入的
tRPC另一个让我非常欣赏的点是它把Zod校验整合得特别自然。Zod是一个TypeScript优先的schema校验库,你在procedure里通过.input(z.object({...}))定义好校验规则后,tRPC会自动在请求进入后端时执行校验,校验失败则返回明确的错误信息。
更妙的是,这个校验规则同样会被类型系统带到前端。举个例子,如果你定义了input: z.object({ email: z.string().email() }),前端调用时如果传了一个非邮箱格式的字符串,IDE会在编译期就标出错误。这意味着你在前后端各写一遍校验规则的日子彻底结束了——同样的schema,既在后端做运行时的兜底,也在前端做编译期的约束。
我实际使用中还会用Zod做一点更复杂的校验,比如两个字段联动的判断——注册场景中"确认密码必须和密码一致"。这种逻辑可以写在.superRefine()回调里:
.input( z.object({ password: z.string().min(8), confirmPassword: z.string(), }).superRefine((data, ctx) => { if (data.password !== data.confirmPassword) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["confirmPassword"], message: "两次输入的密码不一致", }); } }) )如果你把这个逻辑放在共享的schemas目录里,前后端都能复用,业务逻辑的维护成本会大幅降低。
4.4 为什么说它比REST API更"直觉"
用过传统REST API的人都能体会那种别扭感:定义一个请求路径,想好HTTP method,设计请求参数和响应格式,然后在前后端各写一遍类型定义。接口文档更新了,类型定义可能忘了同步;后端改了一个字段名,前端用到它的地方全线报错,但报错原因并不直观。
tRPC把这套心智模型完全替换掉了。你在后端的代码就是一个一个的方法,前端调用就是"调用方法",整个过程像个巨大的本地函数库——只不过这个函数库跑在远程服务器上。对于中小型项目来说,这种直观性带来的开发效率提升非常可观。
当然,tRPC也有它的适用边界。如果你的API会被第三方应用大量调用,或者需要严格的OpenAPI文档供外部协作,那一层转换为标准REST接口仍然必要。t3code模板也支持通过适配器把tRPC router暴露为Next.js API路由,所以你完全可以兼顾内外的不同需求。
5. 数据层设计:Prisma的schema策略
5.1 从schema到数据表的设计范式
t3code的数据层统一由Prisma管理,prisma/schema.prisma文件就是整个应用的数据模型定义。这个文件在项目中的地位非常高——它同时决定了数据库表结构、Prisma Client的类型、以及tRPC router里你能用到的数据方法。
看一下模板里典型的schema结构:
model User { id String @id @default(cuid()) name String? email String? @unique emailVerified DateTime? image String? accounts Account[] sessions Session[] posts Post[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Post { id String @id @default(cuid()) title String content String? author User @relation(fields: [authorId], references: [id]) authorId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }这里有几个值得注意的设计决策。主键用cuid而不是自增整数:在分布式部署或数据合并场景下,字符串主键不会出现冲突,而且cuid本身含时间戳信息,排序行为也较稳定。updateAt字段由Prisma自动维护:任何一次update操作都会自动刷新这个字段,完全不需要在业务代码里手动赋值。关联关系显式声明:User和Post之间的外键关系既在Post侧声明了author和多对一关联,也在User侧声明了posts列表作为反向引用。这一对关系在生成数据库时会变成外键约束,在生成类型时会变成对象嵌套。
如果你对团队协作有要求,我特别推荐在Prisma schema里为每个模型写上清晰的三斜线注释,说明这张表的业务含义和关键字段规则。Prisma的格式化工具会保留这些注释,它们会成为团队内部"活的文档"。
5.2 迁移流程和开发期策略
开发过程中,你经常会调整schema——加一个字段、改一个约束、新增一张表。Prisma的迁移机制(Migration)处理得非常顺手。基本流程是:
- 修改
schema.prisma。 - 运行
npx prisma migrate dev --name describe_change。 - Prisma会生成一个迁移SQL文件,并自动执行到数据库。
- 同时重新生成Prisma Client类型。
我在操作中发现一个非常实用的流程优化:先把数据库里已有的生产数据备份好,然后在本地用prisma migrate reset把数据库清零,再重新执行全部迁移。这个操作会把所有表和数据进行重建,特别适合开发初期数据结构还不稳定的时候。如果你已经上线了,那就要谨慎一些,migrate reset会摧毁掉所有数据,建议只在没有重要数据的开发环境里使用。
对于已经有线上数据的版本升级场景,你需要在正式迁移之前仔细检查生成的SQL文件和预期是否一致。比如删除字段、修改字段类型这类操作,不同数据库的具体SQL语法会有差异,Prisma生成的SQL里也许有不符合你预期的地方。我会在prisma/migrations/<版本目录>/migration.sql文件里人工检查并调整后再执行,这是团队项目里非常关键的一步。
5.3 和tRPC + TypeScript的类型联动
Prisma和tRPC之所以能形成良好的开发体验,核心在于TypeScript的结构化类型系统把数据模型、查询结果、API返回值串联成了一条线。
比如你定义了一个createPost的mutation:
createPost: protectedProcedure .input(z.object({ title: z.string().min(1), content: z.string().optional(), })) .mutation(async ({ ctx, input }) => { return ctx.prisma.post.create({ data: { title: input.title, content: input.content, author: { connect: { id: ctx.session.user.id }, }, }, }); }),前端调用api.post.createPost.useMutation()之后,mutate方法接受的参数、返回的响应类型都是准确推导出来的。哪怕你只是改了一个字段名,比如把title改成heading,只要重新生成了Prisma Client,所有涉及这个字段的代码都会在编译时收到报错。整个过程不需要手写任何一份接口文档。
这里有个小技巧值得分享:在实际项目中,我习惯把常用的数据查询结果类型抽出来复用。Prisma生成的数据模型类型非常具体,比如Post是包含author等嵌套关联的返回类型。与其在前端自己定义一个新的interface去"二次包装",不如直接用Prisma.PostGetPayload这类工具类型选择字段子集。这样既保持了类型和数据库的同步,也能避免把多余的返回字段暴露给前端。
6. 常见问题与排查技巧实录
6.1 类型报错:Context里的session可能为null
这是我在t3code里最早遇到的一个问题。写好一个protectedProcedure后,前端调用时一切正常,但服务端类型检查一直报错,说ctx.session.user可能为null。
排查思路是这样的:NeXT-Auth的session类型返回的字段都是可空的,因为在tRPC上下文创建时,用户可能未登录,session就是null。而在protectedProcedure中间件里,你做了登录判断并抛出了未授权错误,按理说走到业务逻辑时session就不该是null了,但TypeScript编译器不认这个逻辑。
解决办法是做一个类型守卫,在中间件里把上下文"收窄":
const protectedProcedure = t.procedure.use(({ ctx, next }) => { if (!ctx.session?.user) { throw new TRPCError({ code: "UNAUTHORIZED", message: "请先登录", }); } return next({ ctx: { session: { ...ctx.session, user: ctx.session.user }, }, }); });其实更通用的方式是直接在createTRPCContext里不做任何保证,在中间件里用类型断言把session.user标记为非空。如果你经常需要在这类场景中调试,我建议在src/server/api/trpc.ts里加上一段type GetSessionUser = NonNullable<Session["user"]>这类辅助类型,让类型逻辑更容易复用。
6.2 Prisma查询时出现连接池超时
有一次在本地环境跑联调,前端页面偶尔会报出类似PrismaClientInitializationError或连接超时的错误。我的第一反应是数据库连接串配置或网络问题,但检查半天发现都不是。
后来才定位到真实原因:Next.js在开发模式下,每个被访问的模块都会做热更新重载,而Prisma Client的实例如果被重复创建,可能会产生大量并发连接,把本地PostgreSQL的连接池打满。解决方案是彻底启用Prisma的全局单例模式——把PrismaClient实例缓存到globalThis上,避免每次热更新都去新建连接。t3code模板自带的src/server/db.ts就有这段代码,但如果你在自己改过的项目里忽略了这一点,出问题的概率极高。
// src/server/db.ts import { PrismaClient } from "@prisma/client"; const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient }; export const prisma = globalForPrisma.prisma ?? new PrismaClient(); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;6.3 NextAuth登录回调跳转异常
t3code模板默认集成了NextAuth作为鉴权方案。很多初学者在这块遇到问题时,往往第一反应是去改NextAuth的配置,但我建议先检查一下环境变量的命名和加载方式。t3code里环境变量通过src/env.mjs校验,如果你添加了一个新环境变量但忘记在env.mjs里声明schema,即使写在.env文件里也不会被正确加载,调用时会直接得到undefined。
检查方法很简单:在项目根目录跑node -e "require('dotenv').config(); console.log(process.env.NEXTAUTH_SECRET)",看能不能打印出预期的值。如果输出是undefined,那就是环境变量装载环节出了问题,优先排查env.mjs的配置。
还有一个很隐蔽的坑:NextAuth的AUTHORIZE回调函数如果放在服务端和客户端共享的模块里,有时候会因为冷启动时序问题拿不到上下文。这种情况,我会直接在回调里从Redis读取session数据,或者改用getServerSession在RSC组件里获取用户信息,避免依赖按需创建的session上下文。
6.4 部署后前端白屏,但本地一切正常
有一次我把t3code模板部署到服务器后,访问首页出现白屏,但本地开发环境完全正常。控制台检查以后发现,浏览器报了一个运行时错误,指向某个API请求返回了非预期格式。这个问题的本质是环境变量在不同环境下指向的服务不同,而API响应数据因为数据库未迁移到生产库,整体结构对不上。
解决方案很朴素:部署前在生产环境上先跑一遍npx prisma migrate deploy,把迁移文件同步到生产数据库,再跑pnpm build和pnpm start。另外,强烈建议在部署之前pnpm build前先执行npx prisma generate,确保本地生成的Prisma Client和线上数据库结构一致。很多所谓的"部署后白屏",根因其实就是迁移没跑通,数据库里缺表或者字段不匹配。
6.5 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
终端报PrismaClientInitializationError | 环境变量缺失或数据库未启动 | 检查DATABASE_URL和.env配置,确认数据库服务正常 |
类型报session.user可能为null | 中间件未收窄类型 | 在protectedProcedure里做session非空断言或类型守卫 |
| 前端请求返回401 | 用户未登录或token过期 | 检查NextAuth的session配置,确认登录态有效 |
| 部署后白屏 | 生产数据库未迁移或前端构建时类型错误 | 执行prisma migrate deploy,重新构建应用 |
| 页面样式错乱 | Tailwind配置未正确包含页面路径 | 检查tailwind.config.ts中的content配置是否正确加载源文件路径 |
tRPC请求报TRPCClientError | 输入校验失败或procedure抛出错误 | 查看后端错误日志,定位具体的validation issue |
7. 经验总结与扩展建议
7.1 我最喜欢这个模板的地方
用t3code跑了几个真实项目之后,我最深的体会是:它把"类型安全"从一句口号变成了一条贯穿全链路的主线。你写的每一个数据模型、每一个API函数、每一个前端调用,都在TypeScript的监督之下协同工作。这种感受很难用一个具体的案例来说清楚,但一旦你习惯这种开发方式,再回到那种"前后端类型各自维护"的旧模式,你会觉得特别没安全感——好像每一处接口变动都可能埋一个运行时的雷。
还有一个我很看重的地方是它对新手友好度与专业深度的平衡。t3code没有过度封装,目录里的每个文件你都能看懂它在干什么,官方库的文档也能直接对照着查。这种东西叫"可学习的基础设施",而不是"黑盒工具"。对团队来说,这意味新人上手速度快;对个人来说,这意味着你永远对项目拥有完整的掌控力。
7.2 后续扩展方向参考
t3code本身是一个很好的起点,基于它你还可以做很多扩展。我举几个实际方向:
- 接入文件上传能力:结合S3或本地存储,在tRPC路由里扩展现有procedure,增加file upload handler。
- 增加缓存层:在tRPC查询结果或者Prisma查询前加一层Redis缓存,降低数据库压力。
- 引入消息队列:对于慢任务(发邮件、生成报告、处理上传文件),可以引入BullMQ或Inngest,让耗时操作异步化。
- 加入单元测试链路:用Vitest测试routers,用Playwright做端到端测试。因为tRPC的类型系统完善,测试时的mock成本其实很低。
- 替换数据库:如果未来业务发展到需要专用数据库,Prisma支持多数据库适配器,你可以相对平滑地迁移到MySQL或SQLite,只要schema语法保持一致。
7.3 最后再分享一个实战小技巧
在实际开发中,我建议你养成一个习惯——每次改动Prisma schema之后,跑一下npx prisma generate,然后顺手git diff看一眼生成的类型和之前有什么区别。这一步能让你第一时间捕获到"字段被删了""类型变了"导致的连锁反应。很多时候,前端编译报错并不直观,但类型变化本身就是一种极其明确的变更日志。
另外,tRPC的错误处理也值得多花一点心思。模板默认情况下,服务端procedure抛出的错误会被统一序列化,返回给前端的是一个错误码和message。但如果你在开发环境需要完整的堆栈信息,建议把isServer端的错误序列化策略配置成"总是返回详细信息",而在生产环境关闭。这样开发期的问题排查速度会快很多,线上又不会泄露内部实现细节。
我实操下来的总体感受是,t3code这类T3 Stack模板最大的价值并不是"省去了搭建时间"这么简单,而是它逼着你以类型安全的方式思考和组织业务代码。花一个周末把它吃透,后面的每一个全栈项目都会受益。