news 2026/10/26 17:09:23

T3 Stack 实战:用 Next.js 与 tRPC 构建类型安全的代码片段管理站

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
T3 Stack 实战:用 Next.js 与 tRPC 构建类型安全的代码片段管理站

前阵子我搭了个小项目,代号 t3code,本质上是一个面向开发者的代码片段管理站。取名 t3code 不是想蹭什么新概念,而是它确确实实跑在 T3 Stack 全家桶上:Next.js、TypeScript、Tailwind CSS、tRPC、Prisma、NextAuth。我写它的动机非常私人——平时存代码的工具太散了,本地文件夹、在线笔记、临时聊天记录里到处都是,真到要用的时候只能靠搜索引擎和模糊记忆。于是想把“代码片段”这件事做成一个有标签、全文检索、代码高亮、可分享的小站。

t3code 最大的标签不是功能多,而是“端到端类型安全做得很彻底”:从 PostgreSQL 表结构、Prisma 返回的数据,到 tRPC 的入参和出参,再到前端 React 组件里拿到的对象,全部由 TypeScript 在同一套类型系统里完成闭环。开发中基本不用猜某个字段是否存在,IDE 直接给提示,编译期能暴露的问题少了一大半。

这个项目的定位很适合作一个完整的 T3 Stack 实战样本:有登录、有数据库、有 API、有页面、有权限控制、有部署。如果你正打算学 T3 Stack,想用它做一个真实可用的网站,又不想看那种只讲 hello world 的教程,那么下面这些从设计到落地的细节,应该都能直接用上。

1. 整体设计与思路拆解:t3code 为什么要用 T3 Stack

1.1 T3 Stack 的底层逻辑:让类型从数据库一路“长”到浏览器

T3 Stack 的命名来源很直白,它取了三个以 T 开头的关键技术:TypeScript、Tailwind CSS、tRPC。再叠上 Next.js 做应用框架、Prisma 做数据库访问层、NextAuth 做认证授权后,整个链路就非常完整。

先拆开看每一层:Next.js 负责页面和渲染,Tailwind 负责样式,Prisma 把数据库表映射成 TypeScript 模型,tRPC 负责服务端函数的远程调用,NextAuth 管理登录会话。单独看每一个工具都不复杂,复杂的是它们之间怎么配合。

t3code 里最核心的组合是 Prisma + tRPC。Prisma 负责让数据库记录变成带类型的对象,tRPC 负责让浏览器直接调用服务端函数。两层叠加以后,前端页面拿到的数据对象是从数据库记录一路推出来的,不需要手写 API 文档,也不需要在两个项目之间重复维护 interface。这里面的体验差别,只有真正写过前后端联调的人才能体会。

打个比方方便理解。传统开发模式里,前端和后端像是两个公司之间的合作,两边各自维护一份合同文本,后端改了字段名,前端不一定同步得到。tRPC 加 Prisma 的模式,则像同一个团队里两个人直接坐在一起协作,后端改动一个变量,前端立刻能在编辑器里看到红色报错,不需要开会对齐。

T3 Stack 里还有一个理念在 t3code 里被我用得很彻底:“类型安全优先”。不是说每个变量都要写得很复杂,而是让类型信息尽量从数据库源头开始自动推导。数据库表长什么样,Prisma 模型就长什么样;Prisma 模型长什么样,tRPC 出参就长什么样;tRPC 出参长什么样,前端组件接到的对象就是什么样。这条链路顺了,开发效率随之提高。

1.2 我把 t3code 拆成了哪几个模块

做项目之前先把边界划清楚。t3code 的需求池被我收敛成四个模块:

  • 用户模块:注册、登录、会话管理。基于 NextAuth,同时支持 GitHub 登录和邮箱密码登录。
  • 代码片段模块:核心业务,包含新增、查看、编辑、删除,以及复制、分享、上下架等功能。
  • 标签模块:片段和标签是多对多关系,支持点击标签筛选,也支持在新建时自动创建标签。
  • 列表与检索模块:关键词搜索、按语言过滤、按标签聚合,排序规则用最新创建优先。

模块边界划清楚了,后面写 tRPC router 的时候就不会乱。每个模块一个 router,router 之间的关系通过 Prisma 模型串起来,业务代码不互相渗透。

