T3 Stack 实战:构建类型安全的全栈代码片段管理工具

发布时间:2026/10/27 23:08:36
T3 Stack 实战:构建类型安全的全栈代码片段管理工具 这几年做全栈项目我越来越认同一句话技术选型直接决定开发幸福感。t3code 是我用 T3 Stack 完整搭建的一套全栈应用从名字也能看出来这是个跟代码强相关的项目——简单说它是一个给开发者自己用的代码片段与学习笔记管理工具。做它的初衷很朴素平时写过的零散代码片段、教程笔记、解决方案草稿经常分散在本地文件、备忘录和好几个仓库里等到要用的时候翻半天都找不到。T3 Stack 不是一个新框架而是一套以类型安全为核心的全栈组合拳主角是 Next.js、TypeScript、tRPC、Prisma、Tailwind CSS再配合可选的 NextAuth 认证。如果你正准备做一个带数据库、带用户体系的 Web 应用又不想在前后端接口联调上反复折腾这篇文章就是一条可以直接照做的完整路线。1. 项目定位与技术选型为什么兜兜转转还是选了 T3 Stack1.1 t3code 到底想解决什么问题先说业务场景。我自己的日常开发里有三个老毛病第一某个组件或函数写得特别好三个月后想复用但完全忘了放在哪个项目里第二看教程时候记的笔记散落各处没有统一的标签体系检索基本靠回忆第三想给某个旧项目补功能时经常要翻好几个仓库才能找到当时的实现思路。t3code 就是冲着这三个问题去的。它有几个核心模块代码片段管理支持标题、语言标签、代码内容学习笔记管理记录过程心得和踩坑经验统一的标签系统把代码片段和笔记用标签串联起来全文检索一分钟内找到想要的内容。当然还有最基础的用户体系登录后每个人只看到自己的数据。这个项目形态很适合做 T3 Stack 的练手和落地。它不像电商系统那样有复杂的订单状态机也不像后台管理系统那样重表单重权限但它把全栈应用的典型要素都覆盖了数据库建模、用户认证、增删改查、一对多和多对多关系、搜索筛选、前端状态管理、部署运维。换句话说把 t3code 完整跑通一遍你对这套技术栈的理解基本就到位了。1.2 组合拳的取舍T3 Stack 为什么省心先说结论T3 Stack 最大的价值不是某个单一框架多强而是整条链路的类型安全。数据库表结构由 Prisma Schema 定义类型直接生成服务端接口由 tRPC Router 定义输入输出自动推导前端组件拿到的是类型完整的响应数据。从数据库字段到页面渲染TypeScript 类型像一条线贯穿始终改一个字段名从 schema 到前端组件一路报错根本轮不到运行时才发现问题。这听起来很理想实际体验也确实如此。举个具体例子以前用 REST API 做前后端分离时后端改了接口字段前端要自己改 interface、改请求逻辑、改页面渲染稍有遗漏就出现线上 bug。而在 t3code 里我改了 Prisma 模型新增一个字段前端调api.note.getAll.query()拿到的数据自动就有这个字段编辑器里直接提示补全还需要手动维护一份 API 类型定义吗完全不需要。对比一下更直观方案类型链路额外工作适合场景Next.js API Routes REST前端手写 interface容易漂移需要维护 OpenAPI 或手写类型简单项目、第三方 API 对接多GraphQL Codegen类型由 Codegen 生成比较完整需要维护 schema 和 resolver多端复用、查询复杂tRPC路由定义即类型定义几乎为零全栈自用、快速迭代tRPC 的核心思路是“共享类型定义”。前端直接引用后端 Router 的类型编译器替你把接口契约的维护工作做了。有人会担心 tRPC 能不能应对复杂查询我的经验是在项目内部用足够遇到报表类复杂聚合也可以直接用 SQL 视图或额外接口不必把所有负担放在一个篮子里。另外Next.js 的 App Router 和 Server Component 在这个组合里也起了大作用。服务端组件里直接调用 tRPC 查询页面首屏数据一次性拿全不需要客户端再发一轮请求也不会有瀑布流请求的等待问题。Tailwind CSS 则是把样式开发速度拉满的工具——写 t3code 的时候我几乎没有写过自定义 CSS 文件全部是原子类组合。2. 环境准备与项目初始化从零把 t3code 跑起来2.1 本地依赖与数据库准备T3 Stack 对本地环境的要求不算苛刻但有些小坑值得提前说。我建议的版本组合如下工具建议版本说明Node.js20.x LTS避免 18 以下App Router 和 tRPC 都需要较新的运行时包管理器pnpm 9.xT3 Stack 官方脚手架的默认选项就是 pnpmnpm 也可以但锁文件不同PostgreSQL15本地用 Docker 装最省事一条命令搞定Docker任意较新版本本地数据库容器化项目删了数据库也跟着干净我一般在本地用一个 Docker Compose 文件专门跑 PostgreSQL开发完一键停止不污染系统环境。如果你不想用 Docker直接装 Postgres 也行但要注意数据库版本最好 14 以上Prisma 有些新特性依赖高版本。创建好数据库后需要准备环境变量。create-t3-app 初始化完成后会自动生成一个.env文件里面最关键的是DATABASE_URL。在开发环境我用的是postgresql://postgres:postgreslocalhost:5432/t3code?schemapublic这种格式。注意 Prisma 的连接串格式和普通连接串略有差异schemapublic这个参数要保留否则 Prisma 会默认找publicschema严格来说会自动匹配但显式写出来更保险。2.2 create-t3-app 初始化全流程T3 Stack 官方提供了完整的脚手架命令这是我在这类项目里最推荐的方式因为它把所有约定都帮你建好了不需要自己拼装。打开终端切到你想放项目的目录执行pnpm create t3-applatest t3code接下来会有一系列交互选项我逐个说下我当时的选择TypeScript 或 JavaScript选 TypeScript这是 T3 Stack 的灵魂。是否使用 ESLint选是后面写代码自动检查会省很多事。是否使用 Tailwind CSS选是前面已经说了它的价值。App Router 还是 Pages Router选 App Router这是当前 Next.js 的主流方向。是否使用 tRPC选是这是 T3 Stack 的核心。是否使用 NextAuth.js选是后面需要用户体系。是否使用 Prisma选是数据库 ORM 就靠它。是否包含 Docker Compose 文件如果你是本地开发选是也无妨可以直接用它跑数据库。初始化完成后进入项目目录先跑一个pnpm install装上依赖然后改.env里的DATABASE_URL为本地数据库连接串。这里有个容易踩的坑初始化生成的.env文件里DATABASE_URL通常是一个默认示例值比如file:./db.sqlite或者一个带假密码的 Postgres 地址千万别直接拿去跑下一步必须改成你的实际连接串。改完执行pnpm db:push它会用 Prisma 把你的 schema 同步到数据库里。create-t3-app自带的 schema 里已经有一个示例Post模型我们第 3 步会把它替换成 t3code 自己的模型。然后启动开发服务器pnpm dev浏览器打开http://localhost:3000如果看到一个默认的欢迎页面脚手架工作正常。此时整个目录结构已经包含了 Next.js 14 的 App Router、src/server/api下的 tRPC 路由、src/server/db.ts的 Prisma 实例、src/trpc的客户端和服务端封装结构比裸手搭要干净许多。3. 数据模型与后端接口落地3.1 Prisma 模型把笔记、标签和代码片段串起来业务模型是 t3code 的地基。我先梳理一下实体关系每个用户有多个代码片段、多篇笔记代码片段和笔记都可以被打多个标签标签也可以挂在多个内容下所以笔记和标签是多对多关系。Prisma 里定义多对多关系推荐用隐式多对多表implicit many-to-manyPrema 会自动创建关联表不需要手动管理。我最终的 Schema 核心部分长这样model User { id String id default(cuid()) name String? email String? unique emailVerified DateTime? image String? notes Note[] snippets CodeSnippet[] } model Note { id String id default(cuid()) title String content String db.Text language String? createdAt DateTime default(now()) updatedAt DateTime updatedAt userId String user User relation(fields: [userId], references: [id], onDelete: Cascade) tags Tag[] relation(NoteTags) } model CodeSnippet { id String id default(cuid()) title String code String db.Text language String note String? db.Text createdAt DateTime default(now()) updatedAt DateTime updatedAt userId String user User relation(fields: [userId], references: [id], onDelete: Cascade) tags Tag[] relation(SnippetTags) } model Tag { id String id default(cuid()) name String unique notes Note[] relation(NoteTags) snippets CodeSnippet[] relation(SnippetTags) }几个关键设计点想多说一下id我用的是cuid()而不是自增 ID。自增 ID 会暴露业务增长量比如让别人知道你注册了第几个用户在某些场景下有信息泄露风险cuid 在所有分布式环境下不需要协调就能生成对本地开发和未来扩展都很友好。content和code这两块承载大量文本的字段我特意加上了db.Text这是因为 Postgres 默认text类型可以存储字符串但如果不显式声明Prisma 在某些版本里生成的是varchar(191)会有长度上限。笔记稍微长一点就直接报错这个坑我踩过一次。Note和CodeSnippet都加了onDelete: Cascade用户删除账号时关联数据自动清理。注意relation的写法要把外键关系写清楚否则 Prisma 的关联迁移生成会报错。写完 Schema 后执行npx prisma migrate dev --name init它会在数据库里生成迁移文件并生成 Prisma Client 类型。从这一刻起后面每处代码能拿到什么类型都已经被 Prisma 约束住了。3.2 tRPC Router类型安全接口的核心实现T3 Stack 的 tRPC 代码组织在src/server/api目录下。create-t3-app默认给了trpc.ts作为创建调用过程的工厂函数里面有公开过程和受保护过程的定义。我来定义一个笔记相关的 Router用受保护过程确保只有登录用户能写笔记// src/server/api/routers/note.ts import { z } from zod; import { createTRPCRouter, protectedProcedure } from ../trpc; export const noteRouter createTRouter({ create: protectedProcedure .input( z.object({ title: z.string().min(1, 标题不能为空), content: z.string().min(1, 内容不能为空), language: z.string().optional(), tags: z.array(z.string()).default([]), }) ) .mutation(async ({ ctx, input }) { const note await ctx.db.note.create({ data: { title: input.title, content: input.content, language: input.language, userId: ctx.session.user.id, tags: { connectOrCreate: input.tags.map((name) ({ where: { name }, create: { name }, })), }, }, include: { tags: true }, }); return note; }), getAll: protectedProcedure.query(async ({ ctx }) { return ctx.db.note.findMany({ where: { userId: ctx.session.user.id }, orderBy: { updatedAt: desc }, include: { tags: true }, select: { id: true, title: true, language: true, updatedAt: true, tags: true, }, }); }), getById: protectedProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) { return ctx.db.note.findFirst({ where: { id: input.id, userId: ctx.session.user.id, }, include: { tags: true }, }); }), search: protectedProcedure .input(z.object({ keyword: z.string().min(1) })) .query(async ({ ctx, input }) { return ctx.db.note.findMany({ where: { userId: ctx.session.user.id, OR: [ { title: { contains: input.keyword, mode: insensitive } }, { content: { contains: input.keyword, mode: insensitive } }, ], }, orderBy: { updatedAt: desc }, select: { id: true, title: true, language: true, updatedAt: true, tags: true, }, }); }), });这中间有几个细节值得展开。第一每个操作都用了protectedProcedure这是因为在trpc.ts里受保护过程会自动把 session 塞进ctx没有登录直接返回 401。前端调用这些接口时未登录用户会统一收到未授权错误不需要在每个路由里重复判断。// src/server/api/trpc.ts 里的关键逻辑脚手架已生成 protectedProcedure procedure.use(({ ctx, next }) { if (!ctx.session?.user) { throw new TRPCError({ code: UNAUTHORIZED }); } return next({ ctx: { session: ctx.session } }); });第二connectOrCreate是处理多对多标签的关键。一个标签名如果不存在则创建存在则直接关联这个操作是原子的省去了“先查再插”的两步逻辑。配合name字段上的unique约束并发创建同名标签时也不会出现重复数据。第三getById的where里同时带上userId这是防止越权的标准写法。用户 A 拿用户 B 的笔记 ID 来查询findFirst会因为条件不匹配直接返回 null而不是把别人的数据暴露给他。这个习惯我用在 t3code 里的每一个查询和删除操作上。Router 定义好后别忘了在src/server/api/root.ts脚手架里的聚合文件里注册// src/server/api/root.ts import { noteRouter } from ./routers/note; import { snippetRouter } from ./routers/snippet; export const appRouter createTRPCRouter({ note: noteRouter, snippet: snippetRouter, });注册完以后前端就能通过api.note.getAll.query()调用这些接口了类型自动加载根本不需要写第二个版本的参数定义。4. 前端页面与交互实现4.1 数据流设计Server Component tRPC 的配合方式t3code 的页面结构分两块第一块是首页/列表页展示最近更新的片段和笔记第二块是详情/编辑页用户在里面增删改。这里我刻意用 Server Component 和 Client Component 做了一套分工具体是Server Component 负责首次数据获取和页面骨架渲染。Client Component 负责交互区域比如表单、搜索框、标签点击。跨组件共享的状态用 React Query 的缓存来管理。在 Server Component 里获取 tRPC 数据create-t3-app 已经封装了一个api对象// src/app/page.tsx import { api } from ~/trpc/server; export default async function HomePage() { const notes await api.note.getAll.query(); const snippets await api.snippet.getAll.query(); return ( main classNamemx-auto max-w-4xl px-4 py-8 h1 classNametext-2xl font-bold我的代码仓库/h1 SnippetList initialData{snippets} / NoteList initialData{notes} / /main ); }注意这里的~/trpc/server是脚手架生成的服务端调用封装它内部会把请求直接路由到当前进程中的 tRPC 服务不走 HTTP非常高效。页面跳转时Server Component 直接拿数据渲染首屏没有 loading 闪烁。对于需要交互的区域比如启动搜索框我用的是 Client Component tRPC 客户端调用use client; import { api } from ~/trpc/react; export function SearchInput() { const utils api.useUtils(); const searchMutation api.note.search.useMutation({ onSuccess: (data) { utils.note.getAll.setData(undefined, (old) data); }, }); const handleSearch (keyword: string) { searchMutation.mutate({ keyword }); }; return input onChange{(e) handleSearch(e.target.value)} /; }这里api.useUtils()是 tRPC v11 的新写法相当于拿到了一个可以操作缓存的对象。搜索成功时直接把结果写进getAll的缓存列表组件会自动重新渲染不用再做跨组件事件通知。React Query 的缓存让数据流变得非常线性查到新数据更新缓存视图跟着变。4.2 表单、代码编辑器与标签交互的落地细节t3code 的表单没有用很重的 form 库我选择的是react-hook-form配合zod做校验。因为 tRPC 的 input 本身就是 Zod schema前端完全可以复用同一套类型约束根本不会出现后端校验通过、前端校验不通过这种两套标准不一致的笑话。use client; import { useForm } from react-hook-form; import { zodResolver } from hookform/resolvers/zod; import { z } from zod; import { api } from ~/trpc/react; const noteSchema z.object({ title: z.string().min(1, 标题不能为空), content: z.string().min(1, 内容不能为空), language: z.string().optional(), tags: z.array(z.string()).default([]), }); export function NoteForm() { const createNote api.note.create.useMutation(); const { register, handleSubmit, setValue, watch } useForm({ resolver: zodResolver(noteSchema), defaultValues: { title: , content: , tags: [] }, }); const onSubmit (data: z.infertypeof noteSchema) { createNote.mutate(data); }; return ( form onSubmit{handleSubmit(onSubmit)} input {...register(title)} placeholder标题 / textarea {...register(content)} placeholder输入笔记内容 / {/* 标签输入组件点击后 setValue(tags, newTags) */} button typesubmit保存/button /form ); }代码编辑器这块我一开始用的是textarea但代码片段的展示效果太差。后来换成了uiw/react-codemirror它轻量、支持多语言高亮不到 20KB 的打包体积。关键配置是import CodeMirror from uiw/react-codemirror; import { javascript } from codemirror/lang-javascript; import { python } from codemirror/lang-python; export function CodeEditor({ onChange, value }: { onChange: (v: string) void; value: string }) { return ( CodeMirror value{value} height300px extensions{[javascript(), python()]} onChange{onChange} themedark / ); }编辑器的onChange直接回填到表单的字段里整个过程非常顺。播放代码高亮时还能顺手把语言标签自动填上这个小细节对体验提升明显。标签交互我做了两个入口新建/编辑时的标签输入框以及列表页顶部的标签筛选区。标签输入框本身是一个简单的自定义组件用户按回车添加标签点击标签上的 × 删除。标签用unique约束保证重名标签只存在一份自动去重。筛选区就是一组Tag按钮点击后通过查询参数或客户端状态过滤列表数据。样式方面我全部用 Tailwind 完成自定义了一个比较克制的色板背景用 slate-950主内容用 slate-800 卡片文字用 slate-200强调色用 indigo-500。暗色模式是开发工具的刚需Tailwind 只需要在tailwind.config.ts里配一个darkMode: class然后给根组件挂darkclass所有颜色类自动切换。5. 部署、性能与安全经验5.1 从本地到线上部署全流程记录t3code 的部署方案我选择了托管平台加云数据库的组合前端和 API 服务部署在 VercelPostgreSQL 数据库用托管云服务。这个组合对全栈 Next.js 应用几乎是零成本的上线路径不需要自己维护服务器也省去了配置 Nginx、PM2 这一套东西。部署前要准备的环境变量如下变量名说明DATABASE_URL云数据库连接串格式与本地一致NEXTAUTH_SECRETNextAuth 加密密钥用openssl rand -base64 32生成NEXTAUTH_URL生产环境域名如https://t3code.vercel.appGITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET如果使用 GitHub OAuth 登录则必填在 Vercel 项目设置里把这些变量配好以后我有两个经验想分享一下。第一个是数据库迁移的时序。Vercel 的构建流程是install - build - deployPrisma Client 需要在构建阶段就生成所以构建命令里必须包含prisma generate。而建表操作不应该由 Vercel 自动执行尤其是生产数据库不能动不动就跑prisma migrate dev。我的做法是在本地迁移完以后把迁移文件提交到仓库然后在数据库迁移环节手动执行prisma migrate deploy。如果是小团队没有专职运维可以直接在项目根目录加一个vercel.json的构建脚本把prisma migrate deploy prisma generate next build串进去。但要注意这样每次部署都会执行迁移迁移文件必须保持幂等和安全绝不包含破坏性操作。第二个经验是环境变量的作用域。DATABASE_URL这种带凭据的信息坚决不能带NEXT_PUBLIC_前缀否则会打进浏览器 bundle 里等于把数据库密码交到每个用户手里。只有真正需要给前端用的公开变量才加前缀比如某个第三方服务的公钥。5.2 性能优化与安全实践t3code 的列表页做得比较克制没有一次性把几十条笔记的完整正文全拉回来而是用select只返回列表需要的字段。这样首屏传输的数据量小渲染也快。真正需要看详情的时候才通过getById查询完整内容配合 React Query 的缓存重复访问同一篇笔记时不用再次请求。搜索这块最开始的实现直接用 Prisma 的contains来做模糊查询。数据量小的时候没问题笔记超过几千条以后就开始慢了。我在这个基础上加了两个优化一是给搜索框加useDeferredValue让输入过程不阻塞渲染二是把搜索请求做成 debounce用户停止输入 300ms 后才发请求。这套组合在 t3code 的数据规模下体验已经很流畅如果以后数据量再上台阶可以考虑引入 Postgres 的全文搜索或者独立搜索服务但目前没有必要提前引入复杂度。安全方面除了前面说的protectedProcedure和userId条件约束还有几点值得强调。Zod 的校验不是摆设。tRPC 的 input schema 是服务端校验的第一道防线但一定要把tags限制成字符串数组字段也要限制长度防止恶意用户提交超大 payload。我在创建笔记时对title和content都有长度上限title.max(100)content.max(20000)代码片段更是限制在 50KB 以内。NextAuth 的配置也不是开箱即安全。我用 GitHub OAuth 做登录回调地址是api/auth/callback/githubVercel 部署时必须把环境变量里的域名填对否则会一直回调失败。同时生产环境要确保NEXTAUTH_SECRET是一个高熵随机字符串不能用默认值或者短密码否则 session 可以被伪造。另外还有个容易被忽视的细节Prisma Client 在 Next.js 开发模式下会重复实例化导致数据库连接数飙升。create-t3-app 默认已经用globalThis做了单例缓存原理是开发模式下模块热更新会反复加载db.ts如果不做缓存每次刷新都新建一个 PrismaClient连接池很快就会被耗尽。这个单例模式在生产环境没有影响但在本地开发非常关键。6. 常见问题与排查技巧实录6.1 开发期高频踩坑记录我把这次开发过程中真正卡过壳、或者你大概率会遇到的问题整理了一下。现象原因解决办法页面一直报UNAUTHORIZED但明明已经登录protectedProcedure里ctx.session为空NextAuth 的 session 没有正确注入 tRPC context检查src/server/api/trpc.ts中createTRPCContext是否调用了getServerAuthSessionPrisma 执行migrate dev报“数据库连接失败”.env中DATABASE_URL没改或者 Postgres 服务没启动先确认本地 Docker 容器状态再在.env里写对连接串更新了 Prisma Schema但前端类型没有变化Prisma Client 没有重新生成执行npx prisma generate并重启开发服务器调 tRPC mutation 一直 400但代码没问题Zod schema 字段名和前端传的字段名不一致检查 input 里的 key 是否严格匹配tRPC 的 zod 校验是严格模式NextAuth 提示Invalid OAuth callback URL官方回调地址配置错误在 OAuth 应用的设置里把回调地址填成https://域名/api/auth/callback/github打包部署后页面数据是旧的React Query 默认gcTime缓存导致客户端拿旧数据在_app配置中修改staleTime或部署后等待缓存过期这里我想展开讲一下第二个问题。DATABASE_URL写错是最常见的新手报错而且报错信息不一定明确。有一次我把schemapublic写成了schemaprod整个 Prisma 查询直接报错但我第一反应是数据库连不上排查了半小时才发现是 schema 写错。建议第一次跑项目时先用一个可视化工具连一下数据库确认连接串无误再去研究 Prisma。还有一个开发中会遇到的奇怪现象改动 Prisma Schema 后pnpm dev有时候不会自动重新生成 Prisma Client于是代码里新增的字段在 TypeScript 层面就没有类型提示。这种情况不必着急重启电脑直接执行npx prisma generate然后 watch 模式会自动重新编译前端类型提示马上就恢复了。6.2 几个值得长期保留的避坑心得做 t3code 的这些天我最大的一个体会是T3 Stack 的默认脚手架质量很高但你不能完全依赖默认配置关键地方还是要自己理解原理。比如context的注入。脚手架生成的createTRPCContext会同时包含headers、session和db但如果你的项目需要额外信息比如请求里的自定义 header、权限标记一定要在createTRPCContext里扩展而不是在 Router 里临时处理。这样权限逻辑才能统一收敛。再比如superjson的作用。t3code 里我前后端传输的Date对象之所以没有被序列化成字符串都是superjson的功劳。你可能会看到transformer: superjson这个配置它让 tRPC 支持 Date、Map、Set 这些非 JSON 原生的类型。如果哪天自定义了一个BigInt字段不要忘了确认 superjson 的配置因为这个库不会自动处理所有类型嵌套在对象里的某些特殊类型会踩坑。还有一个关于状态管理的观察。t3code 前半程我还在用 Redux 和 Zustand 的思路去管理页面数据但后来发现完全没必要。React Query 已经承担了服务端状态管理本地组件之间的共享用 Context 或者简单的useState props 就足够。你再怎么在本地维护一份服务端数据副本最后都要跟服务器同步这本身就是重复劳动。T3 Stack 推荐的模式是让 React Query 成为服务器状态的唯一所有者本地 state 只管 UI 交互状态比如弹窗开关、编辑器内容。最后分享一个内联标签去重的小技巧。我创建标签时用connectOrCreate避免了重复插入但在前端用户可能快速点击同一个标签两次。我加了一层简单的前端去重const uniqueTags [...new Set(input.tags.map((t) t.trim().toLowerCase()))];输入进状态之前就做一次过滤这样既不会出现“React”“react”两个标签也减少了不必要的数据库操作。小事一桩但使用体验的差异很明显。把 t3code 从零搭到上线前后大概用了一个完整周末的时间。回头看最值钱的不是那几千行代码而是这套技术栈帮我省下的“前后端联调时间”。以前做类似的项目最烦的就是定义 API、写文档、对接口、处理类型不匹配全程下来至少多花两天。而在 T3 Stack 里这些事被类型系统自动消化了我能把精力花在真正重要的产品逻辑和交互细节上。如果你手头也正打算做一个全栈小项目我真心建议试一次 create-t3-app 起手跑通一个带数据库、带登录、带增删改查的完整闭环你会对类型安全这个东西有全新的体感。之后想在这个项目上加功能比如给代码片段加收藏、加分享链接、做标签云统计顺着 tRPC 的 Router 继续扩展就行了架构上基本不需要伤筋动骨。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询