☰
T3 Stack 全栈开发实战:从零构建代码片段管理平台 t3code
2026/10/14 14:46:32 网站建设 项目流程

上周刚把一个内部工具项目「t3code」从前端到后端完整落地,它本质上是一个基于T3 Stack的代码片段管理平台。T3 这个词这两年在全栈圈几乎成了“TypeScript 全家桶”的代名词,很多人知道 Next.js、TypeScript、Tailwind CSS、tRPC 这几样东西组合在一起很酷,但真要从零把它搭起来、跑通、上线,踩过的坑只有做过的人才知道。

我写这篇东西不是来讲概念 PPT 的,是想把 t3code 从立项到部署的完整过程复盘一遍,把选型时的纠结、写代码时的细节、部署时的坑全部摊开说。如果你正准备用 T3 栈做自己的全栈项目,或者在犹豫要不要从传统 REST API 切到 tRPC,这篇应该能帮你少走一个星期弯路。

1. T3 技术栈的选型逻辑与整体设计

1.1 为什么是 Next.js + TypeScript + Tailwind + tRPC

先说说选型这件事。t3code 这个项目最开始的需求很朴素:团队里几个后端同事经常把好用的 SQL 片段、Shell 脚本、正则表达式随手发到群里,过两周想找就找不到了。做一个轻量的代码片段管理工具,让每个人都能存、能搜、能分类,这就够了。

需求虽然简单,但技术选型上我还是认真纠结了一轮。备选方案有三条路:传统方案是 React + Express + MongoDB,经典但前后端类型割裂;第二条路是 Next.js 全套但不引入 tRPC,用 REST API 自己封装类型;第三条就是 T3 标准组合,Next.js 管页面、tRPC 管接口、Prisma 管数据库、Tailwind 管样式。

最后选 T3 的理由很直接:类型安全带来的心智负担最小。传统方案里后端定义接口、前端手写类型、联调时对字段名,这些零碎工作是项目大头,而 tRPC 让前后端共享类型定义,后端函数签名一变,前端编辑器立刻报错。

T3 Stack 官方推荐的组合是 Next.js、TypeScript、Tailwind CSS、tRPC、Prisma、NextAuth.js,但这个组合“重”在概念多、“轻”在实际编码。项目脚手架 create-t3-app 能把整套东西在几分钟内初始化好,比手动装配省事太多。

提示:T3 栈不等于必须全用,create-t3-app 初始化时也可以只勾选 Next.js + TypeScript + Tailwind,等需要时再补 tRPC 和 Prisma。我建议初期全选,因为后面再加反而费劲。

1.2 t3code 的架构规划与目录设计

接下来是整体架构。t3code 采用典型的分层结构:页面层(Next.js App Router)、接口层(tRPC Router)、数据层(Prisma Schema),三层通过 TypeScript 类型紧密咬合。

目录结构是我从 create-t3-app 默认模板调整来的:

t3code/ ├── prisma/ │ └── schema.prisma ├── src/ │ ├── app/ │ │ ├── api/ │ │ ├── layout.tsx │ │ └── page.tsx │ ├── server/ │ │ ├── api/ │ │ │ ├── routers/ │ │ │ ├── root.ts │ │ │ └── trpc.ts │ │ ├── auth.ts │ │ └── db.ts │ ├── styles/ │ │ └── globals.css │ ├── trpc/ │ │ ├── client.ts │ │ ├── react.tsx │ │ └── server.ts └── package.json

这个结构有几个值得注意的点。src/server下面放所有服务端逻辑,src/app只负责页面渲染和组件组织,这样职责边界很清晰。tRPC 路由集中在routers目录里,每个业务域建一个文件,比如snippetRouter、tagRouter、userRouter,最后在root.ts里统一合并。

t3code 的核心业务域我划分为三块:代码片段(Snippet)、标签(Tag)、用户(User)。每个域都是一条独立的 tRPC 路由,前端页面通过react.queryClient请求对应的 procedure,不需要写一层额外的 service 层,因为 tRPC 本身就是服务端入口。

这里有个经验:初始项目别设计太复杂的微服务结构。t3code 起初我就三个 router,后续如果要加评论、点赞,再新增 router 文件和服务端方法就行,T3 的架构扩展成本非常低。

2. 从零初始化:环境准备与脚手架搭建

