☰
用T3 Stack打造个人代码片段管理工具:全栈实践与踩坑记录
2026/10/28 5:45:31 网站建设 项目流程

T3code这个项目名一开始只是个临时代号,后来用着用着就不想换了。事情起因很简单:我平时写代码积累了大量片段,散落在本地笔记、VS Code的代码片段文件、GitHub Gist、甚至微信收藏里,想找的时候要么翻半天,要么干脆重新写一遍。于是我用 T3 Stack 做了个轻量 Web 应用,专门解决代码片段的统一管理问题。项目不大,但把 T3 Stack 的核心组件完整跑了一遍,这篇文章就把整个从选型、建模、实现到部署的过程,以及我踩过的坑,详细拆给你看。

如果你想快速了解 Next.js、TypeScript、tRPC、Prisma、Tailwind CSS 这一套组合怎么做实际项目,或者你手头正想写一个内部小工具但不确定技术选型,这篇应该能给你不少参考。整个项目所有代码都是我自己一行行写的,没有套后台模板,所以谈到的问题都是真实会遇到的问题,不是文档里的理想流程。

1. 整体设计与技术选型

1.1 先想清楚:这个工具到底要解决什么问题

写代码久了的人都会有一个体会,代码片段这种东西很“轻”,但收集渠道特别散。最常见的几种存放方式各有各的问题:

  • 本地仓库或者 wiki,适合长文档,但不适合快速录入和检索。
  • VS Code 内置 snippets,只绑定在当前编辑器里,换个环境就得重新配置。
  • GitHub Gist,分享方便,但私有 gist 要登录才看得到,标签功能也比较弱。
  • 浏览器书签,能存链接,不能存代码本身。
  • 笔记软件,能存文字,但代码高亮、格式化、快速复制都不顺手。

我需要的其实是这几个能力:能快速粘贴一段代码、能打多个标签、能全文搜索、能一键复制、能生成一个公开链接发给同事。更进一步,我希望这个工具本身足够轻,打开页面输入几个字就能用,不需要复杂的后台。

所以 t3code 的定位就出来了:一个面向开发者个人使用的代码片段管理工具,支持标签分类、全文检索、语法高亮和链接分享。它不追求做成本地 IDE 那样的重型工具,也不想做成社交平台,就是尽量把一个具体的、我自己每天都会用到的场景做到顺手。

1.2 为什么是 T3 Stack,而不是自己拼一套

T3 Stack 严格来说不是一个框架,而是一组约定好的技术组合。它的核心是 Next.js + TypeScript,再配上 tRPC、Prisma、Tailwind CSS 和 NextAuth.js。我选择它而不是自己手动拼一套脚手架,主要有三个原因。

第一,类型安全是端到端的。以前做全栈项目,前后端之间靠 REST API 加手写类型,接口稍微一多,前后端类型就很容易脱节。tRPC 的模型是你直接在后端定义一个 procedure,前端调用的类型自动推导出来,这意味着我改一个字段类型,编辑器立刻能在前端代码里标红提示。对于 t3code 这样频繁改动的个人项目,这个优势太值钱了。

第二,约定大于配置,省掉大量决策成本。我前些年做项目喜欢每个环节都自定义,最后光配置就花了好几天。T3 Stack 把数据库 ORM、API 方式、认证方案、样式方案都替你想好了,组合方式经过社区验证,踩坑概率低。对个人项目来说,能在拿到代码后半小时内跑起来,比什么都重要。

第三,生态成熟,后续扩展空间大。Next.js 可以做 SSR 也可以做静态导出,Prisma 能适配 SQLite、PostgreSQL、MySQL 多种数据库,NextAuth.js 支持的登录方式很多。这意味着如果 t3code 以后要加团队功能、部署到内网服务器,基本不用推翻重构。

当然,T3 Stack 不是没有代价。它对 TypeScript 的依赖很深,如果你不习惯类型推导,或者团队里大多数人只是后端出身,学习成本会偏高。而且在纯单人项目里,tRPC 带来的类型安全可能感受不明显,反而会让人困惑“为什么不用一个简单 fetch”。我的看法是,如果你想长期维护一个项目,类型安全带来的收益会在改需求的时候集中体现。

