
tech-interview-handbook 的 Portal 应用解析基于 T3 Stack 构建全类型安全的全栈架构【免费下载链接】tech-interview-handbookCurated coding interview preparation materials for busy software engineers项目地址: https://gitcode.com/GitHub_Trending/te/tech-interview-handbook本文以 tech-interview-handbook 仓库中的apps/portal应用为核心完整解读其 T3 StackNext.js Prisma tRPC TailwindCSS NextAuth技术架构从「为什么项目里还有 .js 配置文件」这一 T3 设计哲学讲起深入环境变量 Zod 校验、Prisma 数据库层、tRPC 路由组织方式、NextAuth 认证配置直至 Vercel 部署流程。读完本文你可以复刻该 Portal 应用的分层结构与工程化配置并掌握各关键配置文件在源码中的真实落地位置。Portal 是什么一个 T3 Stack 应用apps/portal是 tech-interview-handbook 项目中的核心 Web 应用面试题库、Offer 数据、简历审阅等功能的承载端它是一个按照 init.tips 中的元数据得到印证ct3aMetadata: { initVersion: 5.13.1 }ct3aMetadata字段表明该应用最初由 create-t3-appct3av5.13.1 初始化随后在真实业务上迭代。package.json 中的依赖清单即对应 README 中提到的四大核心技术与若干业务扩展技术版本dependencies在 Portal 中的角色Next.jsnext: 14.2.4全栈框架Pages Router API RoutesReact / React DOM18.2.0视图层Prismaprisma/client: ^4.4.0ORM / 数据访问层PostgreSQLtRPCtrpc/*: ^9.27.2端到端类型安全的 API 层NextAuthnext-auth: ~4.10.3next-auth/prisma-adapter会话与登录GitHub OAuthTailwindCSS经 PostCSS 集成原子化样式Zod^3.18.0环境变量与数据校验superjson^1.10.0tRPC 的 Date/Map 等复杂类型序列化业务侧还引入了react-pdf、read-excel-file、xlsx简历 PDF 与薪资 Excel 导入、supabase/supabase-js前端文件存储、react-dropzone、react-hook-form等库与 prisma/salaries.xlsx、prisma/companies.csv 等种子数据文件相互印证。为什么项目里还有 .js 配置文件原 README 的核心问题之一是“Why are there.jsfiles in here?”。其回答是遵循 T3-Axiom #3——类型安全不是可选项Typesafety isnt optional。由于并非所有框架和插件都支持 TypeScript 配置部分配置文件必须写成.js但 Portal 通过两种手段维持了类型约束显式声明模块类型文件名后缀区分cjsCommonJS与mjsESM取决于所用库支持的格式ts-check注释所有 js 文件头部加上ts-check仍纳入 TypeScript 检查。在仓库中可以逐一对应验证tailwind.config.cjs 与 postcss.config.cjs —— Tailwind 与 PostCSS 官方只支持 CJS 配置所以用.cjsnext.config.mjs —— 用 ESM 的.mjs并借助 JSDoc 泛型为defineNextConfig提供自动补全src/env/schema.mjs —— 首行即// ts-check配合type注解让纯 JS 文件拥有类型。next.config.mjs的完整内容也很能说明 T3 的「最小但类型安全」取向import ./src/env/server.mjs; // 启动时即校验环境变量非法则构建失败 /** * template {import(next).NextConfig} T * param {T} config * constraint {{import(next).NextConfig}} */ function defineNextConfig(config) { return config; } export default defineNextConfig({ experimental: { esmExternals: loose }, reactStrictMode: true, swcMinify: true, });首行import ./src/env/server.mjs是关键它让环境变量校验先于 Next.js 配置加载执行环境变量不合法时应用直接无法启动/构建这正是 T3 Stack「fail fast」思想的落地。环境变量Zod 模式定义 运行时校验T3 Stack 对环境变量处理的标准做法在 Portal 中完整保留核心文件是 src/env/schema.mjs。服务端必填变量被定义为export const serverSchema z.object({ DATABASE_URL: z.string().url(), // PostgreSQL 连接串 GITHUB_CLIENT_ID: z.string(), // GitHub OAuth 应用 ID GITHUB_CLIENT_SECRET: z.string(), // GitHub OAuth 应用密钥 NEXTAUTH_SECRET: z.string(), // NextAuth 会话签名密钥 NEXTAUTH_URL: z.string().url(), // 应用公网地址 NODE_ENV: z.enum([development, test, production]), SUPABASE_ANON_KEY: z.string(), // 前端文件存储 SUPABASE_URL: z.string().url(), });要点说明DATABASE_URL对应 prisma/schema.prisma 中datasource db { provider postgresql url env(DATABASE_URL) }是本地开发的第一前置条件GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET被 src/pages/api/auth/[...nextauth].ts 中的GitHubProvider直接消费NEXTAUTH_SECRET/NEXTAUTH_URLNextAuth 会话签名与回调 URL 所必需客户端变量约定文件内注释明确「要暴露给客户端的变量必须以NEXT_PUBLIC_为前缀」当前clientSchema为空全部注释占位说明 Portal 目前没有向浏览器暴露任何自定义环境变量敏感信息一律留在服务端。src/env/目录共三个文件schema.mjs模式定义、server.mjs服务端解析 校验导出、client.mjs客户端解析。tRPC 上下文与 Prisma 客户端均从~/env/server.mjs取env保证任何拿不到合法环境变量的模块都无法初始化。数据层Prisma 模式、单例客户端与种子脚本Schema 与数据源prisma/schema.prisma 声明了 PostgreSQL 数据源与客户端生成器generator client { provider prisma-client-js previewFeatures [interactiveTransactions] } datasource db { provider postgresql url env(DATABASE_URL) }其中interactiveTransactions预览特性为 tRPC 的prisma.middleware事务支持提供了基础。Schema 模型可以划分为两个阵营NextAuth 必需模型Account、Session、User、VerificationTokenschema 中注释明确标注 “Necessary for NextAuth”供 PrismaAdapter 持久化 OAuth 账户与会话业务模型Todo、Company、Country/State/City等地理数据以及 Resumes、Questions题目、答案、评论、投票、encounter 记录、Offersoffer、analysis等实体。User模型通过大量关系字段如questionsQuestionEncounters、OffersProfile、resumesComments把这些业务实体与登录用户挂接。migrations/目录下保留了自20220928103800_init以来 40 余个按时间戳命名的迁移如20221006024246_add_companies、20221117094655_add_offersadmin_table可完整追溯 Portal 数据模型的演化历史migration_lock.toml记录了锁定的 provider。Prisma 客户端单例prisma 客户端 采用了 Next.js 热更新场景下的经典写法declare global { var prisma: PrismaClient | undefined; } export const prisma global.prisma || new PrismaClient({ log: env.NODE_ENV development ? [query, error, warn] : [error], }); if (env.NODE_ENV ! production) { global.prisma prisma; }开发环境缓存PrismaClient到global避免 HMR 反复创建连接池日志策略按环境区分开发环境打印query/error/warn生产环境只输出error。种子脚本package.json 的postinstall会执行prisma generate生成客户端并提供了一组 ts-node 种子命令pnpm seed # ts-node prisma/seed.ts 地理数据国家/州/城市 pnpm seed-salaries # ts-node prisma/seed-salaries.ts 薪资数据 pnpm seed-analysis # ts-node prisma/seed-analysis.ts 分析数据 pnpm seed-questions # ts-node prisma/seed-questions.ts 题目数据 pnpm seed-companies # ts-node prisma/seed-companies.ts 公司数据以 prisma/seed.ts 为例它加载 prisma/data/countries.json、states.json、cities.json按 Country → State → City 的顺序createMany入库并用skipDuplicates: true保证可重复执行幂等。API 层tRPC 路由的组织方式Portal 没有使用 REST 风格的手工 API 定义而是用 tRPC v9 的appRouter集中管理。入口 src/server/router/index.ts 展示了其组织约定export const appRouter createRouter() .transformer(superjson) // 支持 Date、Map 等复杂类型的序列化 // All keys should be delimited by a period and end with a period. .merge(auth., protectedExampleRouter) .merge(user., userRouter) .merge(todos., todosRouter) .merge(todos.user., todosUserRouter) .merge(companies., companiesRouter) .merge(locations., locationsRouter) .merge(resumes.resume., resumesRouter) // ... questions / offers 相关子路由 .merge(offers.admin., offerAdminRouter); export type AppRouter typeof appRouter;几个值得注意的工程细节点号命名约定源码注释明确规定「所有 key 用点分隔且以点结尾」如resumes.comments.votes.user.。这使得客户端调用形如trpc.resumes.resume.user.get.query()路由名即 URL 路径可读性极强superjson 转换器保证Date等 Prisma 返回类型在浏览器端无损往返AppRouter类型导出客户端通过trpc.createAppRouter()获得对全部查询/变异的自动补全与类型检查这就是 T3 Stack「端到端类型安全」的核心收益按域分文件questions/、offers/、resumes/子目录下每个聚合一个 router 文件如questions-answer-router.ts、offers-profile-router.ts与src/components下的组件目录结构镜像对应。HTTP 入口是 src/pages/api/trpc/[trpc].ts它把 Next.js API Route 挂接到appRouter上浏览器端则通过 src/utils/trpc.ts 封装的trpc/next集成配合 react-query发起类型化请求。认证NextAuth PrismaAdapter GitHub OAuth认证链路的核心文件是 src/pages/api/auth/[...nextauth].tsexport const authOptions: NextAuthOptions { adapter: PrismaAdapter(prisma), // 会话/账户持久化到 PostgreSQL callbacks: { session({ session, user }) { if (session.user ! null) { session.user.id user.id; // 把 user.id 注入 session } return session; }, }, pages: { signIn: /login }, // 自定义登录页 providers: [ GitHubProvider({ clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET, }), ], }; export default NextAuth(authOptions);其工作机制可拆解为Provider仅配置了 GitHub OAuth凭据来自上文 Zod 校验过的env.GITHUB_CLIENT_ID/SECRETAdapterPrismaAdapter(prisma)将Account/Session/User/VerificationToken四类记录写入 Prisma 数据库因此登录状态在服务重启、多实例部署下依然有效session 回调把user.id写进 session客户端组件即可从useSession()拿到用户 ID再驱动trpc.domain.user.*这类用户态路由例如todos.user.下的增删改自定义登录页pages.signIn指向/login对应 src/pages/login.tsx。路由保护方面原 README「Useful resources」中推荐的 NextAuth 服务端路由保护方式在本仓库中体现为 src/server/common/get-server-auth-session.ts服务端获取会话以及 src/server/router/protected-example-router.ts在 tRPC 层校验ctx.session的示例路由。前端则配合 src/pages/middleware 保护思路 与登录/注册页完成未登录跳转。开发工作流命令速查结合 package.json 的scriptsPortal 的日常开发命令如下仓库根目录为 pnpm workspaceworkspace 定义见 pnpm-workspace.yaml覆盖apps/*与packages/*# 在 apps/portal 目录下 pnpm dev # next dev 启动开发服务器 pnpm build # next build 生产构建 pnpm start # next start 运行生产构建 pnpm lint # 基于 vite-plus 的 lintvp lint next.config.mjs src pnpm tsc # 类型检查 pnpm seed # 见上文种子脚本构建期的类型与依赖体系还有两处值得说明共享 tsconfigtsconfig.jsonextends tih/tsconfig/nextjs.json该包来自 packages/tsconfig workspace 包同时配置了路径别名~/*: [*]相对src因此源码中~/env/server.mjs、~/server/db/client这类导入均解析到src下共享 tailwind 配置tailwind.config.cjs 复用 packages/tailwind-config/tailwind.config.js并额外把packages/ui的组件源码加入content扫描范围——注释解释这样做是为了「直接提取 UI 包样式而非导入其生成好的 CSS以避免样式顺序问题」ts-node 兼容tsconfig 中单独配置了ts-node: { transpileOnly: true, compilerOptions: { module: CommonJS } }正是为了让pnpm seed*系列脚本能在 CommonJS 模式下直接运行 TS 种子文件。部署Vercel 与 Docker原 README 推荐两类部署方式。Vercel官方推荐的步骤是将代码推送到 GitHub 仓库 → 用 GitHub 账号登录 Vercel → 创建项目并导入该仓库 → 添加环境变量对应 schema.mjs 中的 8 个服务端变量→ 点击 Deploy此后每次 push 都会自动重新部署。仓库中的 vercel.json 是这套流程的唯一定制配置{ github: { silent: true } }即不在 GitHub 侧生成部署状态检查部署状态只由 Vercel 平台跟踪。由于DATABASE_URL指向外部 PostgreSQL且postinstall会触发prisma generate在 Vercel 环境变量的配置中必须与本地.env保持一致尤其NEXTAUTH_URL要填最终公网地址。DockerREADME 同时指出可以将该 T3 Stack 容器化部署具体做法参考 create-t3-app 官方的 Docker 部署文档外部链接此处不展开。仓库当前未内置 Dockerfile容器化属于可选路径。小结从 Portal 看 T3 Stack 的分层回顾 Portal 的结构T3 Stack 的分层在 tech-interview-handbook 这个项目里体现得非常完整层关键文件职责环境变量src/env/schema.mjs、src/env/server.mjsZod 模式 启动即校验框架配置next.config.mjs、tailwind.config.cjs、tsconfig.jsonESM/CJS 类型安全配置、共享 tsconfig/Tailwind数据层prisma/schema.prisma、src/server/db/client.tsPostgreSQL 单例客户端 迁移/种子API 层src/server/router/index.ts、src/pages/api/trpc/[trpc].tstRPC 路由聚合 superjson 类型导出认证src/pages/api/auth/[...nextauth].tsNextAuth PrismaAdapter GitHub OAuth部署vercel.jsonVercel 自动部署配置如果你想在自己的项目里复刻这套架构最短路径是用 create-t3-app 初始化 → 按schema.mjs的范式定义环境变量 → 用 Prisma 建立数据模型与迁移 → 按点号命名约定组织 tRPC 路由 → 接入 PrismaAdapter 完成认证 → 用vercel.json对接 Vercel。Portal 的每个环节都有对应的源码文件可供对照这也是它作为 T3 Stack 参考实现的价值所在。【免费下载链接】tech-interview-handbookCurated coding interview preparation materials for busy software engineers项目地址: https://gitcode.com/GitHub_Trending/te/tech-interview-handbook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考