news 2026/10/28 23:22:35

T3 Code全栈模板:Next.js+TypeScript+Tailwind+tRPC实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
T3 Code全栈模板:Next.js+TypeScript+Tailwind+tRPC实践指南

T3 Code这个名字乍一看没什么,但放回实际的前端工程语境里,每个字母都是踩过无数坑之后沉淀下来的选择——Next.js、TypeScript、Tailwind CSS、tRPC。这四样东西单独拿出来都很能打,但真正把它们拧成一股绳,做成一套可以直接复制、开箱即用的全栈项目模板,这就是t3code这个项目干的事。

我最早接触T3相关概念,是在折腾全栈个人项目的时候。那时候技术选型特别纠结:API层是上REST还是GraphQL?数据校验放哪边?服务端状态和客户端状态怎么分?样式用CSS Modules还是上设计系统?每换一个方向就要推倒重来一次,折腾的项目多了,就总想着能不能有一整套组合拳,把这些问题一次性压下去。t3code就是冲着这个痛点来的——它不是一个框架,也不只是一个脚手架,它代表了一套被验证过的技术选型标准和项目组织范式,尤其适合那些想快速从零起步、又不愿意在未来半年里反复重构的前端团队或个人开发者。

这篇文章不打算做一份照搬官方文档的翻译稿,我就以实际把一个项目从Initialize到部署上线的完整过程为线索,把这个组合里最重要的几个决策点、目录怎么组织、类型安全怎么打通、部署时有哪些坑,全部摊开讲清楚。无论你是想评估这套方案到底适不适合自己的场景,还是已经想动手用它跑一个小项目,这篇内容都能给你一个比较完整的参考。

1. 项目整体设计与技术选型背后的思考

1.1 为什么是这四件套,而不是其他方案

先说说这套组合的核心思路。任何一个全栈项目,抽象到最后其实就是在解决四件事:页面用什么渲染、数据从哪来、类型怎么统一、样式怎么组织。T3的组合逻辑,就是把每个维度上效率最高、心智负担最小的工具拼在一起。

Next.js负责应用骨架和渲染策略。它把前端路由、服务端组件、API Routes和中间件都收在同一个工程里,不需要单独维护一个前端项目和一个后端项目,部署的时候也可以一把梭。特别是App Router出来之后,Server Component和Server Action把很多原本需要前端自己发请求、自己管理loading状态的逻辑,直接下沉到了服务端,代码量能砍掉三分之一还多。

TypeScript是这套方案里的粘合剂。它不是可选项,而是强制性的基础。因为后面tRPC和Prisma之所以能把类型安全贯彻到底,全靠TypeScript在编译期的约束。没有类型系统的话,tRPC的价值直接损失一半,Prisma的client高效用法也发挥不出来。

Tailwind CSS解决样式组织问题。原子化CSS的方式争议一直很大,但用过的人都知道,它在不离开HTML结构的情况下完成样式设计,完全没有命名焦虑,也没有css文件碎片化的困扰。配合classnames条件拼接,响应式和暗黑模式都靠class切换,整个样式系统的复杂度被压得很低。

tRPC是真正的杀手锏。它不发明任何新协议,就是基于HTTP把RPC风格的函数调用直接暴露给前端,调后端函数就跟调本地函数一样,入参类型和返回值类型完全从服务端推断出来。REST需要手动维护接口文档和客户端类型,GraphQL需要处理schema和resolver的同步,tRPC干脆把这一层全部自动化了。

注意:tRPC本身依赖TypeScript的类型推断能力,如果你的项目里有大量非TypeScript的历史代码,或者团队对类型安全没有那么高的执念,tRPC会变成负担而不是效率工具。我后面会详细说这种边界情况。

1.2 t3code定位:不是框架,是一套被验证过的组织范式

很多人误会t3code是一个类似Next.js本身的框架,其实不是。它更像是一个起步模板加上一套工程约定,在这套约定里包含了路由规范、数据访问层规范、认证集成方式、部署脚本,以及从创建项目到上线的完整路径。

这套范式的好处是决策前置。一个项目最容易出现分歧的时间点,往往是刚开始做技术选型的时候。你问三个人REST和GraphQL怎么选,能吵三天。但如果大家默认采用t3这套路线,很多争论从一开始就不存在了——模型校验用Zod、数据库ORM用Prisma、查询方式走tRPC、开发联调不需要mock server,每个人拿到的都是同一个约定。