1.3 功能边界:哪些做,哪些坚决不做

很多类似项目最后臃肿起来,是因为什么都想做。我在设计 t3code 时给自己定了几个边界。

第一,不做多人在线协作。个人工具就是要快、要私密,协作会导致权限体系、邀请机制、实时同步等一系列复杂度,这不是当前阶段需要的东西。第二,不做积木式的“分享动态”。分享可以是一个简单的公开只读链接,访问者能看代码、能复制代码,但不能编辑。第三,不做重型的项目管理系统。片段就是片段,不需要关联 Issue,不需要燃气,不需要流水线。

确定边界的一个好处是数据模型会很干净。t3code 的核心对象只有 snippet,围绕它的就是标签、用户、分享记录,没有一堆互相牵扯的业务对象。实际上,如果你准备做类似工具,我建议你也把“不做什么”写下来,哪怕只在 README 里写三行字,需求蔓延的时候真的有用。

秉着这个设计思路,我最终的技术选型清单是这样的:

模块选型理由
前端框架Next.js 14(App Router)SSR、API、前端一体,部署简单
语言TypeScript全栈统一类型,和 tRPC 是绝配
API 层tRPC端到端类型安全,写 CRUD 极快
ORMPrisma数据库 schema 定义清晰,迁移方便
数据库SQLite / PostgreSQL(可选)单机开发用 SQLite,部署可切 PG
样式Tailwind CSS写页面速度快,主题定制容易
认证NextAuth.js支持 GitHub OAuth,无需自己存密码
校验Zod与 tRPC 配合,输入输出校验统一

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

2.1 数据模型设计:一张 snippet 表还是多张关系表

代码片段管理工具的数据模型,其实比看起来要烦。一些片段可能对应多种编程语言,也可能一个片段有多个标签。如果简单地做成”snippet 表里加一个 tags 字段“,看起来方便,后面做标签筛选和关联统计时就会很痛苦。

我采用的多对多关系,包含三张核心表:

  • snippet:片段本体,存储标题、语言、代码、说明。
  • tag:标签,每个标签有独立的 name 和 slug。
  • snippet_tag:关联表,维护片段和标签的多对多关系。

Prisma schema 的关键部分可以这么写:

model Snippet { id String @id @default(cuid()) title String language String @default("plaintext") code String note String? isPublic Boolean @default(false) userId String user User @relation(fields: [userId], references: [id]) tags SnippetTag[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId, language]) } model Tag { id String @id @default(cuid()) name String slug String @unique snippets SnippetTag[] } model SnippetTag { snippetId String tagId String snippet Snippet @relation(fields: [snippetId], references: [id], onDelete: Cascade) tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) @@id([snippetId, tagId]) }

这里有几个容易忽略的细节:

  • 我把language字段做成了字符串而不是枚举,原因是前端代码高亮库支持的语言列表经常变化,硬编码枚举反而每次都要迁移数据库。
  • isPublic是一个简化设计。公开链接本身是只读的,所以不需要再做复杂的访问记录表。
  • 关联表两个外键都设置了onDelete: Cascade,否则删除 tag 时会经常报约束错误。
  • 检索性能上,如果片段数量到十万级,用 SQLite 默认的LIKE '%keyword%'会开始变慢。到时候可以给code字段上FTS5或者直接切换 PostgreSQL 用 pg_trgm 索引。前期完全没有必要过度设计。

2.2 tRPC 路由设计:把 CRUD 写得像在写函数调用

t3code 的 API 层完整走了 tRPC 的流程。tRPC 和传统 REST API 最大的区别在于,你不需要单独定义 URL、请求方法、请求体结构。后端定义一个查询,前端像调用本地函数一样调用它,类型自动同步。

我按资源划分路由,核心路由长这样:

