如果你在搜索引擎里敲下t3code,多半是在找一个能直接抄作业的全栈项目,而不是想看"Next.js 很强大"这种正确的废话。我前阵子正好把一个基于 T3 Stack 的实战项目整理成了这套t3code,包含用户注册登录、文章发布、评论互动、点赞关注、后台管理这些完整业务闭环。考虑到仓库名只有这么几个字,我按自己团队里跑得最稳的一套 T3 Stack 实现来展开讲述,代码细节会尽量给到位。
t3code是一套使用 T3 Stack 构建的全栈应用示例代码。T3 Stack 这个名字可能有人听过,它是目前 TypeScript 全栈生态里很流行的一套组合拳:Next.js 负责页面和渲染,TypeScript 负责类型检查,tRPC 负责前后端接口调用,Tailwind CSS 负责样式,Prisma 负责数据库操作,NextAuth.js 负责登录认证。这套技术选型并不是为了追新,而是为了让开发者在写业务的时候少操一份心。t3code存在的意义就是帮你把这几样工具串成一个能跑、能开会、能上线的产品,同时把过程里容易踩的坑提前标出来。
这个项目尤其适合三类人。第一类是刚学完 React、正准备跨进全栈的新手,跟着代码走一遍能少走很多弯路。第二类是技术负责人,想拿一套内部培训用的参考代码,t3code的目录结构和服务端划分可以作为团队规范的模板。第三类是独立开发者,周末想验证一个产品想法,用这套代码十分钟就能起一个带数据库和登录的 MVP。如果你属于其中任何一类,下面这套拆解应该能给你省下不少时间。
我现在不会按"先讲 Next.js、再讲 Prisma、最后讲部署"这种孤立的方式来说,因为你看完还是会懵。我按真实的项目生长顺序来:先讲整体设计为什么要这样拆,再讲核心细节怎么落地,然后亲手带着你把关键模块实现一遍,最后把常见的坑和排查方法列出来。这样无论你是想直接拿来改,还是想理解原理,都能有收获。
1. 内容整体设计与思路拆解
1.1 把全栈应用的复杂度压到最低
真正动手写过全栈项目的人都知道,最折磨人的不是某个单独的技术难,而是"连起来"的过程。前后端怎么约定接口?字段校验要写几份?后端改了一个字段类型,前端会不会第二天才发现?部署到线上之后数据库连接串怎么管理?这些问题如果只有一个框架,它们是分散的;但当你用一整条链路去解决,体验会完全不同。
t3code的设计初衷,就是把这类"连接成本"压到最低。我选了 T3 Stack 作为整个代码库的底座,打通了六个环节:Next.js 处理页面与服务端渲染,TypeScript 保证类型安全,tRPC 负责前后端调用,Tailwind 快速出样式,Prisma 管理数据库,NextAuth 处理登录。这个组合不是随便拼的,它们有一个共同点:都把开发体验放在优先级很高的位置。尤其是 tRPC,后端定义一个查询,前端立刻拿到完整类型提示,接口文档在这一套体系里几乎可以消失。
在动手写之前,我先反复问了自己一个问题:t3code到底要做一个示例,还是一个能作为产品起点、长期维护的模板?最终我选择了后者。因为如果只是一个 Demo,我会怎么写开心怎么来,但那对你没有参考价值;如果把它当成产品起点,每一条代码都要考虑"三个月后还能不能看懂"。事实证明,这个选择几乎影响了后面的所有细节。
另外一个关键选择是"约定优于配置"。t3code没有发明新架构,而是老老实实遵循 create-t3-app 的目录约定:src/server放所有服务端逻辑,src/app放页面,src/trpc放 tRPC 的初始化代码。这样任何一个熟悉 T3 Stack 的人接手,都能第一时间找到对应文件。项目代码的可读性有时候比某个巧妙的设计重要得多,尤其是当你不打算一个人维护的时候。
1.2 看一遍六层依赖与一次完整请求
t3code里的层次关系很清晰,从底到上可以画成一张表。不需要背,写代码的时候我会顺手对照:
| 层次 | 位置 | 负责的事 |
|---|---|---|
| 数据库 | PostgreSQL / SQLite | 持久化存储全部业务数据 |
| Prisma 数据层 | src/server/db.ts | 提供唯一数据库客户端实例 |
| tRPC 服务端 | src/server/api/root.ts与 routers | 定义全部业务 API |
| tRPC 客户端 | src/trpc/react.ts | 前端调用 API 的统一入口 |
| Next.js 页面 | src/app | 服务端渲染与客户端交互 |
| 样式层 | Tailwind CSS | 设计系统与原子类 |
以t3code里最核心的"文章发布"功能为例,一次普通的读请求长这样:浏览器请求 Next.js 页面,Next.js 在服务端调用 tRPC 的post.list过程,tRPC 通过 Prisma 查询数据库,拿到数据后返回给 Next.js,由服务器组件渲染成 HTML。整个过程只有一个 API 层,不存在 REST 路由、redux、axios 这些中间人。
这个设计的精妙之处在于,t3code把数据获取的入口统一收敛到了 tRPC。前端组件里可以直接写const posts = await api.post.list.query(),然后放心使用返回结果的类型。如果数据库表结构改了,Prisma 会重新生成类型,TypeScript 立刻高亮所有受影响调用点,一个错误的修改根本走不到运行环节。
关于"类型安全链路",我用生活化的方式解释一下:以前你前后端两个人各写一份接口文档,后端改了字段你不一定知道,上线当天才发现参数名对不上,就像两个厨子各按自己的菜谱做同一道菜,出锅了才发现口味不一致。现在t3code的 TypeScript 类型从数据库一路穿透到页面组件,前后端手里拿的是同一份菜谱,改菜谱的地方必然连带改动所有依赖这道菜的地方,想不一致都难。
2. 核心细节解析与实操要点
2.1 目录结构里的设计语言
先看一眼t3code的核心目录结构:
t3code/ ├── prisma/ │ └── schema.prisma ├── src/ │ ├── app/ │ │ ├── (auth)/ │ │ ├── dashboard/ │ │ ├── layout.tsx │ │ ├── page.tsx │ ├── server/ │ │ ├── api/ │ │ │ ├── root.ts │ │ │ └── routers/ │ │ ├── auth.ts │ │ └── db.ts │ ├── trpc/ │ │ ├── react.ts │ │ ├── server.ts │ │ └── init.ts │ ├── components/ │ └── styles/ └── .env.exampleprisma/目录里只有schema.prisma,所有数据模型定义都在这一个文件里完成,它是"数据库结构的唯一事实来源"。src/server/里又细分了api/、auth.ts、db.ts,分别放 tRPC 路由、NextAuth 配置和 Prisma 客户端实例。为什么要单独拎一个db.ts?因为 Prisma Client 应当作为单例存在。如果每个路由文件都new PrismaClient(),开发模式下热更新会导致连接数暴涨,终端里全是绿色的"Too many connections"警告,这是非常典型的血泪教训。
src/trpc/是整个代码库里最重要的一个目录。里面不仅有initTRPC、createTRPCContext,还定义了publicProcedure、protectedProcedure两个过程工厂。前者允许匿名访问,后者会先校验 session、再执行后续逻辑。t3code在做创建文章、删除评论这类写操作时,全部走 protectedProcedure,这样业务代码不用到处写权限判断。
前端部分没有把组件按"页面"硬切,而是按"功能区块"拆。auth/放登录注册相关组件,posts/放文章列表和编辑器。这一点我吃过亏:以前把所有组件堆进一个_components文件夹,项目一改版根本分不清组件属于哪条业务线。后来改成按 feature 组织,每次需求变更只需要打开对应目录,改代码范围一目了然。
2.2 为什么是 tRPC,而不是 REST 或 GraphQL
这是t3code在技术选型时争议最大的点。很多朋友一上来就问我:你为什么不老老实实写 REST 接口?我也不是没犹豫过。REST 直观,GraphQL 灵活,但 tRPC 的核心优势是"把 API 调用变成一次类型安全的函数调用"。在 Next.js 服务端组件里,你不需要 fetch URL、不需要解析 JSON、不需要记忆接口路径,直接调用导入进来的函数就行。写业务代码时你根本不需要关心"这个 API 的 URL 是什么",而只关心"我要拿到什么数据",开发速度完全是另一回事。
从网络层面讲,tRPC 只是把 JSON 数据包通过 HTTP POST 发到/api/trpc,本质上没有引入任何黑魔法。它和 REST 的差别在约定层面:REST 强调资源语义和 HTTP 动词,tRPC 强调调用过程和过程名。对于内部全栈项目来说,REST 的语义设计成本是不必要的开销;GraphQL 又复杂到需要团队统一规范才能玩得转。这不是说它们不好,如果你要开放 API 给第三方,我仍然建议用 REST 或 GraphQL,但t3code的定位是"快速构建内部产品",tRPC 刚刚好。
简单对比一下三者:
| 维度 | REST | GraphQL | tRPC |
|---|---|---|---|
| 类型安全 | 需要手动维护类型和 schema | 通过 Codegen 间接获得 | 原生类型推断 |
| 学习成本 | 低 | 高(schema 设计、缓存方案复杂) | 中低 |
| 适合场景 | 开放 API、第三方集成 | 复杂聚合查询、多客户端 | 内部全栈产品 |
| 接口文档 | 需要 Swagger 等 | 需要维护 schema | 类型自带 |
| 调试手段 | 浏览器直接访问 | Playground | 浏览器 Network 面板 |
如果你想体验这套类型穿透的爽感,可以做一个高频操作:在src/server/api/routers下新建一个 router,注册到根路由后,刷新一下页面,前端输入框里立刻能拿到新 router 下的所有 query。这个体验在新手阶段的冲击力很大,我第一次用的时候觉得不现实,以为是编辑器插件开的什么神秘通道,后来才明白这就是 TypeScript 推断的威力。
2.3 几个必须提前约定好的细节
- 环境变量集中管理。
t3code把DATABASE_URL、NEXTAUTH_SECRET、NEXTAUTH_URL都放在.env里,并用 Zod 在启动时校验。漏配一个变量,项目会在启动阶段直接报错,而不是运行后静默失败。这个体验对部署尤其重要,早一分钟失败,就少一小时排查线上诡异问题。 - 前端组件不能直接 import
server/下的模块。Next.js 会报错,因为这会把 Node 服务端代码打进浏览器包。t3code在服务端入口文件顶部用了server-only包,误引用时编译阶段就会失败。 - TypeScript 严格模式不要关。
strict和 ESLint 规则都开着,代价是初期写代码时频繁被挑战,收益是之后每次改动代码都有安全感。喊严格模式没用的人,通常都是没在大型重构里受益过。
这些细节看起来琐碎,却决定了你能否在一个月后、三个月后继续维护这套代码。t3code里所有目录依赖方向都是朝内的:页面依赖 router,router 依赖服务端工具,服务端工具依赖数据库。任何依赖关系反向的结构,都会在 code review 里被当场打回。
3. 实操过程与核心环节实现
3.1 初始化项目与基础配置
上手t3code最快的路径,是从 create-t3-app 生成。我在本地执行:
npx create-t3-app@latest t3code交互式提示一路问下来:要不要 TypeScript?要不要 App Router?要不要 tRPC?要不要 Prisma?要不要 Tailwind?要不要 NextAuth?这个脚手架的好处是它已经替你把各框架的版本冲突、初始化代码、目录结构都理顺了,自己从零拼装没准要多花三个小时,还不一定保证能跑通。我建议除非你对 ESLint 有强烈偏好,否则其他项全选 yes 就好。
生成后,复制环境变量文件:
cp .env.example .env然后根据你的本地数据库情况修改DATABASE_URL。本地开发最省事的方式是先用 SQLite,把 prisma 的 provider 设为sqlite,连接串写成file:./db.sqlite。SQLite 不需要额外安装任何服务,跑完迁移就能预览效果;等部署到线上再切换到 PostgreSQL 也不麻烦,改一行 provider、改一个连接串就行。很多团队就是在本地 SQLite 上把功能全部验证完,再在 CI 里跑 PostgreSQL 测试,效果很好。
接着执行:
npx prisma migrate dev --name init npm run dev浏览器打开http://localhost:3000,能看到首页有完整的样式和示例数据,说明项目基础已经跑通。如果这一步有任何报错,大概率出在.env文件没配好,可以直接跳到第 4 章的问题速查表去对号入座。
3.2 数据模型设计的完整示例
t3code的核心业务模型主要有:用户、文章、评论、点赞、关注关系。用 Prisma 写数据模型的体验非常直观,先看用户和文章的模型:
model User { id String @id @default(cuid()) name String? email String? @unique emailVerified DateTime? image String? accounts Account[] sessions Session[] posts Post[] comments Comment[] likes Like[] } model Post { id String @id @default(cuid()) title String content String published Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt authorId String author User @relation(fields: [authorId], references: [id]) comments Comment[] likes Like[] } model Comment { id String @id @default(cuid()) body String createdAt DateTime @default(now()) authorId String postId String author User @relation(fields: [authorId], references: [id]) post Post @relation(fields: [postId], references: [id]) }这里 id 统一用cuid()而不是自增整数,主要考虑是分布式场景不容易撞 id,且公开接口不希望暴露业务量。实际项目里如果一开始用了自增 ID,之后想改成 cuid,会牵动大量外键,所以干脆在一开始就定好。
多对多关系是新手比较容易写懵的部分。比如用户和文章之间如果有"点赞"关系,需要显式定义一个中间模型:
model Like { id String @id @default(cuid()) userId String postId String user User @relation(fields: [userId], references: [id]) post Post @relation(fields: [postId], references: [id]) createdAt DateTime @default(now()) @@unique([userId, postId]) }这个设计把"一个用户只能给同一篇文章点一次赞"写进了数据库层面,比单纯在业务代码判断更稳。为什么不在 User 和 Post 之间用隐式多对多关系?我建议显式建中间模型,因为之后大概率要加 createdAt、状态位这类字段,隐式表到时候改起来会麻烦得多。
修改完模型后,执行:
npx prisma migrate dev --name add_comment_and_like npx prisma generate这里必须强调:prisma migrate dev只适合开发环境,它会把本地迁移文件和环境同步;线上数据库的更新应该用prisma migrate deploy。如果把它们混用了,最直接的后果是部署流程里出现一堆本地无关的迁移记录,甚至不小心把开发环境的脏数据同步到生产库。
3.3 tRPC 路由与完整鉴权实现
t3code在src/server/api/root.ts里把各个子路由合并成 appRouter,然后被 Next.js 的/api/trpc路由接管。以文章创建为例,核心代码:
import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { db } from "~/server/db"; export const postRouter = createTRPCRouter({ create: protectedProcedure .input( z.object({ title: z.string().min(1).max(100), content: z.string().min(1), published: z.boolean().default(true), }) ) .mutation(async ({ ctx, input }) => { return ctx.db.post.create({ data: { ...input, authorId: ctx.session.user.id, }, }); }), });这段代码已经完整表达了三层业务约束:只有登录用户能创建文章,标题必须有内容且不超过 100 字,正文非空,当前用户 ID 来自服务端 session,而不是前端传参。最后这一点极其重要,如果依赖前端传用户 ID,任何有点经验的人都能伪造请求越权操作。直接在服务端从 ctx 里拿 session 里的 ID,是t3code从安全层面必须保证的。
列表查询同样简单:
list: publicProcedure .input(z.object({ onlyPublished: z.boolean().default(true) })) .query(async ({ ctx, input }) => { return ctx.db.post.findMany({ where: input.onlyPublished ? { published: true } : undefined, orderBy: { createdAt: "desc" }, include: { author: { select: { name: true, image: true } } }, }); }),前端调用时:
"use client"; import { api } from "~/trpc/react"; import { useRouter } from "next/navigation"; export function PostCreateForm() { const router = useRouter(); const createPost = api.post.create.useMutation({ onSuccess: () => router.refresh(), }); return ( <form onSubmit={(e) => { e.preventDefault(); const formData = new FormData(e.currentTarget); createPost.mutate({ title: String(formData.get("title")), content: String(formData.get("content")), }); }} > <input name="title" className="border p-2" placeholder="标题" /> <textarea name="content" className="border p-2" placeholder="正文内容" /> <button type="submit" disabled={createPost.isPending}> {createPost.isPending ? "发布中..." : "发布文章"} </button> </form> ); }这段代码里的isPending直接控制按钮 loading,提交成功后调用router.refresh()刷新服务端组件数据。整个过程没有手动发过一次 fetch、没有写过一行接口文档,前后端类型保持一致。这就是t3code的核心体验。如果你做的是纯客户端交互场景,useMutation返回的onSuccess里也可以做同样的数据刷新,保证页面数据始终以数据库为准。
3.4 登录注册与 Session 处理
登录是任何全栈项目都绕不开的模块。t3code接入的是 NextAuth.js,最省事的配置是 GitHub Provider。本地开发时在 GitHub OAuth Apps 里申请一个 client id 和 secret,填入.env就能登录。如果不想用第三方,也可以用 Credentials Provider,自己写用户名密码校验逻辑,但这样你得自己处理密码哈希、找回密码等流程,工作量并不小。我建议第一版先用 GitHub 登录验证业务,后续再考虑加更多 Provider。
配置的核心在src/server/auth.ts:
export const authOptions: NextAuthOptions = { session: { strategy: "jwt" }, providers: [GitHubProvider({ clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET })], callbacks: { jwt({ token, user }) { if (user) token.user = user; return token; }, session({ session, token }) { return { ...session, user: token.user }; }, }, };为什么 session 策略用 JWT 而不是 database?对大多数t3code这类轻量产品来说,JWT 更简单、不额外依赖数据库 session 表,且 Vercel 这类 Serverless 环境下更容易扩展。如果你的产品要"踢人下线"、服务端 session 吊销这类强管理,再切回 database session 模式也不难。
session 与 tRPC 的配合是关键。createTRPCContext里调用getServerSession(authOptions),这样每个 tRPC 请求都会携带已登录用户信息。之后在protectedProcedure里直接解构ctx.session就能拿到用户 ID。这套方案比我在一些项目里见过的"自己写 JWT 中间件"干净得多,session 刷新、过期都由 NextAuth 管理,业务代码里只需要关心"当前用户是谁"。
前端判断是否登录,我通常用一个useSessionhook 包一下。但这里要强调一个容易写错的点:useSession是给客户端组件用的 hooks,服务端组件要判断登录状态应该用auth()方法,两者不能混用。t3code在src/server/auth.ts里导出了服务端用的auth(),也在src/trpc/init.ts里导出了客户端用的 session 相关类型。如果你在服务端组件里错误使用useSession,Next.js 会直接报错;反过来在客户端组件里 import 服务端模块,也会被server-only拦下来。这些约束看着严,其实是对你负责。
4. 常见问题与排查技巧实录
4.1 问题速查表
把t3code从开发到部署过程中我遇到过的、以及团队新同学经常踩的问题整理成下面这张表:
| 现象 | 主要原因 | 排查与解决 |
|---|---|---|
| 启动时报 Missing env variables | .env 没复制或字段不全 | 检查 DATABASE_URL、NEXTAUTH_SECRET、NEXTAUTH_URL 是否齐全 |
| 页面报 Server-only module imported by client component | 客户端组件引用了 server 目录模块 | 把逻辑挪到服务端组件,或通过 tRPC 调用代替直接 import |
| tRPC 类型不更新 | router 修改后前端类型未重建 | 重启 dev server,必要时rm -rf .next |
| 修改 Prisma schema 后报找不到模型 | 没执行 prisma generate | 每次 migrate 后执行npx prisma generate |
| NextAuth 登录后 session 为空 | Provider 回调或 session strategy 配置不对 | 确认session.strategy = "jwt",并检查 callbacks 返回值 |
| 页面能打开但数据是空的 | 数据库没跑迁移,或 Seed 没执行 | 执行prisma migrate dev和prisma db seed |
| 部署到 Vercel 后功能挂 | 用了本地 SQLite 路径或漏环境变量 | 切 PostgreSQL,重新配置线上环境变量 |
| 某次部署后数据库表对不上 | migrate dev 用在生产环境 | 生产环境改用npx prisma migrate deploy |
这张表的前三条几乎每个新接手t3code的人都会遇到,尤其第一条,通常不是配置有多难,而是忘了cp .env.example .env。把.env放进.gitignore已经是标配,所以在没有复制之前,项目启动就是必炸的。
4.2 三个值得单独展开的细节
第一个是.next目录缓存问题。tRPC 类型理论上不需要额外生成,但 dev server 对文件修改的监听有时会漏掉深层类型变化,导致前端拿到的还是旧类型。我自己处理这类问题有一套标准动作:先停止 dev server,再执行rm -rf .next,最后重新npm run dev。很多时候"重启大法"没用,是因为没有清缓存,问题出在旧的编译产物上。
第二个是 Seed 数据问题。第一次跑迁移后如果表里没有数据,列表页面会大面积白屏,容易被误认为代码 bug。我的做法是在prisma/seed.ts里创建两三个固定测试用户和一二十篇文章,然后执行:
npx prisma db seed这样登录验证、分页测试、关注点赞这些操作都有真实数据支撑,开发效率高很多。
第三个是部署环境下的连接串问题。很多第一次部署的人把DATABASE_URL写成file:./db.sqlite,然后丢到 Vercel 上,线上自然找不到文件。t3code的部署建议是:本地用 SQLite 验证完功能后,生产环境切换 PostgreSQL;.env.production里单独维护连接串;部署前先本地执行一次npm run build,让 TypeScript 和 ESLint 在 CI 前把问题暴露出来。
说到后台上线,还有一个很多人忽略的点:Next.js 在构建时如果页面里有动态数据,千万别用静态导出(output: 'export')。t3code大量依赖服务端组件调数据库,必须让 Node.js 服务运行在 Vercel 或自己的服务器上。如果你在搭建时误开了静态导出,会出现"部署成功但页面全是 404"的怪现象。
5. 从本地到线上:部署与后续扩展方向
5.1 一键部署的完整路径
部署t3code到生产环境的最佳选择是 Vercel,因为 Next.js 和 Vercel 同生态,基本不用改代码。第一步先把数据库切到 PostgreSQL。如果不想自己维护数据库,Supabase 或 Neon 都提供免费层级的 Postgres,连接串复制到.env.production即可。第二步,把DATABASE_URL、NEXTAUTH_SECRET、NEXTAUTH_URL、GitHub OAuth 的 client id 和 secret 全部配置到 Vercel 的环境变量面板。第三步,设置构建命令为项目默认的迁移加构建流程,或者直接在 Vercel 的 Build Command 里写:
npx prisma migrate deploy && npx prisma generate && npm run build注意NEXTAUTH_URL在线上一定要是真实域名,也就是https://yourapp.vercel.app,而不是http://localhost:3000。这个写错会导致 OAuth 回调失败,用户点击登录后跳转到奇怪地址,排查起来很费劲。如果你自己有服务器,用 Docker 部署也一样,把 Dockerfile 写成多阶段构建,用next start作为启动命令,数据库连接串同样来自环境变量。
部署完成后建议立刻跑一遍核心链路:注册登录、发文章、评论、点赞。别只在首页看一眼就以为万事大吉,我吃过这个亏:首页静态渲染正常,但登录后接口全部超时,最终发现是部署环境没把数据库迁移跑完。把核心链路本地验证一遍,能省掉一半"线上为啥比本地慢/挂"的疑问。
5.2 后续可以往哪个方向扩展
t3code的架构给后续扩展留了很自然的接口。想加文件上传,可以在 tRPC 里增加一个上传 mutation,把文件写入对象存储,然后保存 URL;想加全文搜索,直接用 PostgreSQL 的全文检索或外接一个搜索服务;想加通知系统,可以在 Prisma 里建一个 Notification 表,服务端在评论、点赞创建后写一条记录。
如果你想让t3code成为一个博客系统,还需要补上这几块:文章的 Markdown 渲染、SEO 元数据、站内搜索。如果你想把它做成社区产品,那要考虑审核机制和内容举报,这些都可以在现有 tRPC 路由上继续扩展,不会推翻整个架构。
我自己下一步的计划是给t3code加上一套基于角色的权限系统:普通用户、管理员、超级管理员。因为目前的 protectedProcedure 只区分"是否登录",而真实产品往往还要区分"是否有权限操作"。具体实现不复杂,可以在 protectedProcedure 之外再包一层 adminProcedure,在调用链路上多校验一次用户角色字段。这条加完之后,后台管理页面就能顺理成章地放开写,整个示例的完整度也会再上一个台阶。
6. 最后的实际操作体会
我在实际整理t3code的过程中最大的体会是:这种"全栈套件型"项目最值钱的地方,不是某段代码写得有多优雅,而是它把一堆优秀的工具用一种克制的方式组合在一起。你不需要理解每个框架的全部源码,只要顺着流程跑一遍,就能切实感受到端到端类型安全带来的踏实感。全栈项目最大的成本永远在维护,而维护成本的高低,往往在你写下第一行目录结构时就已经决定了。
最后再分享一个小技巧:如果你打算拿t3code当模板做自己的产品,第一件事不是打开编辑器改代码,而是把prisma/schema.prisma从头到尾读三遍。因为数据库模型就是业务边界,边界画错了,后面每一个页面都要跟着返工。先把模型设计得尽量贴合真实需求,再顺着 tRPC 的链路一层层往上补页面,比直接在组件里堆逻辑要高效得多。祝你把t3code改成你自己的样子,然后跑起来、部署出去、收到第一个真实用户。