它也不强迫你把所有东西都绑定在单一框架上。T3组合的核心思想是渐进式采用,你的项目如果已经存在一个旧的后端服务,t3code并不是逼你把后端推倒重来,而是可以只在新增的页面和功能模块里引入这套链路,服务端tRPC router照常可以代理转发到旧服务,前端的类型安全和开发体验一样成立。

1.3 这套方案特别适合谁,不适合谁

先说特别适合的人。个人全栈开发者或者三五个人的小团队是t3最舒服的使用场景,因为不需要在接口文档上反复对齐,类型即文档,改一个后端字段,前端编译期就报错,少了很多低效沟通。其次就是做内部工具、后台管理系统、CRM这类对开发速度要求极高的场景。这类系统通常页面多、交互密、数据结构复杂,但并发量不大,REST那套写起来既繁琐又缺类型保障,t3的组合方式效率和可靠性都要好不少。

不太适合的场景也很明确。如果你的项目需要对外开放API给第三方调用,那tRPC并不是一个标准的对外协议,对方必须按你的规范写客户端,会很麻烦。另外,如果团队里有很大一部分是纯后端背景,对TypeScript类型体操没兴趣,或者项目里还有非Node服务承担核心业务逻辑,硬上t3反而会制造内耗。

2. 核心细节解析与实操要点

2.1 目录结构设计的逻辑

用create-t3-app初始化之后,项目的目录结构表面看起来中规中矩,但里面每个目录都有它的设计意图。我从一个被几千人看过、自己也反复重构过的项目里总结出来的最舒服的布局是这样的:

src/ ├── app/ # Next.js App Router 页面与路由 │ ├── api/ # 仅放第三方回调或特殊接口,核心API走trpc │ ├── (auth)/ # 认证相关路由组 │ └── (dashboard)/ # 业务页面路由组 ├── components/ # UI组件 │ ├── ui/ # 基础原子组件 │ └── shared/ # 业务共享组件 ├── server/ # 服务端代码 │ ├── api/ # tRPC router根节点和子router │ │ ├── routers/ # 按业务模块拆分的子router │ │ ├── root.ts # 汇总所有子router │ │ └── trpc.ts # tRPC实例与中间件定义 │ ├── auth.ts # 认证配置 │ ├── db.ts # Prisma客户端单例 │ └── query.ts # 服务端数据查询辅助函数 ├── trpc/ # tRPC客户端与服务端类型引用 │ ├── server.ts # 创建服务端调用上下文 │ ├── client.ts # 客户端proxy │ └── react.tsx # 客户端Provider挂载 └── styles/ # 全局样式入口

关键点在于按模块边界组织文件,而不是按技术类型堆文件。很多早期项目喜欢搞src/server/router.ts一个大文件塞几十个路由,到后面一场灾难。我建议在routers/目录下按业务域拆,比如userRouter、postRouter、teamRouter,每个router内部再通过createTRPCRouter嵌套子路由,这样找代码的时候是顺着业务去找文件,而不是在一坨接口里面做CTRL+F。

2.2 类型安全链路的完整构建

t3code里最值得称道的地方,是类型安全的链路不是只覆盖某一段,而是从数据库到UI组件完整贯通。我用一个实际的getUserById来演示这条链路长什么样。

数据库层面,Prisma的PrismaClient会把你的schema文件生成一份完整的类型定义,等于数据库表结构在写第一行业务代码之前就已经进入了TypeScript的编译体系。然后tRPC的router.procedure.input()用Zod定义入参,Zod schema的推断类型会同步到前端调用端;返回值类型更是直接从服务端函数实现中推断出来,完全不需要手写interface UserResponseDTO这种文件。

这条链路带来的实际好处是,你改一个数据库字段,从Prisma层面到tRPC返回类型到前端组件取值,整个链路在编译期就会把你所有引用点都标红。我在一个实际项目里体会到过这种幸福感,当时需求是给用户表加一个积分字段,改动过程就是:schema加一行,种子数据补个值,前端在显示的地方加{user.points},没有跑一次接口联调、没有对一次字段名拼写,前端直接编译通过,因为类型从头到尾都是通的。