// server/api/routers/snippet.ts import { z } from "zod"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "~/server/api/trpc"; const SnippetInput = z.object({ title: z.string().min(1, "标题不能为空").max(100), language: z.string().default("plaintext"), code: z.string().min(1, "代码内容不能为空"), note: z.string().max(5000).optional(), tagNames: z.array(z.string().max(50)).optional(), isPublic: z.boolean().default(false), }); export const snippetRouter = createTRPCRouter({ list: protectedProcedure .input(z.object({ keyword: z.string().optional(), tag: z.string().optional() })) .query(async ({ ctx, input }) => { const where = buildWhereClause(ctx.session.user.id, input.keyword, input.tag); return ctx.prisma.snippet.findMany({ where, include: { tags: { include: { tag: true } } }, orderBy: { updatedAt: "desc" }, }); }), create: protectedProcedure .input(SnippetInput) .mutation(async ({ ctx, input }) => { return ctx.db.$transaction(async (tx) => { const snippet = await tx.snippet.create({ data: { title: input.title, language: input.language, code: input.code, note: input.note, isPublic: input.isPublic, userId: ctx.session.user.id, }, }); const tagNames = [...new Set((input.tagNames ?? []).map((t) => t.trim().toLowerCase()))]; for (const name of tagNames) { if (!name) continue; const slug = slugify(name); const tag = await tx.tag.upsert({ where: { slug }, update: {}, create: { name, slug }, }); await tx.snippetTag.create({ data: { snippetId: snippet.id, tagId: tag.id } }); } return snippet; }); }), delete: protectedProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { const snippet = await ctx.prisma.snippet.findUnique({ where: { id: input.id } }); if (!snippet || snippet.userId !== ctx.session.user.id) { throw new TRPCError({ code: "FORBIDDEN" }); } return ctx.prisma.snippet.delete({ where: { id: input.id } }); }), });

这里最值得说的是购物车问题。一开始我把标签处理写成了多个独立 await,后来发现如果前端传入的 tagNames 里有重复或者空字符串,会出现一半标签创建成一半失败的情况。改成$transaction之后,要么全部成功,要么全部回滚,数据一致性明显提升。

权限控制也应该放在路由层,而不是页面层。页面隐藏按钮只是用户体验,真正的权限校验必须体现在 procedure 里。比如delete这个 mutation 里,我只允许删除userId等于当前登录用户的记录。部署后你会感谢当初在每个接口都加上了这层判断。

2.3 认证与授权:NextAuth 和 OAuth 的接入细节

t3code 使用的是 NextAuth.js,并且默认接入了 GitHub OAuth。之所以没有自己做账号密码体系,很简单:个人工具再维护一套密码重置、邮箱验证、验证码逻辑,是纯粹的负担。用 NextAuth 之后,用户体系的核心工作变成了配置 OAuth App 和读取 session 用户 ID。

配置 auth 时,有几点需要特别注意:

  • 环境变量里的AUTH_SECRET不生成好,开发环境会出现间歇性登录出错。可以用openssl rand -base64 32生成一个稳定值。
  • GitHub OAuth App 的 callback URL 必须写到http://localhost:3000/api/auth/callback/github或者部署后的真实域名路径,填写不对会导致登录后跳转异常。
  • NextAuth 的 session 数据里默认只带 id、email、name,如果要存更多用户字段,记得在callbacks.session里手动扩展。

我并不是只允许 GitHub 账号登录。因为 t3code 是我身边同事也在用的工具,我后续还加了邮箱登录选项,用 Resend 发验证链接。这一项扩展在 NextAuth 里也就是加一个 providers 数组元素的成本,但收益是同事即使没有 GitHub 账号也能用。

登录之后的流程很简单:所有需要操作自己数据的地方都用protectedProcedure,它会自动从 session 里取出用户 id。登录用户最多只能看到自己创建的片段。公开链接走publicProcedure,直接按 snippetId 查数据,不要求登录。

2.4 前端交互:代码高亮、搜索框和数据加载

