资讯动态

T3 Stack实战:用Next.js与TypeScript构建类型安全的全栈应用

发布时间:2026/10/13 17:36:58 来源:尧图企业网站定制
t3code 是我这一个月里真正跑通全流程的个人项目一个面向开发者的代码片段管理工具。项目本身不算复杂但后端要处理账号、权限、多对多标签、搜索过滤还要保证 API 与前端联动时不出类型错误。我最终把整个项目压在 T3 Stack 上——Next.js、TypeScript、tRPC、Prisma、Tailwind、NextAuth 的组合——一方面想验证这套全栈方案在中小型真实产品里的效果另一方面也是因为实在受够了前后端各写一份类型定义的日子。这篇文章会从选型逻辑讲到数据建模再到 tRPC 和认证的实际接线最后落到 Vercel 部署时碰到的几个典型问题。适合正在用 Next.js 做全栈应用、又想知道 T3 Stack 到底怎么落地的开发者也适合只是好奇“类型安全的全栈到底是什么体验”的同学。1. 为什么我会把新项目押在 T3 Stack 上1.1 t3code 到底要做什么从需求反推选型t3code 最初的定位非常明确一个类似“开发者自己的代码片段收藏夹”的网站。用户注册登录后可以创建代码片段填写标题、描述、代码内容、编程语言和可见性可以把片段公开出来让首页展示可以通过语言标签和关键词搜索别人的公开片段作者本人还能编辑和删除自己的片段。这个需求单拎出来看并不复杂但拆开以后有两个容易被低估的点。第一所有资源和操作几乎都和“用户身份”绑定凡是涉及“我的片段”“编辑我的内容”“只有我能删”就必须在全链路做权限控制。第二业务中有大量“前端拿一个对象、改两个字段、传回后端”的常规操作前端需要的字段结构和后端返回的结构天然高度一致。如果按传统方案做我至少要在路由层定义十几个 API 接口再在前端写对应的请求函数和类型定义改一次字段就要同步两处烦且容易漏。这时候 T3 Stack 的价值就出来了。它不是一个框架而是一套围绕 Next.js 组织的全栈约定Next.js 负责页面渲染和路由tRPC 负责前后端 API 的类型安全传输Prisma 负责数据库建模访问Tailwind 负责样式NextAuth 负责认证TypeScript 把整条链路串起来。对 t3code 这种量级的产品来说它几乎把“样板代码”这个老问题抹掉了。1.2 T3 Stack 一边解决样板代码一边解决类型断层之前做全栈项目我的典型路径是后端用 Express 或 Node 原生框架定义路由返回 JSON前端根据接口文档手写类型、写请求函数、处理 loading 和 error。这种做法不是不能用而是“类型断层”很痛苦——数据库里的字段、API 返回的字段、前端使用的字段三套结构往往是靠人肉同步的。后端改了字段名前端编译期不会报错只有运行时接口报错才发现而那时候往往已经部署上线了。T3 Stack 的思路是把 TypeScript 当成一种贯穿项目的“协议”而不是只在某个目录里开个 tsconfig 就算完。Prisma Schema 是数据库层的类型源头生成出的 PrismaClient 类型会被 tRPC 的 router 直接引用router 的输入输出类型又会自动推导到前端 React Query 的 hooks 上。也就是说我从数据库模型到页面组件之间几乎不需要手写任何“DTO”或“接口响应类型”。这一点对 t3code 的日常开发影响很大。比如我改了一个 Snippet 模型的字段Prisma 生成新类型后tRPC 层如果引用这个字段类型立刻生效前端调用listSnippets这个 query 的时候返回数据里自动就有新字段编译器会告诉我页面哪里缺了、哪里类型对不上。开发节奏变成了“改模型—迁移—编译—刷新”而不是“改模型—写接口—写文档—改前端—联调”。1.3 为什么不是“前后端分离”或“纯 Serverless API”也有人问我直接用 Next.js 的 Route Handlers 写 API不也挺干净确实干净但有几个问题Route Handlers 返回的内容需要自己定义类型输入参数要自己解析校验前端调用时依然需要手动写请求封装。t3code 里有“创建片段”“更新片段”“搜索片段”“切换收藏”这一批常规操作如果每个都自己写一遍fetch加zod校验项目迭代速度会明显慢下来。前后端彻底分离的方案React 单独部署后端用 Express/Spring/FastAPI对 t3code 来说更是过度设计。一个主要面向个人和小团队的工具没有必要维护两套部署流程、两套身份体系。T3 Stack 把页面、API、数据库访问收在一个 Next.js 应用里部署时只关心一个应用实例开发时也不必开多个 dev server对单人维护的项目非常友好。这套方案也有边界如果团队里前后端分离是硬约束或者以后要开放公共 API 给第三方tRPC 这种内部 RPC 的体验确实不如传统 REST 直观。所以我的结论是t3code 这类“账号体系 自身业务 页面 UI”的应用适合 T3 Stack但如果你要做开放平台性质的产品还是倾向于传统 REST 或多语言网关。2. create-t3-app 的配置决策哪些默认项该留哪些必须改2.1 初始化命令与勾选项初始化一个 T3 Stack 项目非常快核心命令只有一条npm create t3-applatest t3code命令跑起来后会进入交互式问答我记得当时是逐项确认是否启用 Tailwind、Prisma、tRPC、NextAuth 以及 E2E 测试。这里我没有纠结除了 E2E 测试选了 No其余全部 Yes。理由也简单t3code 的核心玩法就是数据模型加权限操作Prisma 和 tRPC 是主角NextAuth 直接决定账号体系必须选上Tailwind 对我来说写页面速度明显更快。E2E 测试对这种个人项目暂时有些重我有完善的类型检查和基础 dev 联调就够了后面真需要再补。如果你是第一次跑这个命令我建议不要被“要不要 TypeScript”这种问题吓到T3 Stack 默认就是为 TypeScript 设计的直接接受默认值就好。生成完毕后的目录结构大概是这样的src/ ├── app/ # Next.js App Router 页面与 API 入口 ├── server/ │ ├── api/routers/ # tRPC router 定义 │ ├── auth.ts # NextAuth 配置 │ └── db.ts # PrismaClient 单例 ├── trpc/ # tRPC server 与 client 封装 └── env.js # 环境变量安全解析2.2 生成的骨架里值得先读的四个文件生成之后不要急着写业务代码先把几个关键文件读一遍否则后面会遇到“为什么这里报错”的困惑。第一个是env.jsT3 模板默认用 Zod 对运行环境变量做运行时校验。DATABASE_URL如果没配好任何依赖数据库的代码都会在启动阶段直接抛初始化错误而不是等查询时才暴露。第二个是server/db.ts它把 PrismaClient 做成全局单例避免开发环境热更新时产生过多数据库连接。第三个是server/auth.ts这是 NextAuth 的装配点模板已经帮你接好了 Prisma Adapter 和 session 类型。第四个是trpc/server.ts和trpc/react.tsx前者负责创建 tRPC 实例后者把这些 RPC 转换成 React Query 的 hooks。这四个文件我可以花半天时间读一遍绝对能省下后面好几天排查问题的功夫。2.3 环境变量不是随便填的Zod 校验与 DATABASE_URLT3 模板的核心环境变量是DATABASE_URL。我初始化项目后打开env.js发现模板默认用的是 SQLite连接串长这样DATABASE_URLfile:./db.sqlite这个值在本地开发阶段完全够用因为我第一时间只是想先把页面和 tRPC 链路跑通。但等我要在 t3code 里做用户系统时就对 SQLite 有些犹豫了。原因很简单SQLite 对并发写入的控制太弱虽然对个人项目的访问量来说不至于崩但多用户同时写数据时很容易碰到database is locked报错而且线上环境文件型数据库很难管理。所以在进入生产准备之前我直接把本地开发也切到了 PostgreSQL使用同一套配置避免“本地正常、线上出问题”的落差。如果你暂时不想折腾数据库先用模板的 SQLite 跑通也是合理的。但千万记得一点如果用 SQLite别把dev.db文件提交到 Git 仓库也不要去 Vercel 部署这个状态的应用。Serverless 环境根本没有可写文件系统SQLite 文件会变成只读很多新接触这块的人会在这里怀疑人生。2.4 开发阶段的 SQLite 与生产数据库的选择t3code 最后使用的数据库是 PostgreSQL选择逻辑很简单它满足账号系统需要的强一致性、事务能力和并发写入能力而且 Vercel 的 Postgres 集成或 Neon 这类托管服务都能免运维。Prisma 对 PostgreSQL 的支持也最完整比如后面我要用的mode: insensitive搜索过滤、多对多标签表在 PG 上都跑得很顺。执行本地迁移时核心命令是npx prisma migrate dev --name initmigrate dev会在开发环境创建迁移文件并同步数据库。注意顺序一定要对先建数据库连接再写 Schema最后跑 migrate。很多人一上来就执行prisma validate报错往往不是模型写错而是DATABASE_URL没生效。T3 的环境变量校验会在启动阶段提示看到报错先检查.env文件的位置——T3 模板把.env放在项目根目录别放到src里否则 Next.js 读不到。3. t3code 的核心数据模型Prisma Schema 从草稿到定稿3.1 实体关系设计片段、标签、用户、收藏我最初画数据模型时脑子里只有“用户”和“片段”两张表后面边写边补最终定稿是四个核心模型User、Snippet、Tag、Favorite。User由 NextAuth 的 Prisma Adapter 预设结构承接模板会生成对应的accounts、sessions关系这些字段不要随便删。Snippet是主业务表包含标题、代码、语言、可见性、作者 ID、创建时间和更新时间。Tag是标签表一个片段可以有多个标签一个标签也可以属于多个片段所以用多对多关系。Favorite是收藏关系表一个用户收藏多个片段一个片段可以被多个用户收藏它和标签的区别在于收藏是“带用户属性的操作”需要记录是谁收藏的。整体关系就是这样用户对片段是一对多片段对标签是多对多用户对片段通过收藏表实现多对多。写作时这个结构看似简单但它能覆盖 t3code 的所有核心需求个人片段列表、标签聚合、搜索过滤、收藏夹。3.2 Schema 落地与关系写法Prisma Schema 的实际落地可以按模型拆开看。Snippet模型大致长这样model Snippet { id String id default(cuid()) title String description String? code String language String default(plaintext) visibility Visibility default(PRIVATE) createdAt DateTime default(now()) updatedAt DateTime updatedAt authorId String author User relation(fields: [authorId], references: [id], onDelete: Cascade) tags Tag[] favorites Favorite[] }字段类型选择上有一个容易被新手上手时忽略的点主键用cuid()而不是自增Int。自增主键在分布式部署和 API 暴露时容易泄露数据量而且 tRPC 客户端拿到的 ID 是字符串路径参数也更好处理。visibility我用 Prisma Enum 而不是字符串是因为这个字段只有PRIVATE和PUBLIC两种合法值用枚举能在数据库层面挡住非法输入。Tag模型则需要关注多对多的隐式关系model Tag { id String id default(cuid()) name String unique snippets Snippet[] }只要两边都声明Tag[]和Snippet[]Prisma 会自动生成一张关联表我不用手写中间模型。这是 Prisma 的省心之处但后面如果要存“这个片段是在哪一天打上该标签的”这类额外信息就必须换成显式中间模型这也是我后期踩到的一个点后面会单独说。Favorite模型我建议不要省略成“User 表里加一个 snippetId 数组”那是关系型数据库的反模式。我最终是这样写的model Favorite { id String id default(cuid()) userId String snippetId String createdAt DateTime default(now()) user User relation(fields: [userId], references: [id], onDelete: Cascade) snippet Snippet relation(fields: [snippetId], references: [id], onDelete: Cascade) unique([userId, snippetId]) }unique([userId, snippetId])保证同一个用户不能重复收藏同一片段这是业务上的硬约束比在业务代码里先查再插要可靠得多。3.3 第一次迁移的流程Schema 写完后第一次迁移我用了prisma migrate dev --name init。这里有个很实用的流程先跑npx prisma format让 Schema 排版统一再跑migrate dev。迁移成功后Prisma 会自动生成新的 Client我可以在前端直接导入.prisma/client类型不需要额外generate。如果你的项目是从模板初始化的模板里server/db.ts大概率已经创建了 PrismaClient 单例。我建议不要在业务代码里再 new 一个 PrismaClient否则开发模式下每次热更新都会新开连接长期运行会把连接池占满。保持全局单例是 T3 模板已经替你优化好的方案直接用就行。3.4 字段设计走完两轮后才发现的问题等 t3code 功能做得差不多时我又回去改了 Schema原因有三个。第一个是description字段最早没加后来发现片段列表页如果没有一句话描述卡片就是一个孤零零的代码块体验很差。这个问题最好在一开始就想清楚但修改也简单加字段后重新迁移即可。第二个是“标签是否大小写敏感”的问题。用户可能会打JavaScript和javascript两个标签如果直接按名字唯一约束它们会被当成两个标签。我的方案是存储时统一toLowerCase()查询时也统一转小写保证名称唯一性。第三个是搜索过滤时的性能问题。早期我直接在页面端把所有片段拉下来再 JS 过滤数据量小的时候没问题但片段一多就非常慢。后来我改成在 tRPC 查询里直接用 Prisma 的contains做数据库层过滤只把必要字段返回给前端响应包小了一个量级。4. tRPC 链路类型安全不是口号是开发节奏本身4.1 tRPC 在 T3 里的接线方式t3code 之所以开发得顺很大程度要归功于 tRPC 的接线体验。模板里已经把 tRPC server 和 client 都配好了我只需要在src/server/api/routers/里写业务 router然后在根 router 里挂载。tRPC 的核心概念有三个router、procedure、context。router 是一组相关操作的集合procedure 是具体的一个查询或变更操作context 是每个请求都能访问的上下文T3 模板默认把 NextAuth 的 session 放进 context。这里我专门说一下 context 的重要性。session 是 tRPC 里做权限判断的基础所有带“当前用户是谁”的查询都要从ctx.session拿身份。模板生成的createTRPCContext里已经调用了getServerSession这意味着前端调用 tRPC 时后端能从每个请求的 cookie 里解析出用户身份我不需要自己写登录态校验也不用在请求头里手动传 token。4.2 snippetRouter从查询到授权的一整套 procedure我把片段相关的所有操作都收进了一个snippetRouter。整个 router 最基本的形态是这样import { createTRPCRouter, protectedProcedure, publicProcedure } from ~/server/api/trpc; import { z } from zod; export const snippetRouter createTRPCRouter({ listPublic: publicProcedure .input( z.object({ language: z.string().optional(), search: z.string().optional(), }) ) .query(async ({ ctx, input }) { return ctx.db.snippet.findMany({ where: { visibility: PUBLIC, ...(input.language ? { language: input.language } : {}), ...(input.search ? { title: { contains: input.search, mode: insensitive }, } : {}), }, include: { author: true, tags: true }, orderBy: { createdAt: desc }, take: 50, }); }), create: protectedProcedure .input( z.object({ title: z.string().min(1).max(120), description: z.string().optional(), code: z.string().min(1), language: z.string().default(plaintext), visibility: z.enum([PRIVATE, PUBLIC]).default(PRIVATE), tagNames: z.array(z.string()).default([]), }) ) .mutation(async ({ ctx, input }) { return ctx.db.snippet.create({ data: { title: input.title, description: input.description, code: input.code, language: input.language, visibility: input.visibility, authorId: ctx.session.user.id, tags: { connectOrCreate: input.tagNames.map((name) ({ where: { name: name.toLowerCase() }, create: { name: name.toLowerCase() }, })), }, }, }); }), });这段代码里我特意用了connectOrCreate来处理标签。新建片段时标签的前端输入是一个字符串数组这些标签可能已经存在也可能不存在connectOrCreate正好能在同一个操作里复用已有标签并创建新标签避免我先查一次数据库再决定用connect还是create。publicProcedure和protectedProcedure的区别也很直观前者任何人都能调用后者如果 session 里没有用户就抛UNAUTHORIZED。这个授权判断只在后端做前端没有任何绕过方式这是所有权限操作必须遵守的红线。4.3 前端调用useQuery/useMutation 与类型推导T3 模板会把 tRPC client 转换成 React Query hooks所以我在页面组件里调用时代码非常像直接调用本地函数const { data, refetch } trpc.snippet.listPublic.useQuery({ language: typescript, search: prisma, }); const createSnippet trpc.snippet.create.useMutation({ onSuccess: () { refetch(); }, });关键在于这里不需要任何类型注解。useQuery的入参会校验我传的参数是否符合 zod 定义data的返回类型会自动匹配listPublic的 query 输出。我在写页面属性时拿到的是一个结构化对象编辑器补全直接就能提示字段名根本不用打开 API 文档。很多人问 tRPC 和 GraphQL 有什么区别拿这段代码就可以比较GraphQL 要在 schema、resolver、client 三方维护tRPC 的类型就是 schema后端 procedure 就是 resolver前端 hooks 就是 client而且三者共用 TypeScript 类型系统模型一变全链路变。对 t3code 这种单一全栈项目这套模式让我的迭代速度快得离谱——改一个 Prisma 字段页面类型提示立刻更新。4.4 我在 tRPC 上踩的三个小坑第一个坑是 mutation 成功后的缓存刷新。t3code 里用户创建片段后首页的公开片段列表必须马上更新。如果只调useMutation不主动刷新页面会一直显示旧数据。我一开始用了useQueryClient().invalidateQueries()传的 key 写错导致列表怎么都不刷。后来改成在 mutation 的onSuccess里直接refetch()或者用模板生成的ctx.utils.snippet.listPublic.refetch()问题立刻解决。简单粗暴但稳定。第二个坑是 zod 校验错误对前端不够友好。默认情况下 zod 抛出的错误信息是一个很长的结构化对象直接展示给用户不现实。当时我加了统一错误处理层把所有校验错误转成“字段 提示语”的格式。虽然 zod 有专门的errorMap配置但放在 tRPC 中间件里统一格式化对我来说最省事。第三个坑是返回数据量失控。listPublic一开始我直接把整个code字段返回到列表页一个片段动辄几十行列表页加载几十个片段就几百 KB。后来我把列表接口的返回值调整为id、title、description、language、visibility和作者摘要只有详情页在单独查完整代码。这个优化对加载速度提升非常明显类似问题在 tRPC 里比 REST 更容易出现因为类型推导太顺畅人容易忘了“查太多了”。5. 认证接入把登录、会话和受保护 API 串起来5.1 为什么 T3 默认推荐 NextAuth账号体系是 t3code 逃不掉的一部分。选认证方案时我几乎没有考虑第三方直接选了 NextAuth。原因有两个一是 T3 模板已经把 NextAuth 和 Prisma Adapter 接好了数据模型天然支持持久化 session二是 NextAuth 的 OAuth 接入成本很低加一个 GitHub 登录只需要申请一对 client id 和 secret不需要自己实现授权码跳转流程。这里顺便说一下 NextAuth 的数据模型要求。它的 Prisma Adapter 希望你有Account、Session、VerificationToken这几张表分别用来存 OAuth 账号、会话和验证令牌。T3 模板默认自带这些模型千万别手贱删掉。我第一次不懂事把Session模型删了想“简化”结果登录后 session 一直查不到折腾半天才明白是数据模型和 Adapter 没对上。5.2 加 GitHub Provider 的三步在 t3code 上加 GitHub 登录非常简单三步搞定。第一步在 GitHub 开发者设置里创建一个 OAuth App回调地址填http://localhost:3000/api/auth/callback/github。第二步把生成的 client id 和 secret 写进.envGITHUB_CLIENT_IDxxxx GITHUB_CLIENT_SECRETxxxx第三步在server/auth.ts的providers数组里加上GitHub。模板的 NextAuth 配置里通常会预留一个 providers 数组往里面追加即可。这里特别要注意登录成功后用户表里的id是 NextAuth 生成的而不是 GitHub 传过来的要区分user.id和user.accountId。在 t3code 的业务里所有authorId我都用session.user.id也就是应用内部用户的 ID。5.3 在 Server Component 和 API 里取 sessionNextAuth 的 session 获取方式在 T3 里比较特殊。服务端组件页面里我直接用模板封装好的serverAuth.getSession()或原始getServerSession以此判断用户是否登录。tRPC 的 context 里已经自动注入了 session所以后端 procedure 里要拿当前用户非常直接ctx.session?.user。这里有个很多人会混淆的点Session 的旧版本环境变量是NEXTAUTH_SECRET新版是AUTH_SECRET。T3 模板如果默认用了 NextAuth v5环境变量名通常是AUTH_SECRET。我最初按照网上旧教程配置NEXTAUTH_SECRET本地没报错部署后 session 却一直失效查了半天才确认是变量名不匹配。建议部署到 Vercel 时直接把AUTH_SECRET和兼容的NEXTAUTH_SECRET两个都配了能省很多时间。5.4 受保护路由和受保护 procedure 的配合t3code 里有两类受保护内容一类是页面级私密操作比如“我的片段”页面、设置页另一类是 API 级写入操作比如创建、编辑、删除片段。我用了两层保护。页面级保护放在middleware.ts类似于拦截器未登录用户访问/my路径时自动跳转到登录页。但 middleware 只能做粗粒度路由保护真正细粒度的数据权限判断必须在 procedure 里做。比如一个用户不能通过 tRPC 客户端直接调用“删除某片段”的 mutation必须先校验ctx.session.user.id和要删除片段的authorId一致。这种两层配合非常重要。如果把权限判断只放在 middleware那么绕过前端直接调 tRPC API 的用户依然能越权如果只放在 procedure那么未登录用户会先看到页面再被 API 拒绝体验不好。两者各司其职页面级控制“能不能进入”API 级控制“能不能操作”。6. 上生产Vercel 部署过程里我改过的三处配置6.1 第一步永远是数据库不是代码把 t3code 部署到 Vercel 之前我先确保数据库处于可迁移状态。因为本地用的 PostgreSQL 是开发库我不能直接把本地迁移记录同步到线上数据库。正确流程是先创建一个线上 PostgreSQL 实例Vercel 集成 Postgres 或 Neon 都可以拿到线上的DATABASE_URL然后在本地对线上库执行迁移。生产环境我使用的是prisma migrate deploy它不会像migrate dev那样生成新迁移只会应用已经提交的迁移文件。这个命令最好放在构建流程的postinstall里或者部署前手动跑一次。Vercel 构建环境下Prisma Client 需要重新生成所以要在安装依赖后执行prisma generate否则 Vercel 环境的二进制和本地不一致容易报PrismaClient Initialization错误。6.2 Vercel 环境变量清单与常见命名陷阱部署到 Vercel 时我总共配置了这几个环境变量变量名说明DATABASE_URL线上 PostgreSQL 连接串AUTH_SECRETNextAuth 会话签名密钥AUTH_URL部署后的站点 URL通常填https://t3code.vercel.app或自定义域名GITHUB_CLIENT_IDGitHub OAuth 客户端 IDGITHUB_CLIENT_SECRETGitHub OAuth 客户端密钥AUTH_URL这个变量非常容易踩坑。本地开发时回调地址是 localhost部署后必须改成线上域名否则 GitHub 的 OAuth 回调会跳回本地用户在手机或同事电脑上打开就会直接 401。Vercel 的 Preview 环境尤其坑每个分支都有不同域名所以我在.env里注释掉了本地AUTH_URL让各环境自己去适配。另一个坑是DATABASE_URL传进去了但构建阶段仍然报数据库无法连接。常见原因是线上数据库的连接白名单没放行 Vercel 的服务 IP或者连接串里的用户名密码含特殊字符未转码。如果用的托管服务支持 URL 编码直接复制官网生成的连接串不要再手动改。6.3 Serverless 下的 Prisma 和认证细节部署成功后有几个运行时问题会逐渐浮现。第一个是数据库连接数的处理。Serverless 环境下每个请求都可能触发新的执行实例如果每个实例都创建一个新的 PrismaClient连接池很容易被打满。T3 模板的server/db.ts已经用 global 单例做了缓存但线上部署时要确认 Prisma 的连接池配置没有硬编码过小。我在连接串里用了 PostgreSQL 的默认连接池参数暂时没出过问题。第二个是 Prisma 二进制引擎在 Vercel 上的兼容问题。此前遇到过构建日志里出现 Prisma 引擎下载失败的报错常见原因是 Vercel 构建环境的操作系统和 Prisma 默认二进制不匹配。处理方式很粗暴但有效把 Prisma 升级到最新稳定版或者在 schema 的generator client里显式带上binaryTargets [native, rhel-openssl-1.0.x]。现在新版本 Prisma 对 Vercel 的支持已经很好了如果还遇到初始化报错第一时间看 Vercel 构建日志别在本地反复尝试。第三个是 NextAuth 的 session 持久化。因为 session 是存数据库的线上首次登录时如果AUTH_SECRET没配好会频繁跳转回登录页。我的解决方法是把 secret 固定为一个随机长字符串而不是依赖默认生成。此外在 Vercel 上用单一地域的部署可以减少跨区域数据库延迟别把 Vercel 项目放在美西、数据库放在欧洲那样首次 session 查询会明显变慢。6.4 部署完成后的几个体验优化线上跑通之后我又花了一点时间做体验层面的优化。第一件是给列表页加了缓存时间把useQuery的staleTime设置成十几秒用户来回切换页面时不会每次都发请求。第二件是给代码块做了语法高亮和复制按钮这个虽然和 T3 Stack 本身无关但代码片段工具的体验质量全在这两个细节上。第三件是把首页的公开片段数量做了分页控制不一次拉太多保证首屏加载速度。部署完成那天我盯着 t3code 的页面看了很久。坦白说这个项目不算大但它从数据模型到认证从 RPC 到部署几乎走完了一个全栈应用的全流程。如果非要说这段经历里最值得反复咀嚼的部分我会说是“让类型系统替人记事情”这个思路数据库模型一变tRPC 类型跟着变前端页面提示跟着变再到最后 Vercel 构建成功整个过程里我没有自己写过一行类型定义却处处被类型保护着。这种感觉比项目本身更值得留在脑子里。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