2.3 认证体系的集成与上下文设计

T3模板最成熟的地方之一,是把认证和tRPC的上下文设计得干净利落。默认情况下,tRPC的context创建函数里会从请求的session中提取用户信息,挂到ctx.session上。这样每个procedure都有一个统一的上下文入口去获取当前登录用户,而不需要每个接口自己再去解析token。

我强烈建议项目在早期就弄明白认证中间件的作用域控制。模板里的protectedProcedure很直观,就是把publicProcedure包了一层auth校验。这里有个很多教程都讲得不透的点:中间件里的throw发生在procedure执行之前,前端拿到的错误是UNAUTHORIZED,这时候前端不应该简单弹一个"登录态失效",而应该在全局tRPC客户端链路里统一拦截这个错误码,做路由跳转或者状态清理。

实际项目里我见过不少前端调用tRPC时报401,然后每个页面自己写一套token过期的处理逻辑,重复代码满天飞。正确做法是在react-query的QueryClient配置里,针对401统一添加全局onError处理。这样认证逻辑在客户端也只维护一份,跟服务端在protectedProcedure里做的校验形成对称。

2.4 数据校验与DTO的取舍

Zod在这套技术栈里不只是用来做tRPC入参校验,它在很多边界场景里都很能打。比如从外部API拉数据的时候,响应可能是脏的,用Zod做一个运行时校验,解析出来的类型直接就是干净的TypeScript类型。再比如从环境变量读取配置时,用z.object().parse()保护启动配置,这在项目上线前一天因为环境变量少配置一个字段而崩溃的场景里救命过很多次。

关于是否要为每个操作单独定义DTO输入类型,我的建议是:必要的裁剪可以做,但不要过度设计。tRPC因为类型是从服务端推断的,天然省掉了REST开发中手写DTO的体力活。只有在需要对外暴露接口、或者数据结构的边界跟前端视图不完全一致时才需要显式定义。

3. 实操流程与完整项目搭建

3.1 初始化与基础配置

起步方式有模板和全手动两种,我觉得对于第一次接触的人,直接用官方脚手架最省心,因为很多配置细节(比如tsconfig路径别名、next.config的transpilePackages、eslint规则集)都预设好了。

pnpm create t3-app@latest my-project

交互式提示里会让你勾选需要的模块。我的经验是第一次玩就全选——Next.js、TypeScript、Tailwind、tRPC、Prisma、NextAuth——先把完整形态跑起来,再逐个删减,不然你根本不知道哪些模块是彼此配合的。

安装完成后第一件事,不是写代码,而是先跑一次构建验证基线是否正常。

pnpm dev

如果能正常打开首页,说明环境没问题。紧接着我习惯先做三件事:配置环境变量、清理示例代码、初始化数据库。

环境变量配置里,最容易踩坑的是DATABASE_URL。如果你本地用Postgres,需要先建好数据库;如果本地压根没装数据库,可以先在render或者neon上开一个免费实例,把连接字符串拿过来用。还有NEXTAUTH_SECRET,这个一定要用一个足够长的随机字符串,开发环境下可以随便填,但生产环境必须换掉,不然token随便就能被伪造。

3.2 数据库schema设计与Prisma接入

Prisma在这套栈里不只是ORM,更是类型的安全之源,所以schema设计值得认真对待。官方模板默认给了一个Post和一个User的示例模型,我通常会把示例清掉,直接写自己业务的模型。

拿一个简单的项目管理场景举例。假设我们要做Project和Task,两者是一对多关系,同时每个Project有多个成员,成员可以是Owner或者是Contributor:

model User { id String @id @default(cuid()) email String @unique name String? projects ProjectMember[] tasks Task[] } model Project { id String @id @default(cuid()) name String members ProjectMember[] tasks Task[] } model ProjectMember { userId String projectId String role String @default("MEMBER") user User @relation(fields: [userId], references: [id]) project Project @relation(fields: [projectId], references: [id]) @@id([userId, projectId]) } model Task { id String @id @default(cuid()) title String done Boolean @default(false) projectId String project Project @relation(fields: [projectId], references: [id]) assigneeId String? assignee User? @relation(fields: [assigneeId], references: [id]) }