t3code 前端没有用特别复杂的状态管理库。这个项目的状态分两类:服务器端数据和本地 UI 状态。服务器端数据完全由 tRPC 的 query 管理,UI 状态只有弹窗开关、编辑是否打开等本地 small state,用 React 自带的useState就够了。

代码高亮我一开始在react-syntax-highlighter和shiki之间纠结过。前者的渲染体积比较大,首屏加载大量语言包时明显变慢;后者虽然打包配置麻烦一点,但基于 TextMate 语法,高亮效果和我 VS Code 里看到的几乎一致。我最终选了 shiki,并用动态导入的方式按需加载语言包:

import { createHighlighter } from "shiki"; let highlighter: Awaited<ReturnType<typeof createHighlighter>>; export async function getHighlighter() { if (!highlighter) { highlighter = await createHighlighter({ themes: ["github-light", "github-dark"], langs: ["javascript", "typescript", "python", "go", "sql", "bash", "json"], }); } return highlighter; }

代码高亮服务端渲染有个好处:页面源码里直接包含高亮后的 HTML,首屏就能看到完整样式,不会像客户端渲染那样闪一下。代价是 SSR 的时候 CPU 会多花一点时间。对 snippet 这种界别的内容量来说,代价完全可以忽略。如果你做更重的富文本渲染,要考虑给高速缓存加应用层缓存,我当时直接跳过了这一步。

搜索框的实现,我用的是 tRPC query 里带keyword参数,配合useDeferredValue做防抖。具体做法是 React 部分:

const [keyword, setKeyword] = useState(""); const deferredKeyword = useDeferredValue(keyword); const { data: snippets } = api.snippet.list.useQuery({ keyword: deferredKeyword, tag: selectedTag ?? undefined, });

这样做的好处是输入框更新时不会立刻触发请求,React 会在渲染空闲的时候再发起请求,用户感知到的卡顿明显减少。搜索逻辑后端通过 Prisma 的contains实现:

const where: Prisma.SnippetWhereInput = { userId: userId, ...(tag ? { tags: { some: { tag: { slug: tag } } } } : {}), ...(keyword ? { OR: [ { title: { contains: keyword } }, { code: { contains: keyword } }, { note: { contains: keyword } }, ], } : {}), };

关键词多表查询的 OR 条件在 SQLite 下可以正常工作,数据量大了以后还是建议换成专门的全文搜索引擎,但对个人工具来说完全够用。

2.5 表单校验与状态管理:Zod 一处定义,到处复用

表单校验我没有用传统的前后端两套校验。Zod 和 tRPC 配合使用后,在路由层定义的SnippetInput可以同时用于后端校验,也可以被前端表单库消费。t3code 用的表单库是 React Hook Form,Zod 作为 resolver,这样改一次校验规则,所有调用方同步生效,不可能出现前后端校验不一致的 bug。

实际开发里,我最常碰见的表单问题反而是“标签输入框”的处理。用户输入React, Next.js,全栈这样的字符串,要拆分成数组、去掉重复、去掉首尾空格。这里面有个坑是全角逗号和中文括号,所以我在前端做了统一的清洗函数,再交给 tRPC 路由端做二次清洗。双保险:

function parseTags(raw: string): string[] { return raw .split(/[,,;;]/) .map((t) => t.trim().toLowerCase()) .filter(Boolean); }

另外,我必须提醒一句:即时后端有 Zod 做了校验,前端也一定要控制提交按钮的 loading 状态,否则用户在弱网环境连点三次提交,会创建出三个重复片段。虽然这次我会 utils 触发 revalidate,刷新后只显示一个,但数据表里垃圾数据已经写进去了。

3. 实操过程与核心环节实现

3.1 用 create-t3-app 初始化项目骨架

T3 Stack 官方脚手架是create-t3-app,执行命令以后可以选择是否引入 Prisma、NextAuth、Tailwind、tRPC。我建议第一次做就直接全选,省得后期补配置,补起来往往会动到不少文件。

npx create-t3-app@latest t3code cd t3code npm run dev

