Resume Matcher 前端架构实战指南:基于 CLAUDE.md 的 Next.js 16 工程全景解读
本指南以
apps/frontend/CLAUDE.md为骨架,结合apps/frontend下的真实源码、配置与测试,系统拆解 Resume Matcher 前端(Next.js 16 + React 19 + TypeScript strict + Tailwind CSS v4)的路由组织、数据流、i18n 国际化约束、Swiss 设计系统、命令体系与测试策略。读完你既能按图索骥地在本地跑起前端,也能在改动en.json、新增路由或接入后端 API 时避开真实的构建陷阱。
技术栈与工程定位
Resume Matcher 的前端是一个不依赖任何 UI 组件库的 Next.js 应用,核心技术栈在 apps/frontend/package.json 中完整声明:
| 维度 | 选型 |
|---|---|
| 框架 | Next.js 16(App Router + Turbopack,output: 'standalone') |
| UI 层 | React 19 + TypeScript(strict 模式) |
| 样式 | Tailwind CSS v4(在 CSS 中配置,无 tailwind.config,见 app/(default)/css/globals.css 的 @theme inline) |
| 组件库 | 无,全部为手写 components/ui 原语 |
| 富文本 | Tiptap 3(@tiptap/starter-kit 等,见 package.json 依赖) |
| 拖拽 | @dnd-kit(core / sortable / utilities) |
| 图标 | lucide-react(^0.575.0,配合 optimizePackageImports 摇树) |
| 测试 | Vitest 4 + jsdom + Testing Library |
| 别名 | @/* → apps/frontend/* |
前端与后端是两个独立进程:后端 FastAPI 跑在 :8000,前端 Next.js 跑在 :3000。前端通过 next.config.ts 的 rewrites 把 /api/*、/docs、/redoc、/openapi.json 代理到 BACKEND_ORIGIN(默认 http://127.0.0.1:8000)。因此一个值得记住的约束是:不要创建 app/api/ 路由——文件系统路由会遮蔽代理(next.config.ts 的注释与 CLAUDE.md 都明确警告了这一点)。
路由与页面地图
App Router 全部挂在 app/ 下,其中 (default) 路由组包裹主应用(带 Provider 嵌套),print/* 是无 Provider 的纯服务端渲染页面(供无头 Chromium 抓取 PDF)。CLAUDE.md 给出了完整路由表:
| 路由 | 文件 | 类型 | 用途 |
|---|---|---|---|
/ |
app/(default)/page.tsx |
Server | 落地页,渲染 <Hero/> |
/dashboard |
app/(default)/dashboard/page.tsx |
Client | 简历列表、上传、删除、重试、状态网格 |
/builder |
app/(default)/builder/page.tsx |
Client 包装 → components/builder/resume-builder.tsx |
母版简历编辑器(表单、拖拽分节、模板、AI 重写、求职信/外联信) |
/tailor |
app/(default)/tailor/page.tsx |
Client | 粘贴 JD → 预览/确认定制简历(diff 弹窗) |
/tracker |
app/(default)/tracker/page.tsx |
Client | 看板式投递追踪器,7 列看板(拖拽、批量操作、手动新增) |
/settings |
app/(default)/settings/page.tsx |
Client | LLM 提供商/模型/密钥、各提供商 API 密钥、功能开关、提示词、语言、重置数据库 |
/resumes/[id] |
app/(default)/resumes/[id]/page.tsx |
Client | 查看单份简历、下载 PDF、重命名、AI 增强弹窗 |
/print/resumes/[id] |
app/print/resumes/[id]/page.tsx |
Server | 仅供打印的简历渲染(从 searchParams 读取模板设置 + lang) |
/print/cover-letter/[id] |
app/print/cover-letter/[id]/page.tsx |
Server | 仅供打印的求职信渲染 |
布局分层清晰:app/layout.tsx(根布局)负责接入字体(Geist + Space Grotesk)与全局 CSS;app/(default)/layout.tsx/layout.tsx) 嵌套四层 Provider:
StatusCacheProvider → LanguageProvider → ResumePreviewProvider → LocalizedErrorBoundary
CLAUDE.md 特别强调:除 print/* 外大多数页面是 'use client';print/* 页面刻意保持为服务端组件,通过 API_BASE + lib/i18n/server.ts 的 translate() 直接从后端取数,千万不要给它们加 'use client'。
目录结构速览
CLAUDE.md 用一棵目录树交代了整体布局(已在仓库中逐一核实存在):
app/ # 路由(见上表)
components/
ui/ # 原语组件:button、input、textarea、dialog、dropdown、
# card、retro-tabs、toggle-switch、confirm-dialog、
# rich-text-editor(Tiptap)、link-dialog、label
builder/ # builder 页面 UI + forms/(按分节拆分的表单组件)
dashboard/ # 简历列表/卡片、上传弹窗
tailor/ # diff-preview-modal
tracker/ # kanban-board、kanban-column、application-card、
# card-detail-modal、bulk-action-bar、
# manual-add-application-dialog、reorder.ts(纯函数 planMove)
enrichment/ # AI 增强向导模态框/步骤
resume/ # 简历渲染模板(单栏/双栏/modern)+ styles/*.module.css
preview/ # 分页 A4/Letter 预览(use-pagination.ts)
home/ # hero、swiss-grid
settings/ # api-key-menu
common/ # error-boundary、resume_previewer_context
lib/
api/ # 后端客户端(见数据流章节)
i18n/ # 翻译引擎(见 i18n 章节)
context/ # status-cache、language-context
utils/ # download、html-sanitizer、keyword-matcher、section-helpers
types/ # template-settings、lucide.d.ts
config/version.ts # APP_VERSION / codename
constants/page-dimensions.ts
hooks/ # use-file-upload、use-regenerate-wizard、use-enrichment-wizard
i18n/config.ts # locale 列表 + 名称/旗帜(注意:与 lib/i18n 不同)
messages/ # en/es/zh/ja/pt-BR 等 JSON(见 i18n 章节)
tests/ # vitest(见测试章节)
注意 i18n/(仅配置)与 lib/i18n/(引擎)是两个不同的位置,CLAUDE.md 在 Key Gotchas 里专门警告过不要混淆。
数据流:page → hook → lib/api → backend
CLAUDE.md 立下的第一铁律是:所有后端调用必须走 lib/api/,组件内严禁直接 fetch 后端。
lib/api/client.ts是唯一真相源,导出apiFetch / apiPost / apiPatch / apiPut / apiDelete、API_URL、API_BASE、getUploadUrl():- Base URL 来自
NEXT_PUBLIC_API_URL(默认'/'),API_BASE据此变为/api/v1; - 在服务端,
/开头的相对 base 会被重写为http://127.0.0.1:8000/api/v1(INTERNAL_API_ORIGIN);在浏览器端使用相对路径,由next.config.ts的 rewrites 代理到BACKEND_ORIGIN; - 默认请求超时 240_000ms(与后端
wait_for硬限制一致),AbortError会转为友好的 "Request timed out" 提示。
- Base URL 来自
lib/api/resume.ts— 简历/职位:上传、improve / improve.preview / improve.confirm、拉取、列表、PATCH 更新、PDF URL + blob 下载、删除、求职信/外联信生成与更新、重命名、重试处理、拉取 JD。lib/api/config.ts— LLM 配置、testLlmConnection、系统/status、功能开关、提示词配置、功能提示词(对 422missing_placeholders抛FeaturePromptsError)、按提供商管理的 API 密钥(每个提供商的密钥独立持久化,切换活跃提供商不会清掉另一个的密钥;服务端加密存储)、语言配置、resetDatabase。PROVIDER_INFO列出支持的提供商与默认模型。lib/api/enrichment.ts— AI 增强(analyze/enhance/apply)与 AI 重写(regenerate/apply-regenerated)。lib/api/tracker.ts— 投递追踪 CRUD/批量操作:分组列表、详情(JD + 简历)、手动新增、状态/位置/备注 PATCH、批量移动、删除、批量删除。lib/api/index.ts— barrel 再导出(注意:并非所有函数都被再导出,部分函数直接从./resume/./config/./enrichment导入)。
契约对齐:lib/api/* 的接口与后端 Pydantic schema 一一对应,详见 docs/agent/apis/front-end-apis.md 与 docs/agent/apis/api-flow-maps.md。
三层超时协议:一个 env 变量驱动
这是源码级最值得注意的设计。在 lib/api/client.ts、next.config.ts 与后端 REQUEST_TIMEOUT_SECONDS 之间,存在一条必须对齐的超时链:
前端 AbortController(lib/api/client.ts)
← 一致 → Next.js 代理 proxyTimeout(next.config.ts)
← 一致 → 后端 asyncio.wait_for(REQUEST_TIMEOUT_SECONDS)
三者都由 NEXT_PUBLIC_REQUEST_TIMEOUT_MS 驱动,且被夹在 [30_000, 1_800_000](30 秒~30 分钟)区间内,默认 240_000。注释明确说明“最短的一层先中止”(the shortest layer aborts first),所以三层必须同步修改。如果你在本地跑 Ollama 这类慢模型,就同时调大 NEXT_PUBLIC_REQUEST_TIMEOUT_MS 和后端 REQUEST_TIMEOUT_SECONDS——这正是超时错误信息里给出的官方建议。
共享客户端状态:Context 而非请求库
CLAUDE.md 澄清了两类全局状态,均为 React Context 实现,不引入任何 fetch 库:
StatusCacheProvider(lib/context/status-cache.tsx)— 缓存/status结果(LLM 健康状态 30 分钟、DB 统计 5 分钟过期),并支持乐观计数器更新。通过useStatusCache()/useIsStatusStale()消费。LanguageProvider(lib/context/language-context.tsx)— UI 与内容语言,localStorage + 后端同步。通过useLanguage()消费。
一个容易被误导的点:tracker 看板的状态是本地持有的——components/tracker/kanban-board.tsx 用 useState 保存列数据,并独占唯一的 @dnd-kit DndContext。不存在 TrackerProvider / tracker context,CLAUDE.md 明确说“别去找它”。
i18n 国际化:一套引擎、两套语言、一条构建红线
两套独立设置
在 Settings 里可以独立配置两种语言:
- UI 语言(
uiLanguage)— 界面文案,仅客户端,存 localStorage; - 内容语言(
contentLanguage)— LLM 写简历/求职信时使用的语言,持久化到后端。
引擎实现(无外部 i18n 库,纯 JSON)
i18n/config.ts—locales、defaultLocale='en'、localeNames、localeFlags。lib/i18n/messages.ts— 静态导入每个 locale 的 JSON,关键类型在这里。lib/i18n/translations.ts—useTranslations()返回{ t, messages, locale };t('a.b.c', params)做点路径查找 +{placeholder}替换。lib/i18n/server.ts— 服务端/打印页使用的translate(locale, key, params)。lib/i18n/utils.ts—getNestedValue(点路径取值,找不到返回原 key 字符串,不抛异常)与applyParams({param}正则替换)。
⚠️ 会打断构建的硬约束
lib/i18n/messages.ts 中的核心代码:
export type Messages = typeof en; // 形状派生自 en.json
const allMessages: Record<Locale, Messages> = { en, es, zh, ja, pt, fr };
因为每个 locale 都被标注为 Messages(即 en.json 的精确形状),每个 locale JSON 必须在结构上与 en.json 完全一致。往 en.json 加一个 key,如果不同步到其余 locale 文件,生产环境的 tsc / next build 会直接失败——CLAUDE.md 记录过一次真实事故:恰因该问题导致的构建崩溃。
**因此翻译的修改守则是:**在 en.json 增/删/改任何 key,都必须在全部 locale 文件(当前为 en、es、zh、ja、pt-BR、fr,共 6 份)中以相同结构同步。npm run dev 可能容忍漂移,但 build 不会。
一个文档与代码的差异点值得注意:CLAUDE.md 正文写“source of truth = i18n/config.ts,支持 en/es/zh/ja/pt”,并提示旧表格漏了
pt;而实际代码(i18n/config.ts与messages.ts)已进一步扩展为 6 个 locale,包含fr(messages/fr.json),并建议“以代码为准”。
双保险守卫
该约束有两层自动化守护,形成闭环:
- 测试层:
tests/i18n-locale-parity.test.ts对每个 locale 做结构同构比对——不仅查 key 存在性,还比较 JSON 节点类型(object/number/array vs string),因为“key 都在但形状不同”同样会打断next build。多余 key 仅告警不失败(与下方脚本保持一致)。 - 提交层:
scripts/check_locale_parity.py是同一检查的 Python 版,pre-push 钩子在无 Node 环境下也能运行。
样式体系:Swiss 国际主义风格(强制)
CLAUDE.md 将 Swiss 设计系统标为 MANDATORY,所有 UI 改动都必须遵循。设计包见 docs/portable/swiss-design-system/README.md 及其 tokens、components、anti-patterns、layouts。
Tailwind v4 在 CSS 内配置(app/(default)/css/globals.css 的 @theme inline),没有 tailwind.config,PostCSS 使用 @tailwindcss/postcss,仅浅色主题(源码注释明确移除了未完成的暗色映射)。
CLAUDE.md 给出的品牌令牌表(已在 globals.css 中核实):
| Token | 值 | Tailwind 用法 |
|---|---|---|
| Canvas / background | #F0F0E8 |
bg-background、bg-canvas |
| Ink(正文) | #000000 |
text-ink、text-ink-soft |
| Hyper Blue(主色/链接) | #1D4ED8 |
text-primary、bg-primary、ring |
| Signal Green(成功) | #15803D |
text-success |
| Alert Orange(警告) | #F97316 |
text-warning |
| Alert Red(错误) | #DC2626 |
text-destructive |
| Neutrals | paper-tint、steel-grey、ink-soft(OKLCH) |
用这些,不要随手用灰色 |
风格约定:全局 rounded-none(不定义任何圆角 token,直角是有意为之);1px 黑色边框 border border-black;硬偏移阴影 shadow-sw-xs … shadow-sw-xl(纯色实体墨迹、无模糊,例如 shadow-sw-default: 4px 4px 0px 0px #000000);字体分工:衬线标题 / font-sans(Geist)正文 / font-mono(Space Grotesk)元数据。
简历渲染模板拥有独立的 CSS Modules,位于 components/resume/styles/(_tokens.css、swiss-single、swiss-two-column、modern、modern-two-column),模板类型与设置在 lib/types/template-settings.ts,相关文档见 docs/agent/features/resume-templates.md、docs/agent/design/template-system.md、docs/agent/design/pdf-template-guide.md 与 docs/agent/features/adding-resume-templates.md。
常用命令
CLAUDE.md 在 apps/frontend 下给出的完整命令集(与 package.json 的 scripts 一一对应):
# 在 apps/frontend 目录下执行
npm install
npm run dev # next dev --turbopack(:3000)
npm run build # next build(会跑 tsc —— i18n 形状漂移就在这里暴露)
npm run start
npm run lint # eslint .
npm run format # prettier --write .
npm run test # vitest run
后端需在 :8000 单独运行(详见 apps/backend/CLAUDE.md:uv sync + uv run uvicorn app.main:app --reload --port 8000)。前端通过 next.config.ts rewrites 代理 /api/*、/docs、/redoc、/openapi.json 到 BACKEND_ORIGIN。
非协商前端规则
CLAUDE.md 列出了七条硬性规则,任何提交前都必须满足:
- 所有 UI 必须遵循 Swiss 国际主义风格(
rounded-none、1px 黑边框、硬阴影、品牌令牌)。 - 提交前必须跑
npm run lint和npm run format。 - 任何
en.jsonkey 变更必须同步到全部 locale 文件,否则构建失败。 - Textarea 回车键模式——当 textarea 位于一个“按回车即提交”的 dialog/form 内时,要阻止事件冒泡(代码已确认,如
app/(default)/tailor/page.tsx):const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { if (e.key === 'Enter') e.stopPropagation(); }; - 所有后端访问走
lib/api/*(组件内禁止裸fetch)。 - 用户/LLM 产出的 HTML 必须先用
sanitizeHtml(lib/utils/html-sanitizer.ts,DOMPurify + 白名单strong/em/u/a)清洗,才能交给dangerouslySetInnerHTML。 - Next.js 性能模式为必读材料:docs/portable/nextjs-performance/README.md + checklist。
关键陷阱(Key Gotchas)
- Lucide 导入路径:热路径/页面代码从深路径
lucide-react/dist/esm/icons/x导入图标以避开 barrel;next.config.ts的optimizePackageImports同时摇树 lucide/tiptap/dnd-kit。保持一致性,别退回 barrel 导入。 - 240s 超时:AI 调用默认超时,与后端匹配;不要为 improve/regenerate 流程缩短超时。
print/*是服务端组件:从searchParams读模板设置、经内部 origin 调后端,务必保持服务端属性。- 两处 i18n:
i18n/(配置)与lib/i18n/(引擎),别混淆。 - ESLint 特殊处理:禁用了
react-hooks/set-state-in-effect(既有 effect 用于同步 props/DOM 测量);Prettier 规则经 ESLint 执行(prettier/prettier: error)。
测试体系
vitest(jsdom)+ Testing Library,配置见 vitest.config.ts(含 @ 别名与 jsdom 环境、vitest.setup.ts 自动清理),运行 npm run test。CLAUDE.md 强调测试在范围内(见 docs/agent/testing-strategy.md),且必须确定性、反表演——目标坏了测试就得红。
测试规格(tests/):
- i18n —
i18n-utils.test.ts(getNestedValue点路径 +applyParams替换)、i18n-locale-parity.test.ts(所有messages/*.json必须与en.json结构一致——对构建事故的套内守卫)。 - lib/utils —
keyword-matcher.test.ts、section-helpers.test.ts、html-sanitizer.test.ts(XSS 白名单)、download-utils.test.ts。 - lib/api —
api-client.test.ts(URL 解析、超时/AbortError,fetch被打桩)。 - components —
diff-preview-modal.test.tsx、regenerate-wizard.test.tsx。
纯逻辑(i18n、utils、api)直接以打桩的 fetch/t 测试;组件用 Testing Library 渲染。locale-parity 测试与 scripts/check_locale_parity.py 互为镜像(后者供 pre-push 钩子在无 Node 环境运行);本地 pre-push 门禁在 Node 可用时也会跑整个 vitest 套件(git config core.hooksPath .githooks)。
按任务分类的文档导航
CLAUDE.md 末章按任务给出了精确的文档索引,这里转换为仓库根相对路径供继续深入:
范围外(无明确请求不要动)
CLAUDE.md 明确列出前端侧不鼓励擅自修改的领域:.github/workflows/ 与 CI/CD、Docker 行为;现有测试(不得移除/禁用);next.config.ts 的 rewrites / 代理行为(除非任务本身就是关于它)。这与后端 CLAUDE.md 的“范围外”条款遥相呼应——保持这两份文档作为后续开发的边界,能最大限度避免破坏仓库既有的质量门禁。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K638- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python250
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java301
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java210
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript190
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python300