这里有几个设计要点。主键我沿用模板的cuid(),而不是自增整数,原因很简单:cuid不怕分布式环境下的碰撞,也不会通过递增ID泄露业务规模;联合主键用在关联表上可以天然保证同一用户不会在同一个project里出现两条记录。关系字段里,任务的assignee是可选的,因为一个任务刚创建时可以没有人认领。

schema写好之后跑一次迁移:

npx prisma migrate dev --name init

这个命令会生成迁移文件,并把SQL应用到数据库,同时自动重新生成Prisma Client。后续每次scheam变更都用这个命令,它会帮你把迁移历史归档,上线时用prisma migrate deploy应用生产环境,非常顺滑。

3.3 tRPC路由的创建与实操

在t3code里,业务API不是写在pages/api下,而是全部放进server/api/routers目录里。以Task事务为例,完整router的搭建过程是这样的。

先建server/api/routers/task.ts:

import { z } from "zod"; import { createTRPCRouter, publicProcedure, protectedProcedure, } from "~/server/api/trpc"; export const taskRouter = createTRPCRouter({ listByProject: protectedProcedure .input(z.object({ projectId: z.string() })) .query(async ({ ctx, input }) => { return ctx.db.task.findMany({ where: { projectId: input.projectId }, orderBy: { createdAt: "desc" }, include: { assignee: { select: { name: true, email: true } } }, }); }), create: protectedProcedure .input( z.object({ title: z.string().min(1, "标题不能为空"), projectId: z.string(), assigneeId: z.string().optional(), }), ) .mutation(async ({ ctx, input }) => { return ctx.db.task.create({ data: { title: input.title, projectId: input.projectId, assigneeId: input.assigneeId, }, }); }), toggle: protectedProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { const task = await ctx.db.task.findUnique({ where: { id: input.id } }); if (!task) throw new Error("任务不存在"); return ctx.db.task.update({ where: { id: input.id }, data: { done: !task.done }, }); }), });

然后把这个子router汇总到根router:

import { createTRPCRouter } from "~/server/api/trpc"; import { taskRouter } from "./routers/task"; import { projectRouter } from "./routers/project"; export const appRouter = createTRPCRouter({ task: taskRouter, project: projectRouter, }); export type AppRouter = typeof appRouter;

这里最值得说的是protectedProcedure的认知。业务代码里凡是需要登录后才能操作的接口,统一用它,而不是用publicProcedure之后自己在函数里判断session。因为中间件逻辑是可组合的,以后哪怕要加个"只有项目成员才能操作"的权限校验,也能再包一层。模板里auth回调函数的返回值就是session挂载到ctx上,后续所有procedure都能拿到ctx.session.user.id,不需要反复去读cookie或者token。

客户端调用的时候,前端不需要知道接口是GET还是POST,也不需要考虑缓存策略。react-query帮你管了状态和缓存,tRPC proxy把调用远程函数变得跟调用本地函数一样。实际代码长这样:

const utils = api.useUtils(); const createTask = api.task.create.useMutation({ onSuccess: () => { utils.task.listByProject.invalidate(); }, }); // 在组件里直接 createTask.mutate({ title: "写周报", projectId: currentProjectId });

一个小技巧:很多人初用react-query不会管失效缓存,导致新建完数据,列表不刷新。上面这段代码里的invalidate就是核心,它告诉react-query"task.listByProject这个key的数据已经作废,重新拉一次"。更进阶的做法是直接用setData做乐观更新,函数返回后本地立刻更新UI,失败再回滚,但对新手来说,先掌握invalidate已经能解决绝大多数问题。

3.4 认证的完整接入

NextAuth的配置在T3模板里已经集成好,需要你填的主要是Provider。如果做纯内部系统,建议用Credentials Provider自己写校验逻辑,配合数据库里的User表。如果做面向外部用户的产品,GitHub/Google OAuth的接入体验更顺滑。

我以一个邮箱登录的内部工具为例,说一下Credentials Provider的关键写法:

import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; import { compare } from "bcryptjs"; import { db } from "~/server/db"; export const authOptions = { providers: [ Credentials({ credentials: { email: { label: "邮箱", type: "email" }, password: { label: "密码", type: "password" }, }, authorize: async (credentials) => { const user = await db.user.findUnique({ where: { email: credentials?.email }, }); if (!user) return null; const isValid = await compare(credentials?.password ?? "", user.passwordHash); if (!isValid) return null; return user; }, }), ], session: { strategy: "jwt" }, } satisfies NextAuthConfig;