初始化之后,目录里会有一套很干净的约定结构。短期内你只需要关注几个目录:

  • src/server/api/routers/:按资源拆分 tRPC 路由。
  • src/server/db.ts:统一导出 Prisma client。
  • src/app/:App Router 下面的页面和 API 入口。
  • src/trpc/:给 React 用的 provider 和 hooks。

首次跑起来后,我建议先不要改任何业务代码,而是把 prisma 的 schema 改成自己要用的数据模型,然后跑迁移。跑迁移之前保证DATABASE_URL环境变量已经配置好,本地开发我直接用的是 SQLite。

3.2 写好数据库迁移脚本和种子数据

prisma schema 改完,执行迁移:

npx prisma migrate dev --name init_snippet_tag npx prisma studio

这段命令会生成一张迁移记录表,让数据库状态和 schema 保持同步。开发阶段我基本每天都在跑迁移,如果你在做自己的项目,也记得每次修改 schema 之后都要第一时间迁移,否则前后端类型一会儿对不上一会儿又对上,排查起来特别耗费时间。

种子数据我建议随手写一点点。手动在页面里点击创建测试数据当然也行,但写一个prisma/seed.ts能让你反复删除库之后快速恢复数据:

npx prisma db seed

种子数据里不要全部是模板片段,我特意加了几条中文标签的测试数据,比如“全栈”、“踩坑记录”、“面试题”,这样能顺便测试搜索框的全文检索效果。

3.3 实现片段列表和详情页

t3code 的主页面由三个部分组成:左侧是标签筛选栏,中间是片段列表,右侧是详情编辑器。

片段列表的数据直接从 tRPC 查出来之后,用 Tailwind 做响应式卡片布局。卡片上显示标题、一段代码预览、语言标签、更新时间。预览代码我用line-clamp限制只显示前几行,如果全都渲染出来,列表会非常长,页面滚动体验很不好。

详情页是项目里样式最复杂的部分。输入标题我用普通的 input,代码区我用 textarea 加等宽字体,预览高亮区用 shiki 生成 HTML。你会发现,编辑时和预览时如果字号、行高不一致,光标会错位,复制代码也会出问题。所以我已经固定了font-mono text-sm leading-relaxed作为代码区域的统一样式。

这里顺便分享一个细节:代码复制按钮最好直接用navigator.clipboard.writeText。复制code字段的原始内容是靠谱的,但是如果你复制的是“高亮后的 HTML”就会把标签也复制进剪贴板。我实际测下来,大多数人对这个细节是不敏感的,直到他粘贴到 Markdown 文件里才发现问题。

3.4 页面鉴权和公开分享实现

私密片段需要在页面层判断是否登录。因为 t3code 本身就是需要登录才能使用的工具,我将整个主应用都放在了一个需要 session 的布局下,本地实现得相对粗暴:直接在根布局里判断 session,未登录则渲染一个登录按钮。

公开链接的路径是/s/{id},对应的页面不要求登录。查询数据时要注意两点:

  • 必须有isPublic: true条件,否则任何人猜出 id 就能读到私密片段,这个是最容易漏掉的隐患。
  • 公开页面的分享信息只能暴露片段内容,不要返回 userId、updatedAt 之外的多余字段。虽然你只是在拼一个个人项目,但安全习惯应该从第一行代码养成。

3.5 部署到服务器:Prisma 迁移和环境变量管理

部署我选了 Vercel 上的 Next.js,数据库用的是云上 PostgreSQL。整体配置文件里其实没有太多额外的代码,要注意的就是环境变量和部署钩子。

在 Vercel 的 Project Settings 里,我配了几个环境变量:

  • DATABASE_URL:指向远程 PostgreSQL。
  • AUTH_SECRET:登录密钥,必须在生产环境重新生成。
  • AUTH_GITHUB_ID/AUTH_GITHUB_SECRET:GitHub OAuth 的密钥。
  • NEXTAUTH_URL:生产环境下必须是公开访问的域名,不能带 http://localhost。

数据库迁移方面,我建议在部署前手动在本地对远程数据执行一次:

npx prisma migrate deploy

