1. 项目概述:t3code 到底是什么
先说结论:t3code 是我搭建的一个基于 T3 Stack 的全栈 TypeScript 项目脚手架,定位是解决“从零启动一个完整的现代 Web 应用时要反复做大量重复工作”这个痛点。用更直白的话说,它就是一套开箱即用的起点代码库——用户认证、数据库、API 层、前端页面、部署配置全都给你摆好了,你只需要在这个基础上填充自己的业务逻辑就行。
我想做这个项目的直接原因是:过去一年多我接了十几个外包项目和自己的 side project,每次都从create-next-app开始,然后手动接 Prisma、配 NextAuth、调 Tailwind、写 tRPC 路由……一顿操作下来,光环境搭建和基建代码就耗费了至少两天。而且各家项目的代码风格还不一样,换一个项目就要重新适应一遍。我就在想,为什么不把这些年积累下来的最佳实践固化成一个模板?于是 T3 Code 就诞生了。
T3 Stack 这个名字里的“T3”指的是TypeScript、Tailwind CSS、tRPC这三大件,现在官方已经把 Next.js 也纳入进来,所以具体是Next.js + TypeScript + Tailwind CSS + tRPC + Prisma + NextAuth的六位一体组合。这不是我拍脑袋选的,而是经过了大量项目验证的有效组合:Next.js 负责页面和 API 路由,TypeScript 提供类型安全,tRPC 让前后端 API 调用像调用本地函数一样舒服,Prisma 管理数据库,Tailwind 写样式几乎不用切出 JSX 文件,NextAuth 承担最棘手的认证逻辑。六个工具各司其职,覆盖了一个 Web 应用从数据存取到用户界面的大部分核心需求。
那 t3code 适合谁用?我觉得主要是这三类人:第一类是独立开发者,想快速把一个想法变成能上线验证的 MVP,不想在基建上耗时间;第二类是全栈工程师,需要在多个项目间高效切换,希望有一套统一的技术基线和代码风格;第三类是刚接触 T3 Stack 的初学者,与其面对一堆官方文档不知从何下手,不如直接看一个完整项目是怎么把所有这些串起来的。
我自己用 t3code 启动新项目的实测结果:从拉代码到跑起来,10 分钟以内搞定,之后的开发过程里几乎不再碰基建配置。这就是我做这个项目的初衷,也直接决定了它的设计方向和取舍标准。
2. 整体设计与技术选型拆解
2.1 为什么是 Next.js 而不是 Vite 或 Remix
技术选型这件事,很多人容易走向两个极端:要么只看流行度,跟着热度无脑选;要么只盯着某个新框架的炫酷特性。我的态度是,选型要服务于项目的实际生命周期——你要做什么类型的应用,你的业务要活多久,你的团队偏好什么样的开发体验,这些才是关键变量。
先说说 Next.js。它在 T3 Stack 里承担的是框架层,提供页面渲染、路由、API 能力。选择它最重要的原因之一就是文件即路由的心智模型,一个文件对应一个路由,删除文件就删除路由,没有额外的配置文件。我接触过很多从 Vue 转过来的朋友,他们第一次用 Next.js App Router 时最大的感叹就是:找到某个页面对应的代码,不需要在一个router.js文件里翻来翻去,目录结构本身就告诉你了。
那为什么不选 Vite + React Router?不是说 Vite 不好,它在构建速度和 HMR 快感上的确很爽,但它本质上是一个纯粹的构建工具,路由、SSR、部署都需要再选方案搭配。作为脚手架,我倾向于把常见的需求都内置解决,而不是留一堆选择题给使用者。Next.js 在这方面的优势就是全家桶式的完整度,尤其是 App Router 针对服务端组件做了大量优化,虽然有个学习曲线,但好消息是 T3 Stack 默认的项目结构已经帮你把最佳实践放好了,你不用自己摸索。
至于 Remix,它和 Next.js 的竞争关系已经持续很久了。Remix 的一个核心卖点是对嵌套路由和加载函数的强约束,尤其适合内容型应用和仪表盘类应用。但从我个人的实战对比来看,Remix 的中间件生态、Server 组件支持以及对外部服务的适配还没有 Next.js 成熟;更重要的是,Next.js 背后有 Vercel 这家公司在商业化支持,文档质量、活跃度和范例数量都在持续攀升,真遇到了一个冷门 bug,你更容易通过搜索在几个小时内找到答案,这对中小团队来说极其重要。
2.2 tRPC 为什么比 REST 更适合全栈项目
关于 API 层的选择,我要说一个可能有些争议的观点:在纯全栈项目里,tRPC 比 REST 香得多。我刚接触 tRPC 时也是带着怀疑的,觉得“不做 OpenAPI 生成、不按 HTTP 语义来设计接口,这不会是玩具吧”。直到我在一个项目里把它和传统 REST 混合了一小段时间,才彻底被打脸。
REST 的核心是资源导向,GET /user/1、PUT /user/1,这个模型适用于对外公开 API、多端复用、长生命周期系统。但如果是自己的前端调自己的后端,需求其实很简单——调一个函数拿数据,出错有 TypeScript 报错,最好什么文档都不用写。tRPC 恰好满足了这个需求:它让你在服务端定义一个 router,里面的每一个 query 或 mutation 就是一个服务端函数,然后在客户端像调用本地函数一样直接调用。类型定义自动推导,前端拿到什么数据、传什么参数,编辑器全部帮你检查了。
我用 tRPC 最舒服的点是联调阶段几乎为零。以前用 REST,前端写好请求代码之后,要去 Postman 里测接口、看返回结构、再回来修正类型定义,来回摩擦至少要一两个小时。现在用 tRPC,服务端写完业务逻辑,前端的类型就自动生效,IDE 里直接提示参数和返回值。如果某个字段漏传或者传错类型,编译期就发现了,根本轮不到上线后炸接口。
当然这也有代价:tRPC 对非 TypeScript 客户端不友好,如果你以后要做小程序、React Native、第三方开放 API,这些场景还是得另起一套 REST 或 GraphQL。我的处理方式是:外部接口用 REST,内部全栈调用走 tRPC,两套机制在项目里可以共存,互不干扰。这也是 t3code 的默认设计思路。
2.3 数据库与 ORM 的搭配:Prisma 的正反两面
数据库这块,现代全栈项目里最流行的组合就是 Prisma + PostgreSQL。Prisma 最让我喜欢的能力是它的 schema 到数据库的一键映射:你在schema.prisma里定义一个模型,跑一次prisma migrate,表结构和大字段索引就自动建好了,包括外键关系、枚举类型、默认值这些都能映射得很干净。写前端的人不用在 SQL 里挣扎,就能把数据模型建起来,这套体验对快速迭代项目帮助很大。
但我也要泼一句冷水:Prisma 不是万能的。很多人把 Prisma 当成能够替代 SQL 的工具,这个认知偏差很危险。在一些复杂查询场景下,比如递归的树形结构查询、复杂的行级权限控制、依赖数据库函数实现的聚合逻辑,Prisma 生成的查询要么不够高效,要么写起来比 SQL 还绕。我的实测经验是,这种场景你在 Prisma 里写queryRaw和executeRaw反而更香。Prisma 原生支持 raw query,模板字符串写法也很顺手,性能开销可以忽略不计。
所以 t3code 中的数据库设计思路是:常规 CRUD 用 Prisma 的类型安全 API,复杂报表和特殊查询直接写 SQL。项目里我放了一些典型的复杂查询示例,比如按月份聚合用户增长量、带分页和排序的动态过滤,这些代码可以作为后续项目的模板用。
说到数据库选型,默认是 PostgreSQL,而不是 MySQL 或 SQLite。PostgreSQL 在 T3 Stack 社区中是最主流的选择,它在 JSON 字段支持、扩展生态、全文检索、地理位置支持这些方面都比 MySQL 更灵活;更重要的是,和下一步要讲的用户认证体系配合,PostgreSQL 能提供更稳的并发事务处理。SQLite 不是不能跑,但只在开发环境和轻量演示用,正式上线直接上 PostgreSQL。
2.4 认证体系要去边缘化:NextAuth 的工程化整合
用户认证是每个 Web 应用的刚需,但如果自己从头写登录注册、Session 管理、密码重置,太耗时间和精力了。很多脚手架项目会把认证方案做得“看起来很丰富”——提供一堆第三方登录选项。我的观点是:登录方式尽量不要超过三种,密码登录加一两个最常用的第三方登录就够了,不然调试成本和维护成本翻倍,而实际用户转化率并不会因为多一个 GitHub 登录按钮而提高。
t3code 里集成了 NextAuth,并做了以下几件关键的定制:
- Credentials 登录:支持邮箱密码注册、登录。密码使用 bcrypt 哈希存储,加盐参数设成 12,不要用默认的 10,在暴力破解场景下能多抗一阵。
- Google OAuth 和 GitHub OAuth:只保留这两个最常用的第三方登录,需要更多可以自行扩展。
- Prisma 适配器:用户、账号、Session、验证令牌相关的表结构已经定义好,首次
prisma migrate时自动创建。 - Session 走数据库而不是 JWT:很多人图省事直接用 JWT 存 Session,但 JWT 很难做到服务端主动吊销,用户被删了或改密码了旧 token 仍然有效。t3code 默认把 Session 存在数据库里,安全强度高一截,代价是多一次磁盘查询,对大多数应用场景来说完全可以接受。
这里有一个我强烈建议的细节:开发环境里把 Secure 相关的 Cookie 选项关掉,否则本地开发用 HTTP 会一直登录不上去,NEXTAUTH_URL 在.env里精确配置。很多新手在这个问题上翻车,其实不是 NextAuth 的问题,就是 Cookie 的 Secure 属性要求在 HTTPS 环境才会发送。
2.5 UI 层与样式方案:Tailwind CSS 的使用哲学
最后说说前端样式。Tailwind CSS 在这个组合里扮演的是原子化样式的角色,它把 CSS 压缩成工具类,在写页面时几乎不用离开 HTML 文件,不需要命名一个 class 再去 CSS 文件里找到它、修改它再回来刷新看效果。这个过程对开发效率的提升,是实打实能感觉到的。
但 Tailwind 用久了会有一个问题:页面里全是长长的className字符串,十几二十个类名堆在一起,视觉噪音很大,而且如果重复的样式块散落在多个组件里,改起来很痛苦。t3code 的答案是提供一个轻量的组件层,把常用的 UI 元素封装成小组件,比如按钮、输入框、卡片、导航栏、弹窗等,这样组件内部的 Tailwind 类名只在定义处出现一次。
具体我选用了shadcn/ui作为基础组件库。它不是一个传统意义上的第三方组件库,而是一个“可以从 UI 复制代码到你的项目”的集合,它会直接生成按需定制的 Tailwind 组件代码,你拥有完全的所有权,可以随便改。这对脚手架项目来说简直不能再合适了:既有 UI 的完成度,又没有依赖管理的负担。
另外我还预置了明暗主题切换的基座,使用 CSS 变量 +class策略,切换主题时直接在html元素上增删 class。设计上,CSS 变量存储颜色和基础尺寸,Tailwind 配置里映射到对应的设计 token,这样改主题只需要改几个变量的值,不用把所有涉及颜色的类名找一遍。说实话,光这一套主题机制,就帮我省了非常多踩坑时间。
3. 核心细节解析与实操要点
3.1 项目目录结构:每个文件夹是干什么的
大部分脚手架项目最让人头疼的就是,一打开目录,几十个文件夹摆在那里,你根本不知道应该把业务代码放在哪里。所以 t3code 的目录设计原则是让你一看目录结构就知道该把代码放哪。下面是我目前实际使用的目录结构:
├── prisma/ │ ├── schema.prisma # 数据模型定义 │ └── migrations/ # 数据库迁移文件(自动生成) ├── public/ # 静态资源(favicon、图片等) ├── src/ │ ├── app/ # Next.js App Router 页面 │ │ ├── (auth)/ # 认证相关页面(登录/注册) │ │ ├── (dashboard)/ # 核心业务页面 │ │ ├── layout.tsx # 根布局 │ │ ├── providers.tsx # 全局 Provider(Session 等) │ │ └── api/trpc/ # tRPC 的 HTTP 入口 │ ├── components/ # UI 组件层 │ │ ├── ui/ # shadcn/ui 生成的原子组件 │ │ ├── layout/ # 导航栏、侧边栏、页脚 │ │ └── shared/ # 跨业务复用的业务组件 │ ├── server/ │ │ ├── db.ts # Prisma 客户端实例 │ │ ├── auth.ts # NextAuth 配置 │ │ └── api/ # tRPC 路由相关 │ │ ├── routers/ # 业务模块路由(postRouter 等) │ │ └── trpc.ts # tRPC 初始化 │ ├── lib/ # 工具函数与常量(不含业务逻辑) │ └── styles/ # 全局样式表(Tailwind 入口) ├── .env # 环境变量(敏感信息) ├── .env.example # 环境变量模板 └── package.json这里有一个组织逻辑值得展开:lib里放纯工具函数,永远不要从lib里 import 到数据库或服务器状态,否则容易造成客户端与服务端代码逻辑互相纠缠。components/ui里只放通用组件,任何包含业务逻辑的组件都应该放在components/shared或各业务模块自己的文件夹下。这样做的好处是,当你需要把某个功能拆成独立服务或替换掉第三方组件库时,能清楚地知道哪些文件是核心,哪些是可以动的。
3.2 从零到一:环境准备与启动流程
想真正用起来 t3code,第一步是把它跑起来。我尽量把过程写清楚,即使是第一次用 T3 Stack 的新手,也能照着一步步走通。
- 首先确保机器上有Node.js 18+和包管理器,我建议启用 pnpm。pnpm 相对于 npm 的优势在于磁盘空间占用小、安装速度快、对依赖的隔离更严格,避免“幽灵依赖”问题。
- 克隆项目后,第一件事是把
.env.example复制为.env,然后填写数据库连接字符串和 NextAuth 相关密钥。数据库连接串的格式是postgresql://用户名:密码@主机:端口/数据库名,你可以先用本地的 PostgreSQL,也可以用 Docker 起一个开发用数据库。
# 拉取代码 git clone https://github.com/你的用户名/t3code.git cd t3code # 安装依赖 pnpm install # 复制环境变量模板 cp .env.example .env # 数据库迁移 pnpm prisma migrate dev --name init # 启动开发服务器 pnpm dev这里要特别提醒:pnpm prisma migrate dev这一步是绝对不能跳过的,Prisma 客户端类型的生成依赖数据库 schema 的同步。如果你跳过了,接下来会在项目里到处看到类似 “Property 'user' does not exist on type 'PrismaClient'” 这样的报错,其实不是你代码写错了,而是 Prisma 客户端没有生成最新的类型定义。跑一次prisma generate也行,但我不建议手动跑,直接让迁移流程带着类型生成最省事。
启动后浏览器打开http://localhost:3000,你能看到一个简单的欢迎页和一个小型仪表盘,这说明脚手架安装成功。页面上的这个初始仪表盘不是摆设,它是我故意放的一个最小业务示例:从数据库读取一条记录并展示在页面上,把数据层完整地跑通了一遍。你可以在上面试着注册一个账号(邮箱+密码即可),然后进入后台页面,测试 Session 是否正常。
3.3 理解关键配置:从现在起你可以改什么
脚手架装好之后,很多人问的第一个问题就是:“我该从哪开始改?” 其实 t3code 的所有配置都尽量集中、可预测,你不需要也不应该上来就到处乱改。下面是几个最核心的文件,理解了它们,你对这个脚手架的掌控力就大幅提升了。
src/server/db.ts:这里创建并导出 PrismaClient 的全局单例。在开发模式下 Next.js 热重载会导致模块被重复执行,如果不做全局缓存,会造成数据库连接池溢出,这是导致 “too many connections” 的常见根因。默认代码里已经用globalThis做了缓存,这是经过社区验证的标准写法,你自己不要瞎改。src/server/auth.ts:NextAuth 的完整配置都在这。你可以在这里修改 Session 策略、JWT 回调、事件处理器等。比如说你想在登录成功后把用户信息塞进 Session,就在自定义 callback 里处理。我给这个文件写了详细的注释,把常见字段的用途都说明白了。src/server/api/trpc.ts:tRPC 的初始化入口。你可能会看到createTRPCContext这个函数,它的作用是在每次 tRPC 调用时构造上下文,里面包含当前的 Session 用户信息。如果你希望通过中间件对某些接口做权限校验,就是在 router 层调用protectedProcedure来实现,它的定义就在这个文件里。tailwind.config.ts:默认我已经设置了品牌色、语义色(success、danger、warning 等)、圆角、断点等。如果你要换主题色,直接改这里的色板变量就行,不建议在页面里手动写死bg-blue-500之类的绝对颜色,那样后面换主题会痛不欲生。
3.4 权限设计与中间件实现:你知道这一层有多重要
先说一个很多人忽略的事实:前端页面按钮的“隐藏”不等于后端接口的“保护”。我以前接过一个项目,前任开发者只是在页面级别做了判断——如果你不是管理员就不显示“删除”按钮,但后端的对应接口完全没有鉴权校验,结果随便用 curl 发一个请求就能完成删除操作。所以 t3code 在权限设计上,前后端是两层独立的防线。
前端层面,我在布局组件里用SessionProvider包裹了全局上下文,代码里可以用useSession()拿到当前登录用户。针对未登录用户,页面跳转到/login;针对不同的业务角色,页面渲染不同的操作按钮。这层是纯体验层的,让正常用户有一个清晰的权限反馈,它不负责真正的安全。
后端层面,tRPC 提供了 procedure 的中间件机制。t3code 里默认定义了两种 procedure:publicProcedure可以任意调用,protectedProcedure要求请求头里附带有效的 Session 后才能调用。对于管理员专属接口,再加一层adminProcedure中间件,里面检查当前用户角色是否是 ADMIN,如果不是直接抛UNAUTHORIZED错误。在实际 router 定义中,你看到的代码是这样的:
export const postRouter = router({ create: adminProcedure .input(createPostSchema) .mutation(async ({ ctx, input }) => { // 这里可以放心处理业务逻辑,能走到这的必然是管理员 return ctx.db.post.create({ data: input }); }), getRecent: publicProcedure .query(async ({ ctx }) => { // 不要求登录,任何人都可以看 return ctx.db.post.findMany({ take: 10 }); }), });这样设计的好处是:权限校验和业务逻辑彻底分离。写业务代码的人不需要在函数内部重复检查“当前用户到底能不能干这件事”,中间件帮你统一拦截了。如果你追求更细粒度的数据权限(比如只能看自己创建的订单),建议在业务层里再写判断,不要全堆进中间件,否则中间件会越来越臃肿,难以维护。
4. 实操过程与核心环节实现
4.1 五个核心模块搭建实录:从 0 到 1 的手把手过程
t3code 不是只做架子,我还在里面塞了几个高频场景的完整实现。这些模块不是装饰品,而是能直接给你复制使用的代码。我把它们想成是“可以开瓶的啤酒样品”,你尝过了觉得好喝,再去买整箱,前提是味道必须靠谱。下面挑几个典型模块说说搭建细节。
第一个是用户注册/登录模块。前端页面在(auth)/login/page.tsx和(auth)/register/page.tsx,表单用了react-hook-form加 zod 做校验。zod 在这里的用法值得展开讲讲:前端在提交前用 zod 校验字段格式(比如邮箱格式、密码长度不低于 8 位),但不要只在前端校验。tRPC 的input接收器在服务端也会执行相同的 zod schema,这样即使前端被绕过,服务端依然能拦截非法数据。双端同一套 schema,这是 T3 Stack 中最帅的一个特性。
第二个是文本内容的 CRUD。我建了一个Post模型,然后写了一个postRouter,提供create、update、delete、getAll、getById这几个常规操作。代码大约 90 行,完整覆盖了一个资源的核心操作。这个模块的最大价值在于展示如何把数据库操作和权限中间件无缝组合,新手照着这个写自己的商品、订单、文章的 CRUD,基本不会走偏。
第三个是文件上传。我一开始也想直接集成 S3 或 Cloudinary,但考虑很多人本地开发时没有云服务凭证,就用了本地磁盘存储的方式。具体实现是:前端通过一个普通表单上传文件到 Next.js 的 API Route(/api/upload),后端用writeFile把文件写入public/uploads/目录,然后返回访问路径。这个方法有两个边界情况需要你注意:第一,public目录里的文件会被当作静态资源直接公开访问,如果上传的是私密文件,这个方案就不适用;第二,持久化存储方面,直接写磁盘的方案在 Vercel 这类 Serverless 平台上是不可用的,因为文件系统不是持久化的。要上生产环境的话,建议这套逻辑替换成 S3 或 Cloudflare R2,API 的对接方式不变,只是存储层换掉。
第四个是后台管理基础。后台地址是/dashboard,里面几个默认页面:首页(放入统计卡片)、表格列表页(用 shadcn/ui 的 Table 组件做分页和搜索)、设置页(关于个人资料的常见设置项)。这些页面在视觉上不算惊艳,但作为后台系统的起步样张,它们的代码结构非常讲究,你只需要复制粘贴并替换成自己的数据和语义即可。
第五个是邮件发送。邮件这块我用的是nodemailer加 SMTP 配置。很多项目不需要真实的邮件服务,但比如注册欢迎邮件或密码重置邮件,如果没有一个地方能跑通,后续加了功能一测就卡住。t3code 里我封装了一个sendMail工具函数,.env里配置了通用的 SMTP 参数。想换成 SendGrid 或 Resend,只需要改这一个文件里的实现,其他地方不用动。
4.2 数据库模型定义与迁移的真实操作
数据模型是 t3code 的心脏。我特意演示一个相对完整的数据模型——用户、文章和文章分类的关系。这段 schema 覆盖了常见的关系映射、枚举类型和索引配置,直接拿来学习效率很高。
// prisma/schema.prisma 局部示例 model User { id String @id @default(cuid()) name String? email String @unique emailVerified DateTime? hashedPassword String? image String? role Role @default(USER) posts Post[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Post { id String @id @default(cuid()) title String content String published Boolean @default(false) authorId String author User @relation(fields: [authorId], references: [id], onDelete: Cascade) category Category? @relation(fields: [categoryId], references: [id]) categoryId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([authorId, published]) } model Category { id String @id @default(cuid()) name String @unique posts Post[] } enum Role { USER ADMIN }这个 schema 的几个关键点:id用 cuid 而不用自增数字,是因为分布式和合并迁移场景下字符串 ID 更安全;onDelete: Cascade表示删除用户时自动删除他的文章,避免孤儿数据;@@index表示建立组合索引,查询条件频繁用到authorId和published时性能会更好。跑迁移的命令是:
pnpm prisma migrate dev --name add_post_model迁移会自动生成对应的 SQL 并同步到数据库,还会重新生成 Prisma Client 类型。如果你改了 schema 但一时半会不想立刻迁移,也可以用pnpm prisma generate先更新类型,让编辑器里的代码不报错。但请注意,数据库没有同步执行迁移之前,运行时会报表不存在的错误,所以开发流程里该迁移就不要憋着。
4.3 创建页面到数据流的完整链路演示
很多人看脚手架项目最困惑的就是:“页面是怎么把数据拿到手的?”这背后其实是 tRPC 的完整调用链路。就拿 t3code 里的信息展示页为例,前端代码会这样调用:
"use client"; import { api } from "~/trpc/react"; export function RecentPosts() { const [posts] = api.post.getRecent.useSuspenseQuery(); return ( <ul> {posts.map((post) => ( <li key={post.id}> <h3>{post.title}</h3> <p>{post.content}</p> </li> ))} </ul> ); }有意思的地方在于,这段代码看起来像是在执行一个本地函数——api.post.getRecent.useSuspenseQuery()——但实际上它是从服务器拉取数据的。tRPC 的客户端在src/trpc/react.ts里被初始化,api对象是一个类型安全的代理,它会根据你在服务端定义的postRouter生成对应的调用方法。所以当你服务端改了方法签名,前端的类型也会跟着更新;如果服务端返回数据的结构变了,前端代码在编译期就会报错,而不是运行时才炸。
在服务端,tRPC 的事件处理流程是:请求先到达 Next.js 的 API Route(/api/trpc),触发createTRPCContext函数;它从req中解析 Session(依赖 Cookie),拿到ctx.user和ctx.db;再根据 URL 的路径匹配到具体的 router 方法,执行对应代码。整个链路清晰,对于调试也友好:只要看浏览器 Network 里发了什么请求,就能对应到服务端哪段代码在处理。
4.4 部署经验:从本地到线上环境的关键步骤
部署是很多项目“最后一公里”最容易翻车的地方。t3code 的默认部署目标是 Vercel,因为 Next.js 是 Vercel 亲儿子,部署体验最丝滑。但不管部署到哪里,有几个步骤是必须认真对待的。
第一,环境变量必须齐全。在 Vercel 或你的服务器上,把.env里的所有变量都填一遍,尤其是DATABASE_URL和NEXTAUTH_SECRET。我建议在本地生成一个随机密钥并妥善保存,永远不要用可预测的值。
第二,数据库与迁移。线上数据库建议用托管服务商,比如 Railway、Neon、Supabase 等。迁移操作要在部署前执行,本地跑pnpm prisma migrate deploy,这个命令不会生成新迁移,只是将本地已有的迁移文件应用到线上数据库。如果你用的 CI/CD 流程,就把这个步骤作为发布流程的一个环节,确保代码更新时数据库结构已经同步完成。
第三,构建与静态导出。Next.js 的构建命令默认是next build,如果项目里没有任何动态服务端逻辑,也可以尝试纯静态导出。但 t3code 里因为使用了 tRPC 和 NextAuth,我强烈不推荐静态导出,否则认证和 API 调用全部失效。需要output: 'standalone'的自托管用户,我已在配置文件中预留好了,你可以根据主机环境调整。
部署完成后,第一件事打开线上页面跑一遍完整的注册、登录、创建数据的流程,把日志和监控挂了再对外发布。很多小团队跳过这个验收步骤,结果上线第一天一堆环境相关的低级错误,用户体验很差,这个坑希望你不要踩。
5. 常见问题与排查技巧实录
5.1 数据库与 Prisma 报错:跑不通怎么快速定位
在脚手架使用过程中,数据库相关报错是出现频率最高的。我给几个典型场景和排查思路,基本都是我在实际开发中直接碰到的。
场景一:PrismaClientInitializationError,连接字符串不对。这个报错通常会给出很清晰的提示,你直接去.env里核对DATABASE_URL的格式。常见的坑是密码里含有特殊符号但没转义,比如@符号在连接串里会被误认为是主机和端口的分隔符。解决办法是用 URL 编码来表示特殊字符,或者在连接串后面加上?sslmode=require来满足云端数据库的加密要求。
场景二:The table does not exist in the current database。这个基本就是忘了跑迁移。先跑pnpm prisma migrate dev,检查prisma/migrations目录下的文件是否被正确应用。
场景三:迁移冲突或 schema drift。这是多人协作时的常见问题——两个人同时改了schema.prisma并各自生成了迁移文件。解法是先备份数据库,然后pnpm prisma migrate reset把数据库重置到迁移记录的状态,再重新生成一份完整的迁移。这个命令会清空数据库数据,生产环境不要乱跑;开发环境重置是家常便饭,不用担心。
5.2 认证与 Session 的常见坑:为什么登录总是失败
认证问题有个特点:报错信息常常不直接说明根因,而是绕来绕去,比如“回调地址无效”或“Session 为空”。下面的速查表基本能覆盖大部分情况。
| 症状 | 原因排查 | 解决方案 |
|---|---|---|
| 登录后立即跳回登录页 | 数据库 Session 表未被正确创建 | 跑prisma migrate dev确认 Session 模型已映射 |
| NextAuth 报 redirect_uri 错误 | 项目地址和 NEXTAUTH_URL 不一致 | 检查本地访问地址和NEXTAUTH_URL完全一致 |
| 生产环境点击登录无反应 | Cookie 被浏览器拦截 | 确认域名的 HTTPS 已生效,否则不能用 Secure Cookie |
| 有的第三方登录可用,有的报错 | Provider 相关环境变量没配全 | 到对应平台控制台核对回调 URL 和密钥是否匹配 |
| 部署后 Session 频繁失效 | 未配置 NEXTAUTH_SECRET | 设置一个固定且随机的 SECRET,重启服务 |
另一种非常隐蔽的情况是:你改了.env里的认证相关配置,但本地开发服务器没有重启。Next.js 会热更新大部分代码,环境变量不会热更新,所以改完.env务必手动重启pnpm dev,否则你会发现改动完全不生效。
5.3 tRPC 常见坑:类型与上下文问题
tRPC 的类型安全是它的卖点,但这也意味着一旦类型报错或者上下文不对,代码直接不让运行。最常见的两类问题如下。
第一类是Context 类型不匹配。你在trpc.ts里定义了createTRPCContext的返回类型,然后在业务 router 里通过ctx.db或ctx.session取数据。如果你改了schema.prisma里某个模型的字段,但数据库迁移还没同步,运行时拿到的是旧数据,类型却期望新结构,开发时会有各种奇怪的报错。我的排查习惯是:先跑一次迁移和 generate,再启动项目,问题大概率消失。
第二类是使用 tRPC 的input时 zod 校验不过。比如前端传的对象里多了一个多余字段,zod 默认会忽略,但如果你开启了.strict(),多余字段会直接导致校验失败。这个报错信息容易让人困惑:“明明我传了啊,为什么不行”。调试技巧是在 tRPC 的onError回调里打印 error 对象,把error.data.code和error.shape.message输出出来,很快能定位到是哪个字段违规。
5.4 性能和依赖管理:让脚手架在长期项目中也扛得住
用脚手架启动的项目,最怕的是运行三个月后处处报错、依赖混乱。我的经验是,开发第一阶段就要把基础纪律立好。这里分享几个低成本的防呆措施。
- 不要把依赖随随便便加进 package.json。安装任何新库之前先问自己:官方 T3 Stack 真的没有内置方案吗?shadcn/ui 真的不能满足需求吗?盲目加 UI 库、状态管理库、请求库,会让项目失去 T3 Stack 轻量快速的核心优势。
- 定期跑一次
prisma migrate dev。数据库结构在开发期经常变,别攒着十天半个月的迁移一次,那样冲突概率极高。我建议每完成一个功能模块就迁移一次,迁移信息写得具体点,以后查看历史也方便。 - 前端页面组件的“服务端/客户端”边界必须分清。App Router 默认所有组件都是服务端组件,如果你在服务端组件里调
useSession()会直接报错。正确做法是把需要客户端功能的组件加"use client"指令,或者在父组件里传children,让子组件自己处理客户端逻辑。 - 写一个
.env.example并保持更新。团队新成员克隆项目后会直接复制这个文件,如果这里缺了变量或和实际代码不对应,浪费的时间都是实打实的。每次加新的环境变量,顺手更新 example 文件,养成肌肉记忆。
5.5 高频报错速查表
我最后把使用 t3code 过程中最常遇到的报错和排查方式整理成一个速查表,方便你对照着找问题。
| 报错信息片段 | 问题根因 | 快速处理方式 |
|---|---|---|
Error: ENOENT: no such file or directory | 缺少公共目录或上传目录 | 创建public/uploads/目录,确认 .gitignore 排除了它 |
Invalidprisma.post.create()invocation | 缺少必填字段或字段名错误 | 对照 schema.prisma 的模型定义检查代码参数 |
NEXT_HTTP_ERROR_FALLBACK页面出现 | 某个 server 组件抛错 | 查看服务端日志,多半是数据格式不一致 |
Warning: Cannot update during an existing state transition | 客户端组件回调里不恰当地调用了 setState | 检查 useEffect 的依赖数组是否写清楚 |
Theuserargument is missing | NextAuth 的 Credentials 回调未返回用户对象 | 检查authorize函数的返回值,必须含用户 ID 等基础字段 |
database is locked | 本地 SQLite 存储冲突 | 切回 PostgreSQL,或减少并发的 SQLite 写入 |
Type error: Property 'x' does not exist on type | Prisma 类型不同步 | 跑prisma generate刷新客户端类型定义 |
这个表我写进项目 README 里了,配套一条使用原则:遇到新报错,先自己拆解三分钟,再查表,再搜索,不要上来就问 AI。排查问题的过程才是让你真正理解这套技术栈的过程,跳过这一步,你永远都是在别人的经验上打转。
6. 项目后续演进与个人经验分享
t3code 不是我做完就扔在那的玩具,它是我日常开发工作的一个重要基础设施。可以说,我现在新起项目的路径已经不是“新建项目再配置”,而是“拉一个 t3code,删掉不需要的部分,开始写业务代码”。这个习惯持续下来,最直观的感受就是新项目的启动时间缩短了不止一个量级,而且每个项目的代码风格、目录结构、错误处理方式都高度统一,协作和交接都轻松很多。
关于这个项目的后续方向,我目前有几件在推进的事,分享出来供你参考。第一个是示例模块继续丰富——比如追加一个典型的中后台资源管理系统(订单+商品)和多租户权限模型,也就是让使用者直接复制一整套前后端代码而不是拆开来拼。第二个是借鉴 T3 社区的最新实践,把 React Server Components 的使用边界整理清楚,在文档里明确哪些组件优先服务端、哪些必须客户端。第三个是写一套面向初学者的引导文档,把“为什么这样做”而不是“怎么做”讲透,因为我在实际带人过程中发现,懂底层逻辑的人改起代码比只会复制粘贴的人强太多。
最后分享一个个人体会:脚手架类项目最大的价值不是“帮你省掉配置时间”,而是把你验证过的工程决策固化成标准。当你下次遇到一个 “要不要用状态管理库”“要不要上 monorepo” 之类的问题时,t3code 已经帮你回答了,你不需要再花一周时间试错。但同等重要的是,它不能变成思想的枷锁。任何一个脚手架都有它的适用边界:如果你的项目开始出现大量为绕开脚手架而写的 hack,或者它的抽象层次已经限制你的设计,那就是时候重新审视这个基础了。我每隔一段时间都会梳理一遍 t3code 里的每个配置项,问自己“这个决策在当前的技术生态下还成立吗”,不成立的果断替换,让它始终是我手里那个最趁手的工具箱。