需要注意的点有两个。第一,密码一定不要明文存,建议用bcryptjs做哈希,成本和安全性的平衡比较好,不要为了性能去选明文存储。第二,JWT策略下用户信息如果变了,比如昵称改了,Session不会自动同步,需要在callbacks.jwt里刷新数据。模板对这个场景有衔接,但很多人不读文档就会在这里困惑半天。

3.5 前端页面集成与数据流打通

页面层面,t3code用App Router时全局Provider只能加在app/layout.tsx里,因为它需要包住所有页面。与此同时,Server Component里不能直接用api.someQuery的hook形式,但可以在Server Component里直接用createCaller做服务端数据获取。这是很多新人的第一个卡点,我拆开说一下这两种用法的区别。

客户端组件里的调用方式:

"use client"; import { api } from "~/trpc/react"; export default function TaskList({ projectId }: { projectId: string }) { const { data, isLoading } = api.task.listByProject.useQuery({ projectId }); if (isLoading) return <div>加载中...</div>; return <div>{/* 渲染任务列表 */}</div>; }

服务端组件里的实现方式:

import { createCaller } from "~/server/api/root"; import { createTRPCContext } from "~/server/api/trpc"; export default async function ProjectBrowser() { const ctx = await createTRPCContext({ headers: await headers() }); const caller = createCaller(ctx); const projects = await caller.project.listAll(); return <div>{/* 渲染项目列表 */}</div>; }

两种方式应用的场景完全不同。如果页面里只需要一次性获取数据、不需要频繁交互,用server端caller,内容直接渲染成静态HTML发到浏览器,性能最高。如果页面里需要数据变更后的即时刷新,比如新建任务后列表要无感更新,就必须用客户端hook方式,配合react-query的缓存失效机制。理解了这个分工,整个页面的数据流就清清楚楚了。

3.6 部署上线的完整流程

部署这块是t3code最容易被低估的部分。很多人开发环境跑通了,一上生产就到处挂。根源在于生产环境要处理的差异点比开发环境多不少。

我目前比较推荐的部署路线是Vercel加托管数据库的组合,前提是你不需要长期占满服务器的硬件资源。核心步骤:

  1. 数据库迁移先跑:
npx prisma migrate deploy

注意不是migrate dev,dev会在迁移过程中做开发辅助操作,生产环境千万不要用。首次部署要先确保数据库的schema已经到位,否则应用启动时Prisma一查表发现结构不对,直接报错。

  1. 构建前先在本地检查一遍Build:
pnpm build

如果build命令挂掉,多半是类型错误或者Prisma Client没生成。大部分部署平台的日志能看出是哪个环节失败,但你在本地先跑一遍能极大缩短排查时间。

  1. 环境变量一个都不能少。用Vercel的话,在项目设置里把DATABASE_URL、NEXTAUTH_SECRET、NEXTAUTH_URL,以及OAuth相关变量全部填对。这里最容易犯的错是NEXTAUTH_URL填了http://localhost:3000,生产环境每次登录回调都会失败。

  2. 构建命令模板里已经配好了prisma generate,只要没有乱改build script,通常没问题。

关于自托管,如果你不想用Vercel,而是部署到自己的VPS上,建议用Docker文件跑next start。需要留意的是node版本不低于18.18,PM2的进程守护我个人也建议加上。数据库如果也部署在同一台机器上,注意备份策略,云数据库服务商的自动备份功能更省心。

4. 常见问题与排查技巧实录

4.1 冷启动和热更新的类型报错

实际开发里最烦的一种情况是,明明没有改类型相关的代码,但页面突然报一个来自Prisma的错。追溯下来大多是同一个原因:schema改了,但Prisma Client没有重新生成。官方文档建议每次schema变更后跑prisma migrate dev,但有些人图快,直接在数据库控制台手动改表,然后前端类型还是旧的。

解决办法很简单,养成改完schema就跑npx prisma generate或者prisma migrate dev的习惯。凡是遇到莫名其妙的类型错误,第一个排查方向永远是Prisma Client版本和schema是否同步。

4.2 tRPC调用时返回404