如果忘了这一步,应用部署成功但数据库里没有表,页面会直接抛PrismaClientInitializationError。这种错误定位起来很快,但如果你第一次接触会在 Vercel 日志里反复检查代码,白白浪费时间。

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

4.1 tRPC 类型不同步,改了后端前端不认

这个问题的频率出乎我的意外。前端写调用时已经在对象里看见新字段,可是编辑或者 build 的时候仍然报错。实际上原因多半是 tRPC 的类型推倒是延迟的,或者你的 Next.js 正在用缓存。

排查步骤:

  • 检查src/server/api/root.ts是不是已经 export 了新加的 router。
  • 检查src/trpc/server.ts里的AppRouter类型有没有被重新加载,建议杀掉整个 dev server 再npm run dev。
  • 确认是在路由文件里返回了新的字段,而不要把字段写在 Prisma select 里却忘在 tRPC 输出层。

如果你改完类型之后前端页面没有报错,但数据就是不出来,多半是 React Query 的缓存问题。可以在 query 里加一个trpc-react-query返回的utils.invalidate或者手动指定refetchOnWindowFocus: true。

4.2 Prisma client 在本地和线上行为不一致

最典型的例子是cuid()和uuid()。SQLite 本地生成的 id 是字符串,PostgreSQL 线上如果用cuid()也支持。但如果你把 schema 里改成@default(uuid())并且数据库是 SQLite,本地跑没问题,部署到 PostgreSQL反而报错,因为 PostgreSQL 默认没有生成 UUID 的函数,除非你有插件。我建议固定使用cuid(),避免这种跨平台差异。

另一个常见的坑是连接数。用 Serverless 部署到 Vercel 时,Prisma 每次冷启动都创建新连接,而云数据库连接池有上限。超过以后日志里会频繁出现P2024错误。解决办法有两个:要么直接使用支持连接池的连接字符串,比如 Supabase 或 Neon 给的?pgbouncer=true后缀;要么就别把所有页面都做成动态渲染,尽量让 Next.js 缓存静态部分。

4.3 NextAuth 登录后回跳地址错误

我在测试 GitHub 登录时踩过一个诡异问题:登录后地址栏变成了http://localhost:3000/api/auth/callback/github,然后白屏。排查后发现是我在 OAuth App 里填的 callback URL 带了额外路径,和 NextAuth 默认的路径不匹配。

NextAuth 的 callback 路径是固定的/api/auth/callback/{provider},如果你用自定义前缀,必须同步修改配置,否则登录流程会在最后一步死掉。这类问题看日志不一定直观,最好的方式是打开浏览器 Network 面板看最后一次重定向到哪个地址,然后对比环境变量里的NEXTAUTH_URL是否和实际访问一致。

4.4 代码高亮首屏渲染慢

如果你按我的方案用 shiki,第一次请求时会明显感觉到页面渲染变慢,因为要异步加载语言包和主题。解决思路是缓存 highlighter 实例,已经在上文提到了。另外,shiki 支持@shikijs/rehype在 Markdown 解析阶段直接做高亮,如果你的片段说明带 Markdown,可以统一交给 rehype 处理,避免前言不搭后语。

前端预览代码的行数如果特别多,渲染上万行的语法高亮也会卡顿。我给代码预览区加了一个最大高度和滚动条,同时用useMemo缓存高亮结果?实际上 shiki 本身没有 useMemo,但你可以通过memo(CodeBlock)来避免无谓的重新渲染。

4.5 标签筛选、URL 同步和浏览器返回到问题

我一开始的标签筛选只是存在 useState 里,后来发现一个问题:用户选中了一个标签,刷新页面还处在选中状态在个人工具里可能无所谓,但当你把公开链接分享出去时,看到的标签状态完全不一样,就奇怪了。

我后来把标签筛选同步到了 URL query 上,比如/ ?tag=typescript。这样刷新不丢失,分享链接也能直接定位到标签维度。要注意的是,和router.push同时更新查询参数和列表数据时,不需要重新触发 query,只要把tag作为 query 的输入参数,React Query 会自动处理。