2.1 环境依赖清单

T3 全栈项目对本地环境要求并不高,核心就三样:Node.js、包管理器、数据库。我的实际版本供参考:

依赖版本建议说明
Node.js18.x 或 20.x LTS必须 18 以上,App Router 和 tRPC 都对 Node 版本有要求
pnpm8.x 以上也可以用 npm/yarn,但 T3 配 pnpm 的依赖树更干净
PostgreSQL14 以上T3 默认数据库,也可以用 SQLite 或 MySQL
Prisma CLI跟随脚手架数据库迁移和管理工具

Node 18 以下不建议尝试,Next.js 14 的 App Router 和 tRPC 10 都需要较新的 JavaScript 特性。数据库方面,本地开发我推荐用 Docker 起一个 PostgreSQL:

docker run --name t3code-db -e POSTGRES_USER=t3code \ -e POSTGRES_PASSWORD=t3code123 \ -e POSTGRES_DB=t3code -p 5432:5432 -d postgres:15

这个命令会创建一个干净的 PostgreSQL 15 容器,端口映射到宿主机 5432,t3code-db是容器名,方便后续管理。

2.2 create-t3-app 初始化步骤

初始化 t3code 我用的是官方脚手架,一条命令就能把整套项目结构拉起来:

npx create-t3-app@latest t3code

执行后会进入交互式选择界面,我勾选的项目如下:

  • Next.js(开启 App Router)
  • TypeScript
  • Tailwind CSS
  • tRPC
  • Prisma
  • NextAuth.js
  • 包管理器选 pnpm,初始化完成后自动安装依赖

整个过程大概三分钟。这里有一个非常关键的决策点:是否勾选 NextAuth.js。如果你确定项目需要用户登录、权限区分,一定要在初始化时勾上,因为 create-t3-app 会把认证相关的初始化代码、数据库模型、环境变量全部生成好,后面手动接 NextAuth 相当折磨。

启动项目:

cd t3code pnpm dev

浏览器打开http://localhost:3000,如果能看到默认的 T3 欢迎页,说明整个基础链路已经通了。

注意:默认模板里的.env文件在安装时会生成,里面包含了数据库连接串DATABASE_URL和 NextAuth 的NEXTAUTH_SECRET。后面改动数据库配置时,记得重新加载环境变量。

2.3 初始项目遇到的两个小坑

初始化过程虽然快,但周围不少朋友第一次跑都会卡在两个位置。第一个是依赖安装时报版本冲突,这通常是因为本地 Node 版本过低,换到 Node 18 LTS 就解决了。第二个是启动后数据库表不存在导致页面报错,原因很简单:还没有跑 Prisma migration。

npx prisma db push npx prisma generate

这两条命令一个负责把 schema 同步到数据库、创建所有表,一个负责生成 Prisma Client 类型。跑完之后默认页面就不会再报 P2021 之类的表不存在错误了。

3. 数据模型设计:Prisma Schema 从构思到落地

3.1 t3code 核心表结构设计

t3code 的表设计经历了两次迭代。第一次很粗糙,一张表装所有字段,tags 用逗号分隔存字符串,后来发现查询和统计完全没法做,才改成了规范的三表模型。

最终 schema 设计如下:

model User { id String @id @default(cuid()) name String? email String? @unique emailVerified DateTime? image String? snippets Snippet[] accounts Account[] sessions Session[] } model Snippet { id String @id @default(cuid()) title String content String @db.Text language String @default("plaintext") userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) tags SnippetTag[] views Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Tag { id Int @id @default(autoincrement()) name String @unique snippets SnippetTag[] } model SnippetTag { snippetId String tagId Int snippet Snippet @relation(fields: [snippetId], references: [id], onDelete: Cascade) tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) @@id([snippetId, tagId]) }

这个设计有几个关键考量。第一,Snippet和Tag是多对多关系,用中间表SnippetTag关联,避免用逗号分隔这种反模式。中间表就用复合主键,不需要额外 id 字段,Prisma 在创建关系表时原生支持这种方式。

第二,userId字段直接引用User表,onDelete: Cascade保证用户删除时他的代码片段也一并清掉,不会留下孤儿数据。