这个也是新手期高频问题。现象是前端调用某个procedure,控制台网络请求返回404,但检查router名称反复看都觉得没有写错。真正原因多半是router的文件没有被正确导入,或者子router挂在根router的键名和前端调用的路径不一致。

比如服务端注册的是{ task: taskRouter },前端如果调api.task.listByProject,那没问题;但如果你写成了api.taskRouter.listByProject,路径对不上,自然就是404。t3code的目录约定里,子router命名带不带Router后缀全看个人习惯,但一旦定下,所有调用端必须严格对齐。前端IDE里借助类型提示,这个错误通常会在输入API名字时就被拦住,反而说明类型链路的价值所在。

4.3 生产环境数据缓存不刷新

场景是生产环境明明已经有人更新了数据,页面上却还显示旧数据。t3code默认集成react-query,它有默认的缓存策略,staleTime不配置的话,客户端会认为数据在短时间内是新鲜的,根本不会发起新的请求。

如果是需要高实时性的数据,比如看板状态、任务完成情况,我给的建议是直接用useQuery时覆盖缓存策略:

api.task.listByProject.useQuery( { projectId }, { staleTime: 0, refetchOnWindowFocus: true }, );

或者,如果所有数据的实时性要求都很高,可以在QueryClient的默认配置里统一处理。但也要注意,不是所有数据都需要实时刷新,像项目列表这种相对静态的数据,适当延长staleTime反而能减少后端压力。

4.4 NEXTAUTH_* 环境变量引起的循环重定向

部署后用户访问首页,被不停地重定向到登录页,形成循环。这个问题十有八九是NEXTAUTH_URL和实际访问域名不一致。如果你用了子路径或者反向代理,这类问题还会更隐蔽。

排查思路:登录前先打开浏览器Console看redirect路径,如果重定向地址带的域名跟你访问的地址不同,改掉环境变量。再一个容易被忽略的点是反向代理的请求头要透传host和x-forwarded-proto,不然NextAuth拿到的协议是http,重定向到https时还是会出诡异问题。

5. 性能优化与工程化进阶

5.1 利用React Server Component减少客户端JS体积

tRPC + React Server Component这种组合在App Router时代的优势特别明显。过去Next.js Pages Router模式下,一个页面要用到哪些数据,客户端必须先把对应逻辑打包再执行。App Router的Server Component天然会把数据获取和渲染放在服务端,很多原本要在浏览器里跑的代码,现在不会进入客户端bundle了。

我在一个内部数据看板项目里实测过,把大部分列表页从客户端调用改成服务端caller之后,首屏JavaScript体积下降了约40%。代价是页面交互时需要额外配合Server Action或者客户端组件去处理变更,但这个取舍在绝大多数后台管理场景里都非常划算。

5.2 数据查询的性能边界与批量加载

tRPC的query函数如果写得不小心,很容易触发N+1查询问题。Prisma的include和select是把性能抓好最重要的两个工具。比如前面listByProject的写法里,我要拿每个任务的assignee姓名,如果不用include,那就得循环发查询去取用户信息,数据一多卡片就跟蜗牛爬一样。

大列表场景下我还建议用take加skip做分页,或者用cursor做游标分页。游标分页更适合做无限滚动,不会因为中间插入数据导致分页错位。Prisma对cursor的支持很完整,just setcursor: { id: lastItemId },配合take: 20就行。

5.3 日志与缓存监控

团队协作或者个人项目到了中期之后,就会开始关心线上到底有没有报错。tRPC的错误默认不会打印堆栈,建议在根中间件里统一catch:

export const t = initTRPC.context<typeof createTRPCContext>().create({ errorFormatter({ shape, error }) { console.error("[tRPC]", error); return shape; }, });

线上环境就按这个思路,把对应平台的日志服务接进去。前端每次点击操作如果静默失败,用户可能已经反复点了好多次提交按钮,带着一堆重复请求打到后端。这种事在用户量不大时不容易暴露,但等系统正式投入使用,日志里能看到大量重复mutation。

6. 最后分享几个我反复用的实战技巧

一个是关于目录的命名。用过t3的人可能都会纠结,组件目录到底叫components还是ui。我建议在一个项目里下狠心用一套明确的分类:基础展示组件放components/ui,业务组合组件放components/shared,页面独有组件就近放在app对应的路由分组里。项目大了之后,这种就近组织的习惯会让你少找很多文件。