4.6 数据一致性和 XSS 隐患

代码片段工具里不能只想着高亮,还要警惕 XSS。虽然我们用 textarea 收集代码,但代码最终会被渲染成 HTML。我强烈建议在服务端对代码内容做一下转义处理,或者在渲染时使用专门的高亮库,它会对代码里的特殊字符做 escape。短期内你可能只是给自己用,但如果以后开放注册,这种问题会自动变成安全漏洞。

同样地,公开链接的<title>标题也要做编码。我在早期版本遇到过一个问题:用户给片段起了一个包含</title>的标题,整个页面的标题直接被截断。后来我统一对标题做转义,再用 React 的普通文本渲染,这个问题才彻底消失。

5. 后端扩展方向与个人体会

5.1 下一步准备加的功能

t3code 当前版本已经满足我自己的日常需求,后面确实也想过几个比较有价值的方向。

一个是浏览器插件。大家遇到代码片段时往往是在浏览文档、看博客的时候,这时候如果有个浏览器插件可以直接右键收藏代码,录入成本会低很多。插件通过 OAuth 拿到 token,调用的还是 t3code 现有的 tRPC 接口。

另一个是 AI 辅助。当前的片段检索虽然支持全文搜索,但是对“模糊记忆”比较弱,比如记得一段正则大概效果是什么,却说不出关键词。接入语义搜索或者直接用 AI 给片段生成摘要,搜索体验能提升一个档次。如果要做,我大概率还会用 tRPC 包一层 AI 服务,保持类型安全。

还有一个是离线模式。虽然基本没人会在没有网络的时候搜索代码片段,但如果我们把常用标签的前 50 条缓存到 localStorage,确实能让工具体验更接近原生应用。Next.js 的 PWA 方案我也在关注,只是还没动手。

5.2 做到现在,我建议你也关注一下这些事

如果你准备模仿 t3code 做类似的项目,我给你几个实操层面的建议,都是我自己反复折腾后总结出来的。

数据库从 SQLite 切到 PostgreSQL 要趁早。我自己前期图省事一直用 SQLite,后面为了部署到多家云厂商,改成 PostgreSQL 时遇到了几个类型相关的小问题。如果你预计项目会部署到公网,直接用 PostgreSQL 起步可能会更顺。

日志和监控不要等到用户反馈问题才开始配。tRPC 本身有中间件机制,我建议在根 router 上挂一个轻量日志中间件,每次请求记录一下耗时和路径。个人项目也不用接很重的监控系统,Vercel 自带的日志面板对我来说就够用了。

还有一点比较主观但我觉得很重要:自己造的工具一定要“常用”。很多人做完内部工具就不再维护了,因为总觉得运行在别人的产品里。其实像 t3code 这种个人工具,只要你能做到每天打开、每周处理几条新片段,就说明这个项目真的在给你创造价值。我目前就是把它固定在了浏览器新标签页的常用站点里,慢慢用顺手了。

5.3 说说我对 T3 Stack 这套东西的看法

这个项目做完,我个人对 T3 Stack 的好感度明显提升了。它并不是让你少写代码,而是帮你在代码规模变大之后,仍然能保持一个清晰的结构。类型安全的传递效果是最值得称道的,尤其是你改了 Prisma schema 之后,前端、后端、校验层几乎全都同步更新,让重构的恐惧感小了很多。

不过我也要说点实话。T3 Stack 对项目规范的约束是双刃剑。如果你希望项目有非常特殊的架构,或者团队习惯用 Redux、自定义验证体系,那这套组合反而会有点“较劲”。我自己做个人项目的时候喜欢拿它作为默认配置,因为它已经帮我把大部分“无聊决策”做了。

最后分享一个我刚开始写 t3code 时没预料到的小技巧:在 tRPC 路由文件里,你可以直接使用ctx.db,而不必每次都从ctx.prisma重新解构。这个看似很小的差异,在写大量的时候能省掉很多重复代码。祝你也早点把自己的常用工具做成理想的样子。

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

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

立即咨询