第三,views字段做计数用,默认 0。一开始我想用单独的表记录浏览量,后来觉得对于代码片段这种读多写少的场景,直接在主表上加个整数更干净,也方便列表页排序。

3.2 迁移流程与 seed 数据

设计好 schema 后正式的迁移流程是:

npx prisma migrate dev --name init

这条命令会在prisma/migrations目录下生成一份 SQL 迁移文件,把三个核心表创建出来。dev环境用migrate dev,生产环境用migrate deploy,这是 Prisma 的标准工作流。

为了方便开发调试,我还写了一个 seed 脚本,初始化时插入几个默认标签和示例片段:

npx prisma db seed

package.json 里需要配置:

"prisma": { "seed": "tsx --env-file=.env prisma/seed.ts" }

这里有个容易踩的坑:Prisma 6 之后url参数可以用环境变量替代,但 seed 脚本里如果直接读process.env.DATABASE_URL,在 tsx 执行时不一定能自动加载.env文件,我加了--env-file=.env参数才解决。

4. 业务核心实现:tRPC 路由与页面渲染

4.1 创建代码片段的 tRPC 路由

tRPC 是 t3code 的核心链路。它在服务端定义 procedure,前端直接调用函数,不需要定义 REST 端点。这么说可能有点抽象,换个角度:传统 REST API 像打电话,你需要先拨号(写 URL),然后等着对方给你解释(解析 JSON);tRPC 更像直接走进隔壁房间喊一嗓子,类型安全让后端函数的参数和返回值在前端代码里就跟本地函数一样。

t3code 的 snippet 路由长这样:

import { z } from "zod"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "@/server/api/trpc"; export const snippetRouter = createTRPCRouter({ create: protectedProcedure .input(z.object({ title: z.string().min(1).max(200), content: z.string().min(1), language: z.string().max(50), tagNames: z.array(z.string()).max(10), })) .mutation(async ({ ctx, input }) => { const snippet = await ctx.db.snippet.create({ data: { title: input.title, content: input.content, language: input.language, userId: ctx.session.user.id, tags: { create: input.tagNames.map(name => ({ tag: { connectOrCreate: { where: { name }, create: { name }, }, }, })), }, }, }); return snippet; }), getAll: publicProcedure.query(async ({ ctx }) => { return ctx.db.snippet.findMany({ orderBy: { createdAt: "desc" }, include: { tags: { include: { tag: true } } }, }); }), getByLanguage: publicProcedure .input(z.object({ language: z.string().min(1) })) .query(async ({ ctx, input }) => { return ctx.db.snippet.findMany({ where: { language: input.language }, include: { tags: { include: { tag: true } } }, }); }), });

拆解一下这里发生了什么事。protectedProcedure表示这个操作需要登录才能调用,没有 session 的用户会被 tRPC 直接拒绝;publicProcedure则允许匿名访问查询列表。

zod负责输入校验。标题最长 200 字、内容不能为空、语言不能超过 50 字符、标签最多 10 个,这些限制写在这里,前端无需重复写一套校验。像create这种复杂输入,zod schema 既做运行时校验,又能在类型层面推导出input的 TS 类型,tRPC 的input字段信息直接传到前端,写客户端代码时编辑器会提示完整字段结构。

这里最漂亮的细节是connectOrCreate:保存代码片段时如果传了一个新标签名,它会自动在 Tag 表创建新记录而不是报错;如果标签已存在,就只建立关联,不重复创建。

4.2 页面调用 tRPC 的正确姿势

前端页面调用 tRPC 有两种方式,t3code 里都用到了。客户端组件用api.snippet.getAll.useQuery(),服务端组件用api.snippet.getAll()直接调用。

涉及 App Router 有个经典问题:服务端组件里无法使用 hooks,所以 tRPC 提供了server.ts封装一个服务端调用器。t3code 的片段详情页就把数据用服务端查询直接传给组件:

import { api } from "@/trpc/server"; export default async function SnippetListPage() { const snippets = await api.snippet.getAll(); return <SnippetGrid snippets={snippets} />; }

服务端直接拿数据的体验真的很好,页面首屏渲染速度快,没有 loading 闪烁。但有个前提:只有 publicProcedure 才能这样直接在服务端叫,protectedProcedure 只能在服务端拿到 session 后用createCaller调用,否则拿不到用户上下文。

客户端组件则用 tRPC 的 React Query 集成:

"use client"; import { api } from "@/trpc/react"; export function SnippetForm() { const utils = api.useUtils(); const createSnippet = api.snippet.create.useMutation({ onSuccess: () => { utils.snippet.getAll.invalidate(); }, }); const handleSubmit = (data: SnippetFormData) => { createSnippet.mutate(data); }; return <form onSubmit={handleSubmit}>{/* ... */}</form>; }

useUtils().snippet.getAll.invalidate()这一行是 T3 生态的精髓,因为 React Query 的缓存机制,新增数据后让查询失效,React Query 会自动重新请求列表,整个过程不需要手动 setState 或者刷新页面,也不用写任何全局状态管理。

4.3 认证与权限:NextAuth.js 的接入细节

t3code 的认证采用 NextAuth.js,GitHub OAuth 作为登录方式。配置在src/server/auth.ts中:

import NextAuth from "next-auth"; import GitHub from "next-auth/providers/github"; import { PrismaAdapter } from "@auth/prisma-adapter"; import { db } from "@/server/db"; export const { handlers, signIn, signOut, auth } = NextAuth({ adapter: PrismaAdapter(db), providers: [ GitHub({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), ], session: { strategy: "jwt" }, });

GitHub OAuth 的配置是:在 GitHub 账号的 Developer settings 里创建一个 OAuth App,回调地址填http://localhost:3000/api/auth/callback/github,然后把生成的 Client ID 和 Client Secret 放到.env里。

这里的核心概念是 PrismaAdapter 自动处理用户表的创建和关联。用户第一次用 GitHub 登录时,系统自动在 User 表里插入一条用户记录,并把 GitHub 返回的头像、昵称等字段映射到对应列。

权限控制的部分在 tRPC 的protectedProcedure里实现。它的实现逻辑是在 procedure 的resolve执行前检查ctx.session是否存在,如果 session 为空就抛出 UNAUTHORIZED_REQUIRED 错误。这样我在create等写操作上直接用protectedProcedure,就能保证只有登录用户才能创建片段,而查询列表用publicProcedure,游客也能看。

提示:NextAuth.js 的 session 如果在 Prisma 数据库里存 session 表,需要配置session.strategy = "database";用 JWT 策略则可以不建 session 表。我选择 JWT,省一张表。

4.4 Tailwind CSS 在 t3code 里的实际使用

Tailwind 在这个项目里主要用来快速搭界面,没有做太复杂的主题定制。T3 模板默认带了 Tailwind 配置,globals.css里可以直接用 CSS 变量定义主题色。

t3code 的列表页卡片用了 Tailwind 的几个核心类:

<div className="rounded-xl border border-gray-200 bg-white p-6 shadow-sm transition hover:shadow-md"> <h2 className="text-lg font-semibold text-gray-900">{snippet.title}</h2> <p className="mt-2 text-sm text-gray-500">{snippet.description}</p> <div className="mt-4 flex gap-2"> {snippet.tags.map(({ tag }) => ( <span key={tag.id} className="rounded-full bg-blue-50 px-3 py-1 text-xs font-medium text-blue-600"> {tag.name} </span> ))} </div> </div>

重构下页面的时候有个感受:Tailwind 的原子类初期写起来像回到了手写 CSS 的时代,但熟悉了以后真的爽,不用来回切文件改样式,改颜色、改间距都在 JSX 里直接操作,视觉重构起来效率极高。如果你是从传统 CSS 切过来的,前两三天会很不适应,但第五天就会真香。

5. 数据与图片:上传存储方案的取舍

5.1 图片和文件存哪里

t3code 一开始想支持给代码片段上传示例图片,但马上意识到自己搭文件服务完全没必要。直接接云存储是最快路径,我使用的是兼容 S3 协议的对象存储服务。

相关配置放在.env:

S3_ACCESS_KEY_ID=xxx S3_SECRET_ACCESS_KEY=xxx S3_BUCKET_NAME=t3code-assets S3_REGION=auto S3_ENDPOINT=https://your-endpoint.example.com UPLOAD_DIR=uploads

上传接口也是用 tRPC mutation 实现,普普通通一个uploadprocedure。之所以不用 REST API,是因为 tRPC 的输入输出校验可以和 zod 统一,上传文件时校验文件类型、大小,直接复用同一套流程。

5.2 直传还是后端中转

图片上传方案有两种:后端中转和前端直传。后端中转是指前端先把文件发给 Next.js 服务端,服务端再转存到对象存储;前端直传是浏览器直接和对象存储交互。

t3code 最终用了后端中转。原因很简单:直传需要向客户端暴露 AccessKey,或者在服务端临时签名,签名逻辑反而比较复杂。后端中转虽然多一次转发,但整个链路统一走 tRPC,权限控制和文件校验都在服务端完成,短期内更稳。

要注意的是 Next.js App Router 默认的 body 大小限制是 4MB,上传大文件时会报 413。如果你要支持更大文件,需要在路由处理函数里手动调整限额,不建议超过 10MB 的单文件阈值,代码片段配图一般几 KB 就够了,太大纯粹是滥用存储。

6. 部署上线:从本地到生产环境的完整流程

6.1 部署平台选择

t3code 部署我选的是 Vercel,因为 Next.js 是 Vercel 的亲儿子,部署成本几乎为零。但如果你不想绑定单一平台,也可以部署到自己的云服务器上,Docker 套娃的方式跑 Next.js 服务和 Postgres 数据库。

Vercel 部署流程相当无脑:

  1. 把代码推送到 GitHub 仓库
  2. 在 Vercel 控制台 Import Project 选择 t3code
  3. 配置环境变量(DATABASE_URL、NEXTAUTH_SECRET、NEXTAUTH_URL、GitHub OAuth 相关)
  4. 点击 Deploy

数据库如果用本地 Postgres 容器,生产环境就存在问题。开发环境用的 PostgreSQL 15 容器不能直接暴露到公网给 Vercel 连,需要迁移到托管的数据库服务,或者用 VPS 上的数据库开启公网访问并配置白名单。

注意:Vercel 的 Serverless 函数是无状态的,每个请求可能跑在不同的实例上。如果你的应用依赖本地磁盘存文件,在 Vercel 上是存不下来的,必须用外部存储服务。这也是我为什么坚持把图片放到对象存储。

6.2 环境变量与迁移脚本

生产环境的环境变量和本地有一处关键差异:NEXTAUTH_URL必须改成线上域名,不能再用 localhost。GitHub OAuth 那边的回调地址也要同步改成https://your-domain.com/api/auth/callback/github。

数据库结构部署时的命令是:

npx prisma migrate deploy

这条命令和migrate dev不同,它不会创建新的迁移文件,只把已有的 migration 文件应用到生产数据库。这个区别很重要:千万不要在服务器上跑migrate dev,那是开发环境用来生成迁移文件的。

部署后我还写了一个简单的脚本做健康检查,定时访问https://your-domain.com/api/health,接口里查数据库连接状态和依赖服务,有问题会发通知到团队群。

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

7.1 问题速查表

以下是我在 t3code 整个开发周期里遇到次数最多的问题,直接整理成表格,方便遇到类似情况快速定位:

问题现象根本原因解决方案
页面上报UNAUTHORIZED_REQUIREDtRPC procedure 用错类型,写操作用了 publicProcedure改为 protectedProcedure,检查调用处是否已登录
P2021: Table does not exist迁移未执行或执行过旧版本运行prisma migrate dev或migrate deploy
PrismaClientInitializationErrorDATABASE_URL 未正确加载检查 .env 文件和 Prisma schema 的 datasource 配置
NextAuth 登录后 session 丢失NEXTAUTH_SECRET 未设置或部署环境变量不一致生产环境设置强随机 secret,并使用统一环境变量
服务端组件请求 tRPC 失败在服务端组件里用了需要客户端 hooks 的调用方式使用@/trpc/server的 API 调用
上传大文件 413Next.js bodyParser 默认限制调整路由配置或改用外部存储直传
React Query 缓存不更新忘记 invalidate 查询mutation 成功后调用utils.snippet.getAll.invalidate()
esbuild 解析 Prisma 报错tsx 直接运行 seed 脚本时环境变量缺失用tsx --env-file=.env运行

7.2 独家避坑技巧三则

第一条是 tRPC Router 的组织方式。t3code 最初所有接口都写在root.ts一个文件里,到第 20 来个 procedure 时文件已经乱得没法看。后来严格拆成routers目录下的单文件路由,每个文件对应一个业务域,改起来神清气爽。T3 官方模板虽然示范了这种做法,但很多初学者会忽略,建议从一开始就按业务域拆分。

第二条是数据库连接池的配置。Vercel 的 Serverless 环境里,Prisma Client 每次冷启动都会建立新的数据库连接,如果不做热连接复用,连接数会爆炸。T3 模板默认的globalForPrisma方案在全局对象上缓存了一个 PrismaClient 实例,这是对的做法,但要注意如果把应用部署到多个地域,数据库连接仍然会因为跨地域延迟变慢,此时需要改用连接池服务。

第三条是 Next.js 服务端组件和 tRPC 混用的坑。服务端组件里调 tRPC,走的是直接执行函数的方式,不经过 HTTP,你需要确认服务端组件是在同一个 Next.js 应用内运行,否则拿不到 tRPC context。我在首页同时用了服务端查询和客户端 mutation,因为 React Query 缓存了数据,服务端渲染完成瞬间客户端就能接管,体验无缝衔接。

8. 性能优化与后续扩展方向

8.1 首屏速度与缓存优化

t3code 列表页在数据超过 100 条后出现了一个小瓶颈:每次打开页面都要全量查 snippets 表。优化方案很直接,分页 + 缓存:

getPage: publicProcedure .input(z.object({ page: z.number().int().min(1).default(1), pageSize: z.number().int().min(1).max(50).default(10), })) .query(async ({ ctx, input }) => { const [items, total] = await Promise.all([ ctx.db.snippet.findMany({ skip: (input.page - 1) * input.pageSize, take: input.pageSize, orderBy: { createdAt: "desc" }, include: { tags: { include: { tag: true } } }, }), ctx.db.snippet.count(), ]); return { items, total, page: input.page, pageSize: input.pageSize }; }),

数据库一般会在翻页到第 3、4 页之后才感觉到延迟。对代码片段这种低吞吐业务,加一层 Redis 缓存有点杀鸡用牛刀,所以 t3code 暂时没引入 Redis,等数据量真的上来再加不迟。

8.2 可扩展的功能方向

后续你想让 t3code 长得更大,可以考虑加这几个功能:收藏夹(复制 SnippetTag 模式建一个 Favorite 中间表)、代码运行(接一个沙箱执行服务,返回 stdout/stderr)、全文搜索(上 Postgres 的 tsvector 或者接搜索引擎服务)。

不过我个人建议:一个内部工具项目,做到能用、好用、稳定就够了。很多人做技术选型时会忍不住加功能,t3code 我忍住了,没做评论、没做点赞、没做实时代码协作,专注把代码片段的管理体验做顺。事实证明这个决定是对的,从部署上线到现在,团队里每天都在用,这就比什么都重要。

如果真要继续扩展,优先级我会这样排:全文搜索排第一,因为代码片段一多,按语言、按标签筛选还能用,但想看“之前写过的那个获取 JWT 的 Go 函数”,没有全文搜索就只能干瞪眼。收藏夹排第二,团队协作时分享常用片段很需要。其他花哨功能都排后面,先让核心路径跑顺。

写在最后

t3code 这个项目从开始到上线总共花了两周,大部分时间花在理解 tRPC 的工作方式和 NextAuth 的会话机制上。跑通之后回头再看,T3 技术栈真正厉害的地方不是某个单一框架,而是整条链路的 TypeScript 类型贯通:数据库模型、服务端路由、前端调用、自动补全,全部由编译器兜底,运行时错误大幅减少。

如果你也在考虑用 T3 栈做自己的项目,我的建议是:别犹豫太多,直接拿 create-t3-app 开一个项目,把 CRUD 做出来,把部署跑通,感受一下全栈类型安全带来的效率提升。等你踩完几个坑、把流程理顺,大概率就回不去以前那种“后端定义接口、前端对着文档猜类型”的开发模式了。

最后再分享一个我近期摸索出来的小技巧:在服务端组件里直接调 tRPC 的 procedure 很顺手,但千万不要在客户端组件里又调用一遍同样路径的查询,这样会造成数据重复请求。正确的做法是高频变化的数据用客户端查询配合 React Query 的缓存,低频不变的数据用服务端查询,两套机制各管各的,性能自然就上来了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询