另一个是写好种子数据,尤其是在项目刚起步时。Prisma的seed脚本不是可选项,我从项目第一天就认真写seed数据,因为前后端联调的时候,如果数据库空空如也,很多列表、空态、边界分支都测不出来。seed文件里准备好几组不同状态的用户和业务数据,配合tRPC的测试链路,整个开发过程会顺畅很多。

还有一个小习惯是在提交代码前固定预览Prisma Studio。它可以像查Excel一样看数据库的每张表当前长什么样。开发过程中数据状态乱了,打开Studio几条记录拽一拽就能恢复现场,比写SQL调bug快得多。

t3code这套组合走到今天,给我的感觉不只是一个模板、一套配置,更像是一整套工程方法论。它把那些经过反复验证过的优秀实践固化成了一开始就能用的形态,刚接触的人不需要把Next.js官方文档翻完就能跑起来一个全栈应用,有经验的团队则能在此基础上向内深挖各自业务的独特性,不用担心基础链路出幺蛾子。

如果你正在纠结下一个全栈项目从哪入手,或者已经被REST的接口文档和手动维护类型折磨得心累,不妨拿t3code试一次。第一次跑通tRPC调用的时候,前端连着后端那张类型表一格都不差的顺滑感,是值得体验一次的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 23:21:48

ICM拓扑优化与均匀化实践:轻量化设计参数调优与多尺度校核

简介&#xff1a;面向结构拓扑优化研究&#xff0c;ICM 方法与均匀化思想的结合常依赖过滤函数来衔接重量与刚度。压缩包以幂函数形式过滤函数为实例&#xff0c;从 ICM 方法出发&#xff0c;结合均匀化思想&#xff0c;利用最小二乘法确定重量过滤函数及对应的刚度过滤函数&am…

作者头像 李华
网站建设 2026/10/28 23:21:07

基于Spring Boot的社区医疗预约系统设计与实现

1. 项目背景与需求分析社区医疗服务作为基层医疗的重要组成部分&#xff0c;其信息化建设水平直接影响着居民就医体验和医疗资源利用效率。传统的社区医疗预约管理普遍存在以下痛点&#xff1a;预约方式单一&#xff1a;主要依赖电话或现场预约&#xff0c;高峰期线路拥堵资源调…

作者头像 李华
网站建设 2026/10/28 23:18:00

正态分布在计算机视觉中的核心应用与实践

1. 正态分布在计算机视觉中的核心地位计算机视觉系统每天要处理数以百万计的像素数据&#xff0c;这些数据看似杂乱无章&#xff0c;实则隐藏着严格的统计规律。当我第一次在工业级人脸识别项目中应用正态分布理论时&#xff0c;准确率直接提升了23个百分点——这让我深刻理解了…

作者头像 李华
网站建设 2026/10/28 23:17:59

Vue+Flask构建知识图谱可视化系统实战

简介&#xff1a;本资源是一个基于Vue与Flask实现的知识图谱可视化系统完整工程包&#xff0c;面向前端、后端及全栈开发者&#xff0c;尤其适合学习知识图谱交互展示、前后端分离架构设计与轻量级Web应用开发的中高级实践者。项目通过Vue构建动态响应式前端界面&#xff08;含…

作者头像 李华
网站建设 2026/10/28 23:17:39

基于YOLOv8的气压表智能读数系统开发与实践

1. 项目背景与核心价值气压表作为工业生产和实验室环境中的关键监测设备&#xff0c;其读数准确性直接影响工艺流程控制和实验数据可靠性。传统人工读数方式存在三个显著痛点&#xff1a;一是不同操作者的视角差异可能导致2%-5%的读数偏差&#xff1b;二是在高频次监测场景下&a…

作者头像 李华
网站建设 2026/10/28 23:13:11

异构系统编队控制:动态响应与通信协议统一方案

1. 异构系统编队控制的核心挑战与破局思路在无人机集群、智能物流车队等实际应用场景中&#xff0c;我们常常需要让不同性能、不同结构的设备协同工作。去年我在一个农业无人机项目中就遇到过这种情况&#xff1a;团队里有5台大疆M300RTK、3台极飞P80和8台自制的小型植保机&…

作者头像 李华