我也刻意做了一些减法。t3code 没有做团队空间、评论、点赞、关注这些重社交功能,也没引入富文本编辑器。因为 T3 Stack 的核心哲学是“保持精简、快速推出”。一个代码片段管理站,最核心的体验是把代码存下来、找得到、看起来舒服。功能堆得太多,反而会让类型安全和单元测试的边际成本上升,第一批使用者也容易被复杂入口吓跑。

1.3 技术选型:tRPC 和 Prisma 为什么让我舒服

很多团队写 Web 应用,默认就是 REST API 那一套:后端定义路由,写请求处理函数,前端根据接口文档手动写类型。小项目可能没问题,但一旦字段多了,接口文档和后端代码很容易不一致。

t3code 用 tRPC 后,这个困扰基本消失。tRPC 不需要生成独立的 API 定义文件,它是把后端函数变成可以远程调用的过程。前端调用api.snippet.getAll.useQuery(),传入的 query 参数、返回的数据结构,全部由 TypeScript 自动推断。REST 时代那种“看到接口文档和线上响应不一样”的问题,在 tRPC 这条路上找不到生存空间。

Prisma 同样让数据库操作简单了很多。原来写 SQL 映射到对象,需要手工处理字段名、关联关系、类型转换;Prisma 通过 schema 文件统一描述数据源和模型,迁移命令也能自动生成 SQL。t3code 里新增一张标签表,只需要改 schema 文件再跑一次 migrate,关联关系的前端类型也自动更新。

当然,技术选型要分场景。如果 t3code 以后要开放第三方 API,让外部开发者接入,那么 REST 网关仍然有意义。但内部的增删改查、列表分页、管理后台,用 tRPC 是最舒服的。两者不是替代关系,可以在同一个项目里共存。

方案类型维护成本中小型全栈项目体验是否需要独立 API 服务
REST + OpenAPI高,需要手动维护文档和类型一般,联调成本偏高通常需要
GraphQL中高,需要维护 schema 和 resolver可以,但对小项目偏重可以
tRPC + Prisma低,类型由代码自动推导非常好,前后端变量同源不需要

我会把下表里的选型结论用一句话概括:t3code 是一个偏内部产品、重类型安全的中小型全栈项目,tRPC + Prisma 是当前最顺手的选择。

2. 核心细节与实操要点:从零初始化 t3code 项目

2.1 使用 create-t3-app 初始化项目

初始化直接使用官方脚手架,省掉一大部分手工配置时间。

npx create-t3-app@latest t3code cd t3code pnpm install pnpm prisma migrate dev --name init

create-t3-app 初始化时会问到要不要使用 NextAuth、Prisma、Tailwind、tRPC,我的建议是全选。如果你只想做一个纯展示型网站,NextAuth 可以不开;但 t3code 需要登录和权限控制,所以这几个核心依赖都要保留。

安装完以后,默认项目里已经包含一个基本示例:有post相关的 router、一张Post表、一个简单的页面。我实际开发 t3code 时,先把示例删除并重置 Prisma migration,然后从空 schema 开始,一步步重新设计。

这里有一个经验:包管理器尽量用 pnpm。因为 T3 Stack 的依赖链很长,node_modules会占用很多磁盘空间,pnpm 在速度和磁盘占用上都有明显优势。

2.2 目录结构与文件职责

初始化后的目录结构是 create-t3-app 的默认规范,t3code 在此基础上做增删。核心路径如下:

t3code/ prisma/ schema.prisma src/ pages/ index.tsx snippets/[id].tsx api/trpc/[trpc].ts server/ api/ root.ts routers/ snippet.ts tag.ts user.ts trpc.ts auth.ts db.ts utils/ api.ts

每个文件的作用大概是这样的:src/server/db.ts导出 PrismaClient 单例;src/server/api/trpc.ts负责创建 tRPC 实例,定义公共/受保护 procedure;src/server/api/root.ts把业务 router 汇总为 appRouter;src/server/api/routers下按业务域拆分;src/utils/api.ts是前端调用 API 的入口。

t3code 的 bug 少,一部分要归功于这种把分工落到清晰位置的目录结构。不会出现在页面组件里直接 new PrismaClient 的坏味道。

2.3 数据模型设计:Prisma Schema 里的关键表

