
1. 项目概述t3code 到底是什么1.1 项目背景与需求解析先说清楚一个事情t3code 不是什么官方框架也不是某个大厂的开源库。它是这两年我在做全栈项目时沉淀下来的一套约定式脚手架最初只是自己本地维护的一些模板文件后来被几个团队内部复用慢慢打磨成了现在这个状态。你可以把它理解成“一个装满最佳实践的起点工程”把 TypeScript 全栈链路里最麻烦的配置问题一次性解决掉让你从零开始一个新项目时半天内就能跑通“数据库 → API → 前端页面”的完整闭环。为什么会想做这个东西因为现实开发里我们碰到的最大痛点不是写业务代码而是“大量时间花在重复搭建和联调上”。举几个真实场景后端写好了接口前端不知道参数类型两边对着文档调半天数据库表结构改了一个字段名后端代码改了前端类型定义忘了同步编译期不报错运行期直接崩鉴权逻辑每次都要从网上抄一遍抄完还得调试两天。这些事情本身不难但架不住每个项目都来一遍纯纯的重复劳动。t3code 的核心理念只有一句话让类型安全覆盖从数据库到 UI 组件的每一层。也就是说数据库里字段长什么样、后端接口接受什么参数、前端页面拿到的数据是什么结构这三者由一套代码推导出来任何一层改动其它层在编译期就会告诉你哪里出了问题。这套方案最初借鉴了 T3 Stack 的思路——TypeScript、Tailwind、tRPC、Next.js 这些技术的整合再加自己踩坑后补进去的 Prisma 数据模型约定和 NextAuth 鉴权封装最终形成一个不太需要动脑子的基础工程。1.2 这套方案解决了哪些具体问题如果你手里同时管着三五个中后台项目八成对下面这些场景不陌生第一是“接口文档失去时效性”。文档写的是 v1 版本的参数代码已经改到 v3新来的同事照着文档联调调完满屏报错。t3code 的做法是消灭手写接口层用 tRPC 在前端直接调用后端函数类型由 TypeScript 编译器自动推导接口结构变了调用方立刻在编辑器里看到红色波浪线根本等不到运行期。第二是“数据库表结构变更的连锁反应”。传统 ORM 虽然能同步实体类但前端的类型定义、校验规则、表单字段经常是另一套代码。t3code 直接用 Prisma 的 schema 文件作为唯一事实来源再通过 zod 和 tRPC 把类型约束一路传到浏览器端改字段名这种操作从“改三个地方”变成“改一个地方”。第三是“鉴权代码反复重构”。很多项目到后期才会认真做权限控制回头再补的代价极高。t3code 在脚手架阶段就把登录、会话、受保护路由、角色判断这些全接好了默认用 NextAuth 管理会话tRPC 层做统一鉴权拦截业务代码里不需要自己写任何 session 或 token 的解析。1.3 适合谁来用我按使用人群拆开说。如果你是刚接触全栈开发的新人t3code 最大的价值是省去配置地狱。你不需要理解 Next.js 的每个配置项为什么存在不需要研究 Prisma 的 datasource 应该怎么填跟着这篇文章的步骤走一遍就能得到一个结构清晰、类型安全的完整工程这时候再去看官方文档很多概念会突然变得好懂。如果你是有经验的开发者想快速验证一个想法或给团队搭内部系统t3code 能帮你把“基建时间”压缩到极致。核心代码和业务无关全部封装在src/server和src/lib里你只需要按约定添加自己的业务模块剩下的事情脚手架全包了。如果你在带团队想让大家的代码风格统一、减少互相踩坑t3code 这类约定式脚手架也很适合作为团队的 starter template——目录结构、命名规范、错误处理方式都约定好了新人上手速度会快很多。2. 技术栈选型解析每个组件为什么是它2.1 全栈框架Next.js App Router 的取舍全栈方案里框架层的选择通常会在 Next.js、Nuxt、SvelteKit 之间纠结。我最终定在 Next.js 上有一个很实际的理由生态半径。公司里做中后台系统时涉及到的 UI 组件库、图表库、状态管理库绝大多数都优先支持 React 生态团队成员也普遍接触过 React。选一个大家熟的技术栈比选一个理论上更好的技术栈在实际落地时省下的沟通成本高得多。App Router 是 Next.js 13 之后默认的路由方案它带来最大的变化是“服务端组件优先”。在 t3code 里大部分页面默认就是服务端组件直接调用数据库查询函数拿数据渲染不需要额外的 API 请求层。只有像表单提交、搜索联想这类需要交互的场景才用use client标记转成客户端组件接入 tRPC。服务器组件减少了一次“前端 fetch → 后端接口 → 返回 JSON → 前端再渲染”的往返。体验上最直观的体现是首屏加载时间变快了而且代码逻辑更简单——你写的查询函数就在服务端执行不需要考虑用什么 hook 来管理请求状态。刚开始用 App Router 时我担心学习曲线实际踩下来发现只需要掌握三个概念布局与页面、服务端组件与客户端组件的边界、路由处理程序的写法。剩下的和普通 React 开发没有本质区别。2.2 数据层Prisma 如何统一类型与数据库选 ORM 时我其实有一段时间在 Prisma 和 Drizzle 之间摇摆。Drizzle 更轻量SQL 风格也更接近原生但 Prisma 有一个 Drizzle 目前还比不上的能力它不止是一个查询工具更是一个声明式的数据建模工具。Prisma 的schema.prisma文件定义了数据库的全部表结构这是 t3code 一切类型安全的源头。举个例子当你在 schema 里定义了一个User模型Prisma 会自动生成对应的 TypeScript 类型后面你在业务代码里写prisma.user.findMany()返回值自带完整的字段提示和类型检查。字段名拼错了、类型对不上编译器当场就能发现根本不用跑一次查询才知道出错。而且 Prisma 的 migration 机制对团队协同特别友好。数据库结构变更通过迁移文件记录团队每个人跑一下prisma migrate dev本地库就同步到最新结构。相比手动写 SQL 建表改表Prisma 胜在“可追溯、可回滚、可自动生成”。在我们的模板里prisma/schema.prisma是最核心的文件其它所有层的类型都从它推导而来。2.3 API 层tRPC 如何消灭重复定义这是 t3code 的灵魂。传统前后端分离式开发中后端定义 REST 路由前端维护 API 调用模块两边各维护一份“请求与响应”的定义如果中间没有强约束很容易出现字段不同步。GraphQL 解决了字段选择的问题但引入了 schema 学习成本和复杂的缓存策略。tRPC 给出的答案是干脆不要单独的 API 层。tRPC 的工作机制用一句话说后端写一个函数前端直接以调用本地函数的方式调用它类型自动推导。这里的核心魔法是 TypeScript 的编译期类型推导——你不需要写 URL、不需要定义 DTO、不需要处理序列化协议。前端代码里trpc.post.list()这个调用在编译时就绑定了后端post.list函数的入参类型和返回类型。入参不符合要求编辑器立即标红连运行都不用运行。很多人会问tRPC 是不是只能用在全栈同构的 Next.js 项目里其实不是只要前后端都用 TypeScript即便部署在完全独立的两个服务上tRPC 依然适用只是传输层从进程内函数调用变成了 http 请求。在 t3code 的默认配置里我们用 Next.js 的 API Route 来承载 tRPC 请求这样就能享受同一个代码库内“零配置”的联调体验。2.4 样式与鉴权Tailwind 和 NextAuth 的配合方式样式这块选 Tailwind CSS理由很简单中后台项目的 UI80% 是布局和状态而不是复杂的设计稿还原。Tailwind 的原子类能极大减少 CSS 文件的维护量改样式基本不需要在 JS 和 CSS 之间来回跳。t3code 里默认配置了深色模式支持和一些常用组件类确保你用类名拼 UI 时不需要写额外样式覆盖。鉴权选用 NextAuth.js现在叫 Auth.js是因为它和 Next.js 的集成最顺滑默认支持多种 OAuth 提供方和数据库会话策略。在 t3code 中我做了两层封装第一层是 NextAuth 的配置对象集中管理登录回调、JWT 策略和 session 的持久化第二层是 tRPC 的 middleware拦截所有需要登录态的 API 调用未登录直接抛错。用这套组合业务代码里几乎感受不到鉴权的存在——你知道“这一步必须登录才能做”所以加一行protectedProcedure就完事了。不用在函数里写 session 判断防止遗漏。3. 目录结构与核心模块实现3.1 目录结构解读先看整体结构。t3code 按“服务端能力”和“客户端页面”分开组织强制你分清服务端代码和客户端代码的边界避免把数据库连接串或者密钥暴露到浏览器端。t3code/ ├─ prisma/ │ ├─ schema.prisma │ └─ migrations/ ├─ src/ │ ├─ app/ # Next.js App Router 页面 │ │ ├─ (auth)/ # 登录相关路由组 │ │ ├─ dashboard/ # 受保护的业务页面 │ │ ├─ api/ │ │ │ └─ trpc/ # tRPC 的 HTTP 入口 │ │ ├─ layout.tsx # 根布局 │ │ └─ globals.css │ ├─ server/ # 只运行在服务端的代码 │ │ ├─ api/ │ │ │ ├─ routers/ # 业务路由定义 │ │ │ ├─ trpc.ts # tRPC 初始化与中间件 │ │ │ └─ root.ts # 所有路由聚合 │ │ ├─ db.ts # Prisma Client 单例 │ │ └─ auth.ts # NextAuth 配置 │ ├─ lib/ │ │ ├─ utils.ts # 工具函数 │ │ └─ validations/ # zod 校验规则 │ ├─ trpc/ │ │ ├─ client.ts # 客户端 tRPC 实例 │ │ └─ provider.tsx # React Context Provider │ ├─ styles/ │ └─ env.js # 环境变量客户端安全映射 ├─ .env ├─ tailwind.config.ts ├─ next.config.mjs └─ tsconfig.json几个关键点解释一下。src/server目录是一个硬性约定整个 Next.js 应用里只有这个目录下的代码允许直接操作数据库、读取全部环境变量、写服务端逻辑。src/app下的页面文件如果是服务端组件可以导入src/server里的函数但如果被标记成了客户端组件就绝对不能导入否则 Next.js 在编译时会直接报错防止你没注意就泄露了服务端敏感信息。src/trpc目录则是给客户端用的 tRPC 实例它暴露的类型是从服务端推导出来的同构类型。这是整个类型安全闭环的关键连接器后面讲实操时会展开。3.2 数据库 Schema 设计在 t3code 中我默认提供了一套最基础的模型你拿到项目后可以直接在此基础上扩展。prisma/schema.prisma大致长这样generator client { provider prisma-client-js } datasource db { provider postgresql url env(DATABASE_URL) } model User { id String id default(cuid()) name String? email String unique password 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) author User relation(fields: [authorId], references: [id]) authorId String createdAt DateTime default(now()) updatedAt DateTime updatedAt } enum Role { USER ADMIN }设计上我倾向于使用cuid()而不是自增数字作为主键 ID。原因有两个第一cuid 在分布式环境下不依赖自增计数器不容易撞车第二从安全角度讲数字 ID 容易被遍历猜测cuid 就没有这个问题。很多初学项目喜欢用自增 ID等到做权限校验时才会发现数字 ID 有多容易被遍历——比如你把某篇文章的 ID 换成1、2、3试试看有些没做越权校验的系统直接就泄露了其它人的数据。Role枚举在脚手架层面就给用户和管理员两种角色后续扩展第三、第四种角色时直接改这个枚举Prisma 会自动同步到数据库约束和 TypeScript 类型不用手动改别的地方。3.3 tRPC 路由与鉴权流程tRPC 的编写模式我做了两个简单封装。先看src/server/api/trpc.tsimport { initTRPC, TRPCError } from trpc/server; import { ZodError } from zod; import { getServerSession } from next-auth; import { authOptions } from ../auth; export const createTRPCContext async (opts: { headers: Headers }) { const session await getServerSession(authOptions); return { session, ...opts }; }; const t initTRPC.contexttypeof createTRPCContext().create(); export const router t.router; export const publicProcedure t.procedure; const isAuthed t.middleware(({ ctx, next }) { if (!ctx.session?.user) { throw new TRPCError({ code: UNAUTHORIZED }); } return next({ ctx: { session: ctx.session }, }); }); export const protectedProcedure t.procedure.use(isAuthed); export const errorFormatter (shape) { if (shape.error.code PARSE_ERROR shape.error.cause instanceof ZodError) { return { message: shape.error.cause.issues }; } return shape; };这里解释一下每个概念。createTRPCContext负责构建每次请求的上下文对象。tRPC 的每个调用都会先执行这个函数把当前请求的 session 放进去。由于 NextAuth 的getServerSession在服务端执行时能自动解析 cookie所以这里可以非常安全地拿到登录态。protectedProcedure是一个带鉴权的中间件。业务代码里只要你想让某个接口必须登录后使用就把publicProcedure换成protectedProcedure。中间件里如果检查到session不存在直接抛出UNAUTHORIZED错误后续业务代码根本不会执行。这样做的优点是鉴权逻辑不会散落在各个业务函数里你一眼扫过去就知道哪个接口是需要登录的。看一个具体的业务路由示例src/server/api/routers/post.tsimport { z } from zod; import { router, publicProcedure, protectedProcedure } from ../trpc; export const postRouter router({ list: publicProcedure.query(async ({ ctx }) { return ctx.prisma.post.findMany({ orderBy: { createdAt: desc }, }); }), create: protectedProcedure .input( z.object({ title: z.string().min(2).max(200), 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, }, }); }), });ctx.prisma是从上下文对象里取出来的 Prisma Client 单例在createTRPCContext里已经初始化好。这里我特意把 Prisma 放进 context而不是直接在文件里 import 一个全局实例是为了方便后续测试时替换数据源。实战经验告诉我将数据库依赖注入到 context 而不是直接 import是让业务代码可测试的关键一步。input是 tRPC 区分 query 和 mutation 的参数校验层。这里用了 zod 做运行时校验。很多初学者会疑惑TypeScript 不是在编译期就能检查类型吗为什么还需要运行时校验因为编译期检查只对“调用方是 TypeScript 代码”有效可实际项目里总有一些场景会绕过它——比如第三方服务回调、旧版本前端缓存、直接就着浏览器 DevTools 改参数发送的恶意请求。zod 的作用就是在真正执行数据库操作之前把不符合要求的数据拦下来。4. 从零搭建 t3code完整实操过程4.1 环境准备与初始化先列一下需要提前准备好的环境工具我建议直接用最新版本省去很多兼容性烦恼。Node.js 20 LTS 或更高版本pnpm 9 或更高版本npm 和 yarn 也能用但 pnpm 对 monorepo 和依赖管理更友好PostgreSQL 14 及以上t3code 默认使用 PostgreSQL你可以换成 MySQL 或 SQLite但生产建议 PostgreSQLGit初始化工程时我推荐用 create-t3-app 直接生成官方模板然后在此基础上订制 t3code 的目录结构和配置。跑这条命令pnpm create t3-applatest my-app创建过程中会让你选择需要的模块按 t3code 的选型勾选Next.js、TypeScript、Tailwind CSS、Prisma、tRPC、NextAuth。其它如 Drizzle、ESLint 也可以选上不影响核心结构。生成好项目后我第一件事是检查tsconfig.json里的路径别名配置确保/指向src/。官方模板默认已经配置好不需要动。接着清理默认代码把官方带的示例路由和页面删掉替换成 t3code 的目录结构。这个步骤主要看你的具体项目如果是做一块全新业务直接开始写自己的路由就行如果是做后台管理系统就用我在第三章给到的结构做组织。4.2 数据库模型与迁移数据库连接串是整套工程跑通的第一道关卡。我不建议直接把连接串写死在代码里t3code 统一用.env文件管理并且提供.env.example作为模板。示例内容DATABASE_URLpostgresql://postgres:postgreslocalhost:5432/t3code?schemapublic # NextAuth 相关 NEXTAUTH_URLhttp://localhost:3000 NEXTAUTH_SECRETyour-secret-key-here注意NEXTAUTH_SECRET在开发环境可以用随机字符串但生产环境必须用一个长期稳定的密钥否则重启服务后用户会话全部失效。生成随机密钥的命令是openssl rand -base64 32数据库连接串填好后创建一个数据库然后跑迁移命令pnpm prisma migrate dev --name init这条命令做了三件事第一根据schema.prisma生成迁移文件第二在数据库里执行迁移创建对应的表和约束第三自动生成 Prisma Client让prisma对象具备完整的类型提示。后续你每改一次 schema都要跑一遍prisma migrate dev这样数据库结构和 Prisma Client 始终保持同步。如果在迁移时遇到“数据库连接失败”之类的报错先不要怀疑 Prisma99% 的情况是.env里的连接串写错了或者数据库服务没起来。用一句话总结排查思路先用 psql 或 DBeaver 能连上再谈 Prisma 能不能连上。迁移完成后我还习惯顺手跑一次pnpm prisma studio它是一个可视化页面能看到库里所有表的行数据开发阶段调试接口时比 psql 命令行方便很多。这个操作不影响代码结构但能极大节省排查数据问题的时间。4.3 业务接口与页面接入数据库就绪后我们需要把 tRPC 的 HTTP 入口注册到 Next.js 路由。打开src/app/api/trpc/[trpc]/route.ts写入import { fetchRequestHandler } from trpc/server/adapters/fetch; import { appRouter } from /server/api/root; import { createTRPCContext } from /server/api/trpc; const handler (req: Request) fetchRequestHandler({ endpoint: /api/trpc, req, router: appRouter, createContext: () createTRPCContext({ headers: req.headers }), }); export { handler as GET, handler as POST };这实际上就是给 tRPC 创建一个 HTTP 网关。前端所有 tRPC 调用都是 POST 请求到这个地址tRPC 内部再根据函数名路由到具体的 procedure。客户端侧src/trpc/client.ts定义了一个类型安全的调用实例import { createTRPCReact } from trpc/react-query; import { type AppRouter } from /server/api/root; export const trpc createTRPCReactAppRouter();然后在src/trpc/provider.tsx里创建一个 React Provider把 tRPC 客户端挂到应用根组件上。这样每个组件里都能通过const utils trpc.useUtils()来触发查询或是直接用trpc.post.list.useQuery()这样的 hook 拉取数据。接好之后在页面上写一个查询试试。比如在某个服务端组件里import { auth } from /server/auth; import { prisma } from /server/db; export default async function DashboardPage() { const session await auth(); if (!session) return div请先登录/div; const recentPosts await prisma.post.findMany({ take: 10, orderBy: { createdAt: desc }, }); return ( main h1欢迎{session.user.name}/h1 ul {recentPosts.map((post) ( li key{post.id}{post.title}/li ))} /ul /main ); }注意这里用的是服务端组件直接在组件里查询数据库。对只读场景来说这种写法比客户端 tRPC 调用更直接因为服务端组件代码永远只在服务器上跑不会把数据库查询逻辑暴露到浏览器里。如果是表单提交这类需要客户端触发写操作的场景用 tRPC 的 mutationuse client; import { trpc } from /trpc/client; export function CreatePostForm() { const utils trpc.useUtils(); const createPost trpc.post.create.useMutation({ onSuccess: () { utils.post.list.invalidate(); }, }); 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 nametitle placeholder标题 / textarea namecontent placeholder内容 / button typesubmit发布/button /form ); }这里有一个初学者容易忽略的点mutation 成功后手动调用utils.post.list.invalidate()。tRPC 是基于 React Query 构建的它自带缓存查询结果默认会被缓存一段时间。如果不主动失效缓存你刚创建的文章不会立刻出现在列表页里看起来就像“新增失败”一样。很多人第一次用 tRPC 时被这个问题困扰过我在这里提前踩好这个坑。5. 常见问题与排查技巧实录5.1 问题排查速查表我把这一年多里被问得最多的 t3code 相关报错整理成一张表格方便你遇到问题时对号入座。问题现象可能原因排查思路P1001: Cant reach database server数据库连接串错误 / 服务未启动 / 端口被占用先用 psql 检查 DATABASE_URL 指向的库是否存在P2002: Unique constraint failed表里已经有相同值而字段有唯一约束检查业务场景是否存在重复的 email 或用户名TypeError: ctx.prisma is not a function忘记在 createTRPCContext 里初始化 Prisma检查 context 构造器确认db是否被注入tRPC 调用返回UNAUTHORIZED会话确实未登录 / NextAuth 配置里 secret 不匹配打开浏览器控制台看 cookie确认 session 是否存在页面提示 localStorage 未定义组件被误标成服务端组件却使用浏览器 API检查该组件是否加了use client指令Failed to fetch /api/trpctRPC HTTP 入口没有正确注册确认src/app/api/trpc/[trpc]/route.ts是否存在部署后所有 session 失效NEXTAUTH_SECRET 变了部署环境必须显式设置与本地相同的 secret这张表是我最常发给团队成员的首轮排查工具它能覆盖九成以上的起步问题。5.2 我踩过的几个典型的坑第一个坑在客户端组件里直接导入服务端模块。某次做报表页面时我写了一个图表组件为了省事直接在组件里import { prisma } from /server/db然后在服务端把数据查好传给客户端。看起来挺合理的可 Next.js 编译时直接报错一个被标记为 client 的组件引用了只有 server 端才能使用的模块。这个报错不算突然它其实是 Next.js 在保护你——防止你在无意中把敏感逻辑暴露到压缩后的 JavaScript bundle 里。从那以后我形成了一个习惯先想清楚这个组件是在哪一端运行的再动手写导入语句。服务端组件可以导入任何东西客户端组件的导入范围只能包含无害的纯工具函数和类型。第二个坑tRPC 的 type-only import 没写对。早期版本里createTRPCReactAppRouter()里的AppRouter必须使用import type来导入否则客户端代码会把整个服务端路由实现打进 bundle导致客户端包体积暴涨甚至打包失败。事后复盘恰好是“类型只是类型运行时不携带任何逻辑”这个理念的体现——你把类型信息导入客户端是安全的但把实现代码导入客户端就是灾难。这一点新版 tRPC 已经做了优化但如果你还在用 v10 之前的版本关注一下 import 写法。第三个坑schema 文件里改了字段但忘了跑迁移。有一次我临时加了一个likes字段直接在代码里开始写基于它的查询逻辑。当然编译器没报错因为 Prisma Client 在prisma generate之后已经带了新字段的类型。可运行时报错说数据库表里没有likes这一列我才意识到自己根本没执行prisma migrate dev。这个问题的迷惑性在于类型检查通过了让人误以为数据库结构也更新了。事实是Prisma Client 的类型是从 schema 生成的而数据库表的结构是由迁移生成的两者可能不同步。每次改完 schema记得跑迁移没有例外。5.3 一些值得长期养成的工作习惯关于 t3code 这类脚手架的使用我总结了三条“如果用一次就回不去”的工作习惯。第一数据库结构变更先讨论再迁移。Prisma 的迁移是自动生成的但它面向的是数据库结构不是你业务逻辑。团队协作时如果新人改了 schema 直接一键迁移到共享开发库可能导致大家本地所有分支都出现数据库冲突。我建议在团队里约定迁移文件先提交到代码库每个人在自己分支上跑prisma migrate dev看到迁移文件内容后再决定是否应用。第二tRPC 路由命名用面向业务的动词。不要用userGetById、postCreate这种“函数式命名”直接叫list、create、update因为 tRPC 调用是trpc.post.list()这样的链式结构外层已经是领域名内层动词化即可。这比 REST 里纠结用 POST 还是 PUT 要直观得多。第三把 zod 校验写在 procedure 内部而不是外部单独定义文件。虽然 t3code 的模板里有一个validations目录但我实际使用时发现把 zod 的 schema 直接内联到 procedure 里可读性反而更高因为你能在同屏看到“参数规则”和“处理逻辑”。外置 schema 只在多个 procedure 共用同一组规则时才值得这么做。结尾最后分享一点个人体会。t3code 这类方案最大的价值不在于某一个技术选型有多先进而在于它把“前后端联调”这个环节的摩擦降到了最低。我在几个团队里推动这套脚手架落地时最直观的变化是新功能开发不再需要前后端分开排期等接口改表结构时设计师甚至能自己看出哪个字段的类型变了因为我们用的同一套类型定义。当然它也有自己的边界。如果你做的是纯展示型网站不需要登录、不需要写数据完全用 Next.js 的服务端组件就好不必强行上 tRPC如果你的团队里后端用的是 Java 或 Go前端是 ReacttRPC 这种同构方案就不适合因为它本质上是“全 TS 技术栈”的产物。工具永远是为场景服务的不要为了用而用。从这个项目往后扩展我打算做的方向是把这套脚手架里的鉴权模块替换成更细粒度的 RBAC 权限模型以及在 tRPC 层加入请求日志和监控指标的上报。如果你也在折腾全栈脚手架不妨从 t3code 的思路里挑一两个点试试——尤其是“让类型从数据库一路通到组件”这个理念我认为它是未来几年全栈开发最重要的方向之一。