t3code 的 Prisma schema 核心部分设计如下,NextAuth 涉及的 Account、Session、VerificationToken 表由脚手架的默认代码负责,这里不展开。

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 emailVerified DateTime? image String? role String @default("user") snippets Snippet[] } model Snippet { id String @id @default(cuid()) title String language String code String description String? authorId String author User @relation(fields: [authorId], references: [id]) tags SnippetTag[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Tag { id String @id @default(cuid()) name String @unique snippets SnippetTag[] } model SnippetTag { snippetId String tagId String snippet Snippet @relation(fields: [snippetId], references: [id]) tag Tag @relation(fields: [tagId], references: [id]) @@id([snippetId, tagId]) }

数据库选用 PostgreSQL 而不是 SQLite,主要是为线上部署考虑。SQLite 在本地原型验证阶段很轻,但并发写入和部署到 serverless 环境时会受限。t3code 从一开始就按生产环境标准设计,省得后面迁移库重新改 schema。

这里还有一个设计要点:User 表里加了一个role字段,用于区分普通用户和管理员。它是字符串类型,默认值是"user",管理员手动改成"admin"。更严谨的做法是使用 Prisma enum,不过 enum 在后续迁移时会带来类型变更的额外成本。对于中小型项目,字符串角色加后端校验已经足够。

2.4 串联前后端的核心:tRPC 的 context 与 procedure

t3code 的 tRPC 实例定义集中在src/server/api/trpc.ts。这个文件决定了两件事:每个请求能拿到什么上下文,以及哪些操作需要登录。

import { initTRPC, TRPCError } from "@trpc/server"; import superjson from "superjson"; import { ZodError } from "zod"; import { prisma } from "~/server/db"; import { getServerAuthSession } from "~/server/auth"; export const createTRPCContext = async (opts: { headers: Headers }) => { const session = await getServerAuthSession(); return { prisma, session, }; }; const t = initTRPC.context<typeof createTRPCContext>().create({ transformer: superjson, errorFormatter({ shape, error }) { return { ...shape, data: { ...shape.data, zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, }, }; }, }); export const publicProcedure = t.procedure; export const protectedProcedure = t.procedure.use(({ ctx, next }) => { if (!ctx.session?.user) { throw new TRPCError({ code: "UNAUTHORIZED" }); } return next({ ctx: { session: ctx.session, }, }); });

初学 tRPC 时,很多人会对protectedProcedure的用法感到疑惑。它其实就是一段中间件,在正式执行后端函数之前先检查 session 存不存在。不存在就抛出未授权错误。这样的好处是,后续所有需要登录的 router 都可以直接复用protectedProcedure,不需要每个 mutation 里重复写 if 判断。

superjson也是一个容易被忽略但很重要的配置。它可以把 Date、Map、Set 等 JavaScript 特殊类型在序列化时保留下来。没有它的话,Prisma 返回的createdAt日期字段到前端会变成字符串,处理起来很绕。

2.5 Tailwind 排版:快速把页面做得像模像样

T3 Stack 默认集成 Tailwind,这对 t3code 来说受益很大。代码片段列表页、详情页、新建页,我都是用 Tailwind 工具类直接堆出来的,没有写额外的 CSS 文件。

做列表页时,我建议先定一个统一的卡片容器样式:rounded-lg border bg-white p-4 shadow-sm。代码详情页则用bg-gray-950 text-gray-100作为代码块的底色,白字更适合阅读。Tailwind 的原子化类名第一眼会显得杂乱,但用顺手后,改布局就像搭积木,不需要在多个 CSS 文件之间来回切换。

3. 核心功能实现:t3code 代码片段管理链路

3.1 先写 Prisma 层:定义 Snippet 与 Tag 的关系

t3code 的片段和标签是多对多关系,因此中间表SnippetTag必不可缺。之所以不把tagIds存成一个数组字段,是因为关系型数据库里做统计和过滤时,关联表比数组字段可靠得多。比如筛选出“同时包含 TypeScript 和 Prisma 两个标签的片段”,用关联表可以优雅地写条件,数组字段则需要额外处理。

写完 schema 后执行迁移:

pnpm prisma migrate dev --name init

这条命令会做两件事:根据 schema 生成一个新的 migration 文件,并且同步到本地数据库。之后如果再次修改 schema,需要再建新的 migration。我的习惯是:先写 migration 再做业务代码,这样所有数据库变更都有了版本记录,上线后也能知道每个环境跑到哪一版。

这里有一个细节要单独强调:本地开发时,PostgreSQL 并不一定需要真正安装一个数据库服务。用 Docker 起一个容器是最快的做法。启动后把连接串写进.env的DATABASE_URL即可。不过要注意,Docker 容器重启后数据会丢,除非挂载数据卷,所以本地数据别太当真。

3.2 编写 snippet router:查询与写入

业务代码集中在src/server/api/routers/snippet.ts。这个 router 是 t3code 的核心,包含列表、详情、创建、更新、删除五类操作。

先看创建和列表这两个最常用的过程。

import { createTRPCRouter, protectedProcedure, publicProcedure, } from "~/server/api/trpc"; import { z } from "zod"; const createSnippetSchema = z.object({ title: z.string().min(1).max(100), language: z.string().min(1).max(50), code: z.string().min(1), description: z.string().max(500).optional(), tagNames: z.array(z.string().min(1).max(30)).optional(), }); export const snippetRouter = createTRPCRouter({ getAll: publicProcedure .input( z .object({ q: z.string().optional(), tagId: z.string().optional(), language: z.string().optional(), }) .optional() ) .query(async ({ ctx, input }) => { const where = { ...(input?.q ? { OR: [ { title: { contains: input.q } }, { description: { contains: input.q } }, { code: { contains: input.q } }, ], } : {}), ...(input?.tagId ? { tags: { some: { tagId: input.tagId } } } : {}), ...(input?.language ? { language: input.language } : {}), }; const items = await ctx.prisma.snippet.findMany({ where, include: { author: { select: { name: true, image: true } }, tags: { include: { tag: true } }, }, orderBy: { createdAt: "desc" }, }); return items; }), create: protectedProcedure .input(createSnippetSchema) .mutation(async ({ ctx, input }) => { const authorId = ctx.session.user.id; const tags = input.tagNames ? await Promise.all( input.tagNames.map(async (name) => { const tag = await ctx.prisma.tag.upsert({ where: { name }, update: {}, create: { name }, }); return { tagId: tag.id }; }) ) : []; return ctx.prisma.snippet.create({ data: { title: input.title, language: input.language, code: input.code, description: input.description, authorId, tags: { create: tags, }, }, }); }), });

这段代码里有几个值得留意的设计。

第一个是 zod 校验。createSnippetSchema对标题长度、语言字段、代码内容都做了基础限制。如果前端绕过界面直接调用 API,后端也能把不合法的请求挡在业务逻辑之外。tRPC 会把ZodError自动格式化,前端能收到结构化的校验错误信息。

第二个是标签的 upsert 逻辑。tag.upsert的意思是:这个标签名存在则返回已有记录,不存在则新建。这样用户在新建片段时随手输入一个新标签,不需要先跑到标签管理页创建,流程上非常顺手。

第三个是列表搜索条件里用了contains。Prisma 的contains在 PostgreSQL 上会翻译成ILIKE,可以匹配不区分大小写的子串。对于 t3code 这种轻量搜索场景已经够用。真的遇到大文本搜索需求,再考虑接全文检索引擎。

3.3 React 页面:把类型安全用到极致

前端调用部分,t3code 走的是 tRPC 提供的 React Query 集成。在src/utils/api.ts里,脚手架已经配置好了httpBatchLink,指向/api/trpc。页面里直接写:

import { api } from "~/utils/api"; function SnippetList() { const { data, isPending } = api.snippet.getAll.useQuery({ q: keyword, tagId, language, }); if (isPending) { return <div>加载中...</div>; } return ( <div> {data?.map((item) => ( <div key={item.id}> <h2>{item.title}</h2> <p>{item.language}</p> </div> ))} </div> ); }

这个data的类型不是手写的,而是从snippetRouter的返回类型自动推断出来的。页面里写item.author.name时,编辑器会知道author一定包含name和image,如果写错字段名直接报错。

创建和更新的操作要用useMutation:

const createSnippet = api.snippet.create.useMutation({ onSuccess: () => { void utils.snippet.getAll.invalidate(); }, });

invalidate会在创建成功之后让列表页的查询重新拉取,页面自动更新,不需要手动刷新。React Query 的缓存机制会避免不必要的网络请求,用户体验比传统表单提交后整页跳转舒服不少。

这里还要提一个常见的性能细节:列表页的useQuery默认在组件挂载时才触发请求。如果对首屏渲染速度有要求,可以在 getServerSideProps 里用 tRPC 的 server caller 直接请求数据,把初始数据通过prefetch传给前端。t3code 目前对首屏要求不算苛刻,所以没有刻意做 SSR 预取,但 T3 Stack 提供了这个能力,按需选用即可。

3.4 权限控制:从 session 取身份,防止越权

代码片段是用户创建的内容,所以 t3code 需要保证“只有作者本人或管理员可以修改/删除”。这里最容易踩的坑是:前端把要操作的 id 传进来,后端只校验登录,不校验归属。

正确做法是在 mutation 内部再去数据库查询一次目标记录,对比 session 里的用户信息和记录的作者字段。比如更新片段的逻辑:

update: protectedProcedure .input(z.object({ id: z.string(), data: createSnippetSchema.partial() })) .mutation(async ({ ctx, input }) => { const snippet = await ctx.prisma.snippet.findUnique({ where: { id: input.id }, }); if (!snippet) { throw new TRPCError({ code: "NOT_FOUND" }); } const isAdmin = ctx.session.user.role === "admin"; const isOwner = snippet.authorId === ctx.session.user.id; if (!isOwner && !isAdmin) { throw new TRPCError({ code: "FORBIDDEN" }); } return ctx.prisma.snippet.update({ where: { id: input.id }, data: { ...input.data, title: input.data.title ?? snippet.title, language: input.data.language ?? snippet.language, code: input.data.code ?? snippet.code, }, }); }),

千万不要因为前端页面里已经隐藏了编辑按钮,就省略后端校验。API 是公开的,任何人都可以绕过前端直接发起请求。t3code 的权限规则遵循一条铁律:后端必须独立完成三次检查,一是是否登录,二是资源是否存在,三是当前用户是否有权操作。

3.5 部署到 Vercel 与环境变量

t3code 是 Next.js 应用,部署最顺的方案就是 Vercel。具体步骤:

  1. 初始化 Git 仓库,把代码推到 GitHub。
  2. 在 Vercel 里导入项目。
  3. 在 Project Settings 的 Environment Variables 中配置DATABASE_URL、NEXTAUTH_SECRET、NEXTAUTH_URL。
  4. 执行数据库迁移:在本地或 CI 中运行pnpm prisma migrate deploy。
  5. 部署完成后,用正式域名访问并验证登录流程。

NEXTAUTH_SECRET的生成方法:

openssl rand -base64 32

生产环境不能沿用开发时的默认密钥,否则会话信息可以被伪造。NEXTAUTH_URL则填正式域名的根地址,不要在结尾加斜杠。

数据库连接串也要注意。如果用的是云数据库,服务商一般会提供连接池地址。在 serverless 环境下,普通直连可能因为连接数限制而报错。可以在连接串后面追加参数,例如:

DATABASE_URL="postgresql://user:password@host:5432/dbname?connection_limit=5"

这样可以让每个 serverless 实例只创建少量连接,降低数据库压力。

4. 常见问题与排查技巧实录:t3code 开发中踩过的坑

4.1 改了 router,前端类型识别不更新

这是 t3code 开发期遇到最多的一个问题。症状是:后端新加了一个 query,保存后前端useQuery依然读不到,或者提示传入的参数类型不匹配。出现这个现象,通常不是代码写错了,而是 TypeScript 服务还缓存着旧的 router 类型。

解决办法很简单:重启开发服务器,或者在编辑器里手动 Restart TS Server。VS Code 里可以用Ctrl+Shift+P,输入TypeScript: Restart TS Server。做完这一步,前端一般就能立即识别新接口。

要特别提醒的是,不要用@ts-expect-error强行绕过。类型错误是 t3code 最把这次错误“视而不见”,后面页面里拿到字段再报错时,排查成本会高出很多。

4.2 Prisma migrate dev 失败

Prisma 迁移命令失败的原因很多,最常见的是数据库连接不上,错误码P1001。本地开发时可以先确认.env里的DATABASE_URL是否填对、PostgreSQL 服务是否启动。如果中途修改过 schema 里已有的字段,经常会出现提醒说 migration 冲突或者存在 drifted 状态。

有一个可用的命令是prisma migrate reset,它可以把数据库重置到最新 migration 并重新执行种子数据。不过这个命令会清空所有表,只能在开发环境用,绝不能在生产环境执行。生产环境更新 schema 的正确姿势是:本地生成 migration,提交到代码仓库,然后在部署时执行prisma migrate deploy。

我也经历过一次迁移“卡死”,看起来像一直没响应。实际原因是表里有大量数据而 MySQL/PostgreSQL 在反复尝试默写困难。这时候先查看数据库有没有锁,不要盲目反复执行 migrate dev。

4.3 NextAuth 登录后 session 为 null

t3code 里所有受保护接口都依赖 session,如果 session 为 null,登录用户的请求就会被protectedProcedure拒绝。排查这个问题时,我按下面这个顺序走:

  • 先确认浏览器 cookie 里有没有 NextAuth 的 session cookie。
  • 再确认NEXTAUTH_SECRET和NEXTAUTH_URL是否配置正确。
  • 然后确认createTRPCContext里有没有正确调用getServerAuthSession。

在本地开发时,如果用了自定义端口,NEXTAUTH_URL必须写成http://localhost:3000这样完整的内容,否则回调地址可能不对。

有一个很容易忽略的细节:在 tRPC context 里调用getServerAuthSession时要记得把请求 headers 传入。如果 headers 为空,NextAuth 找不到 cookie,session 自然就是 null。新版 create-t3-app 生成的代码里已经处理了这个,但如果你从旧项目升级,容易在这个地方踩坑。

4.4 tRPC 路由 500 或 404

出现这个问题的原因通常比较直接。404 大多是前端请求的路径不对,tRPC 的请求路径默认是/api/trpc/xxx.xxx,某个字母写错就找不到。500 则需要看服务端日志。前端报INTERNAL_SERVER_ERROR时,错误信息会被刻意隐藏,需要去 Vercel 的 Function Logs 里查看具体堆栈。

还有一种情况是:新写了一个 router,但忘记在src/server/api/root.ts里注册。只要你自己把snippetRouter加到createCallerFactory或appRouter的合并里,页面才会真正接管。不要只看 router 文件本身。

4.5 Vercel serverless 下数据库连接数不够

t3code 部署到 Vercel 后,如果并发不高,普通直连数据库没什么问题。但一旦请求量上来,serverless 函数的多个实例同时连接数据库,很容易把连接数打满。解决方案有以下几种:

  • 在连接串里设置connection_limit,让单个实例控制连接池大小。
  • 使用数据库服务商提供的连接池代理地址。
  • 将 Prisma 查询转移到独立的 Node 服务上,但这样就失去了 serverless 的便利。

对于 t3code 这种量级的项目,第一种方案最轻量。改一行连接串即可,不需要动代码。

4.6 代码高亮和 XSS 必须同时处理

t3code 存的是代码片段,展示时会把代码渲染成带高亮的 HTML。这里有一个安全问题很容易被忽视:用户提交的代码内容可能是从网上复制来的,里面可能含有<script>、<img onerror>等危险标签。如果直接把 content 塞进dangerouslySetInnerHTML,就是送上门来的存储型 XSS。

我的处理方式是:渲染高亮用专门的代码高亮库,例如react-syntax-highlighter或shiki,它们默认会转义代码内容,不把它当成 HTML 执行。如果只是渲染纯文本且需要保留空格换行,就用white-space: pre-wrap配合escape-html库转义。t3code 不提供代码在线运行沙箱,因此不需要用 eval 或动态执行机制,这本身也避开了一大批安全问题。

现象最可能原因解决办法
前端读不到新加的 tRPC 接口TS Server 缓存重启 dev server 或 Restart TS Server
Prisma migrate 报 P1001数据库连接串错误核对 DATABASE_URL,确认服务已启动
登录后 session 为 nullNEXTAUTH_SECRET / headers 传递问题检查环境变量及 createTRPCContext
tRPC 返回 404请求路径或 router 未注册检查前端调用路径及 root.ts
部署后经常断连serverless 连接数超限加 connection_limit 或改用连接池
页面出现闪烁或 HTML 标签未转义代码内容使用 highlight 库自动转义,避免 innerHTML

5. 一些基于实践的补充与小建议

项目做到这里,我对“全链路类型安全”有了更实际的体会。类型不是万能的,它能保证字段不写错,但保证不了业务规则没有漏洞。比如权限控制到底该用哪种策略、哪些操作需要写审计日志、删除片段时要不要级联删除标签,这些逻辑仍然要靠人工思考和测试来兜底。t3code 的做法是:能用类型强约束的坚决用类型,不能用类型约束的放到业务层,配合少量关键测试验证流程。

如果以后要继续扩展 t3code,我会优先做两个方向。一是 AI 自动打标签:根据片段标题和代码内容自动推荐候选标签,能省掉手动分类的大量时间。二是导出与导入:把本地代码时间线同步到 t3code,换电脑时一键拉回来。这两个功能都不会破坏 T3 Stack 的类型安全体系,也符合我当初做这个项目的初衷。

最后一个实用小技巧:在新建页面里用 tab 键快速把焦点从标题切换到代码编辑器,实际用起来很顺手。前端写起来就是多监听一个 keydown 事件,但能让日常使用体验提升不少。t3code 作为个人工具,最重要的不是功能堆叠,而是让每次打开它都觉得顺手。

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

t3code全栈项目实战:TypeScript类型安全链路解析

最近有个朋友问我&#xff0c;t3code 到底是干嘛的。乍一听像个新框架&#xff0c;其实不是。它是把近几年全栈圈呼声最高的那套东西——Next.js、TypeScript、tRPC、Prisma、Tailwind CSS——揉在一起做成的一个开源代码实践项目。简单说&#xff0c;t3code 解决的是很多人学全…

作者头像 李华
网站建设 2026/10/26 17:09:05

H3CNE-MSA存储认证考点全解析:五条主线与易错点

简介&#xff1a;面向备考H3C认证工程师-MSA存储&#xff08;H3CNE-MSA&#xff09;的考生与H3C代理存储设备技术服务人员&#xff0c;这份题库文档围绕MSA 2040/2042等设备的基础配置与维护知识组织内容&#xff0c;帮助读者在正式考试前系统梳理考点、检验掌握程度。资源包共…

作者头像 李华
网站建设 2026/10/26 17:05:35

酒店PMS系统t3code实操指南:房态管理、OTA直连与接口对接经验

朋友问我最近又在捣鼓什么酒店数字化项目&#xff0c;我回了一句“t3code”&#xff0c;对方一头雾水。这个听起来像个代码仓库代号的名字&#xff0c;其实是酒店行业里一套非常有代表性的PMS&#xff08;Property Management System&#xff0c;物业管理系统&#xff09;&…

作者头像 李华
网站建设 2026/10/26 17:05:01

text-to-cad实战:从一句话到CAD模型,AI建模的现状与经验

把一句话变成CAD模型&#xff0c;这事在很长一段时间里听起来都像是CAD厂商画的大饼。但最近一年多&#xff0c;text-to-cad这个方向突然被认真做起来了。我第一次用Zoo的在线工具测试时&#xff0c;输入"带四个安装孔的L形支架&#xff0c;长边120mm&#xff0c;短边60mm…

作者头像 李华
网站建设 2026/10/26 17:04:42

RetinaNet在建筑表面缺陷检测中的实践与优化

1. 项目背景与核心价值建筑表面缺陷检测一直是工程质量管控中的痛点。传统人工巡检方式存在效率低、主观性强、高空作业风险大等问题。我们团队基于RetinaNet构建的这套检测系统&#xff0c;在多个大型建筑项目中实测识别准确率达到92.3%&#xff0c;比传统方法提升近40%。这个…

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

MIMO-OFDM信道估计MATLAB仿真包:LS/LMMSE/DFT三算法可复现

简介&#xff1a;本资源是一份面向通信工程专业学生、科研人员及MATLAB仿真初学者的信道估计算法实践代码包&#xff0c;聚焦MIMO-OFDM系统中LS、LMMSE及DFT增强型两类估计算法的性能对比分析。通过误码率&#xff08;BER&#xff09;与均方误差&#xff08;MSE&#xff09;双指…

作者头像 李华