首页
/ Resume Matcher 前端架构实战指南:基于 CLAUDE.md 的 Next.js 16 工程全景解读

Resume Matcher 前端架构实战指南:基于 CLAUDE.md 的 Next.js 16 工程全景解读

2026-09-10 10:15:42作者:裘晴惠Vivianne

本指南以 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.tstranslate() 直接从后端取数,千万不要给它们加 '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 / apiDeleteAPI_URLAPI_BASEgetUploadUrl()
    • Base URL 来自 NEXT_PUBLIC_API_URL(默认 '/'),API_BASE 据此变为 /api/v1
    • 服务端/ 开头的相对 base 会被重写为 http://127.0.0.1:8000/api/v1INTERNAL_API_ORIGIN);在浏览器端使用相对路径,由 next.config.ts 的 rewrites 代理到 BACKEND_ORIGIN
    • 默认请求超时 240_000ms(与后端 wait_for 硬限制一致),AbortError 会转为友好的 "Request timed out" 提示。
  • lib/api/resume.ts — 简历/职位:上传、improve / improve.preview / improve.confirm、拉取、列表、PATCH 更新、PDF URL + blob 下载、删除、求职信/外联信生成与更新、重命名、重试处理、拉取 JD。
  • lib/api/config.ts — LLM 配置、testLlmConnection、系统 /status、功能开关、提示词配置、功能提示词(对 422 missing_placeholdersFeaturePromptsError)、按提供商管理的 API 密钥(每个提供商的密钥独立持久化,切换活跃提供商不会清掉另一个的密钥;服务端加密存储)、语言配置、resetDatabasePROVIDER_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.mddocs/agent/apis/api-flow-maps.md

三层超时协议:一个 env 变量驱动

这是源码级最值得注意的设计。在 lib/api/client.tsnext.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 库

  • StatusCacheProviderlib/context/status-cache.tsx)— 缓存 /status 结果(LLM 健康状态 30 分钟、DB 统计 5 分钟过期),并支持乐观计数器更新。通过 useStatusCache() / useIsStatusStale() 消费。
  • LanguageProviderlib/context/language-context.tsx)— UI 与内容语言,localStorage + 后端同步。通过 useLanguage() 消费。

一个容易被误导的点:tracker 看板的状态是本地持有的——components/tracker/kanban-board.tsxuseState 保存列数据,并独占唯一的 @dnd-kit DndContext。不存在 TrackerProvider / tracker context,CLAUDE.md 明确说“别去找它”。

i18n 国际化:一套引擎、两套语言、一条构建红线

两套独立设置

在 Settings 里可以独立配置两种语言:

  • UI 语言uiLanguage)— 界面文案,仅客户端,存 localStorage;
  • 内容语言contentLanguage)— LLM 写简历/求职信时使用的语言,持久化到后端。

引擎实现(无外部 i18n 库,纯 JSON)

  • i18n/config.tslocalesdefaultLocale='en'localeNameslocaleFlags
  • lib/i18n/messages.ts — 静态导入每个 locale 的 JSON,关键类型在这里
  • lib/i18n/translations.tsuseTranslations() 返回 { t, messages, locale }t('a.b.c', params) 做点路径查找 + {placeholder} 替换。
  • lib/i18n/server.ts — 服务端/打印页使用的 translate(locale, key, params)
  • lib/i18n/utils.tsgetNestedValue(点路径取值,找不到返回原 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 文件(当前为 eneszhjapt-BRfr,共 6 份)中以相同结构同步。npm run dev 可能容忍漂移,但 build 不会。

一个文档与代码的差异点值得注意:CLAUDE.md 正文写“source of truth = i18n/config.ts,支持 en/es/zh/ja/pt”,并提示旧表格漏了 pt;而实际代码(i18n/config.tsmessages.ts)已进一步扩展为 6 个 locale,包含 frmessages/fr.json),并建议“以代码为准”。

双保险守卫

该约束有两层自动化守护,形成闭环:

  1. 测试层tests/i18n-locale-parity.test.ts 对每个 locale 做结构同构比对——不仅查 key 存在性,还比较 JSON 节点类型(object/number/array vs string),因为“key 都在但形状不同”同样会打断 next build。多余 key 仅告警不失败(与下方脚本保持一致)。
  2. 提交层scripts/check_locale_parity.py 是同一检查的 Python 版,pre-push 钩子在无 Node 环境下也能运行。

样式体系:Swiss 国际主义风格(强制)

CLAUDE.md 将 Swiss 设计系统标为 MANDATORY,所有 UI 改动都必须遵循。设计包见 docs/portable/swiss-design-system/README.md 及其 tokenscomponentsanti-patternslayouts

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-backgroundbg-canvas
Ink(正文) #000000 text-inktext-ink-soft
Hyper Blue(主色/链接) #1D4ED8 text-primarybg-primary、ring
Signal Green(成功) #15803D text-success
Alert Orange(警告) #F97316 text-warning
Alert Red(错误) #DC2626 text-destructive
Neutrals paper-tintsteel-greyink-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.cssswiss-singleswiss-two-columnmodernmodern-two-column),模板类型与设置在 lib/types/template-settings.ts,相关文档见 docs/agent/features/resume-templates.mddocs/agent/design/template-system.mddocs/agent/design/pdf-template-guide.mddocs/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.mduv sync + uv run uvicorn app.main:app --reload --port 8000)。前端通过 next.config.ts rewrites 代理 /api/*/docs/redoc/openapi.jsonBACKEND_ORIGIN

非协商前端规则

CLAUDE.md 列出了七条硬性规则,任何提交前都必须满足:

  1. 所有 UI 必须遵循 Swiss 国际主义风格(rounded-none、1px 黑边框、硬阴影、品牌令牌)。
  2. 提交前必须跑 npm run lintnpm run format
  3. 任何 en.json key 变更必须同步到全部 locale 文件,否则构建失败。
  4. Textarea 回车键模式——当 textarea 位于一个“按回车即提交”的 dialog/form 内时,要阻止事件冒泡(代码已确认,如 app/(default)/tailor/page.tsx):
    const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
      if (e.key === 'Enter') e.stopPropagation();
    };
    
  5. 所有后端访问走 lib/api/*(组件内禁止裸 fetch)。
  6. 用户/LLM 产出的 HTML 必须先用 sanitizeHtmllib/utils/html-sanitizer.ts,DOMPurify + 白名单 strong/em/u/a)清洗,才能交给 dangerouslySetInnerHTML
  7. Next.js 性能模式为必读材料:docs/portable/nextjs-performance/README.md + checklist

关键陷阱(Key Gotchas)

  • Lucide 导入路径:热路径/页面代码从深路径 lucide-react/dist/esm/icons/x 导入图标以避开 barrel;next.config.tsoptimizePackageImports 同时摇树 lucide/tiptap/dnd-kit。保持一致性,别退回 barrel 导入。
  • 240s 超时:AI 调用默认超时,与后端匹配;不要为 improve/regenerate 流程缩短超时
  • print/* 是服务端组件:从 searchParams 读模板设置、经内部 origin 调后端,务必保持服务端属性。
  • 两处 i18ni18n/(配置)与 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/):

  • i18ni18n-utils.test.tsgetNestedValue 点路径 + applyParams 替换)、i18n-locale-parity.test.ts(所有 messages/*.json 必须与 en.json 结构一致——对构建事故的套内守卫)。
  • lib/utilskeyword-matcher.test.tssection-helpers.test.tshtml-sanitizer.test.ts(XSS 白名单)、download-utils.test.ts
  • lib/apiapi-client.test.ts(URL 解析、超时/AbortError,fetch 被打桩)。
  • componentsdiff-preview-modal.test.tsxregenerate-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 末章按任务给出了精确的文档索引,这里转换为仓库根相对路径供继续深入:

任务 文档
前端架构 / 用户流 docs/agent/architecture/frontend-architecture.mddocs/agent/architecture/frontend-workflow.md
编码规范 docs/agent/coding-standards.md
API 契约 docs/agent/apis/front-end-apis.mddocs/agent/apis/api-flow-maps.md
范围 / 原则 / 流程 docs/agent/scope-and-principles.mddocs/agent/workflow.md
Swiss 设计系统(强制) docs/portable/swiss-design-system/README.mdtokenscomponentsanti-patternslayouts
Next.js 性能(必读) docs/portable/nextjs-performance/README.mdchecklist
i18n docs/agent/features/i18n.mddocs/agent/features/i18n-preparation.md
简历模板 / PDF docs/agent/features/resume-templates.mddocs/agent/design/template-system.mddocs/agent/design/pdf-template-guide.mddocs/agent/features/adding-resume-templates.md
自定义分节 docs/agent/features/custom-sections.md
AI 增强 docs/agent/features/enrichment.md
JD 匹配 docs/agent/features/jd-match.md

范围外(无明确请求不要动)

CLAUDE.md 明确列出前端侧不鼓励擅自修改的领域:.github/workflows/ 与 CI/CD、Docker 行为;现有测试(不得移除/禁用);next.config.ts 的 rewrites / 代理行为(除非任务本身就是关于它)。这与后端 CLAUDE.md 的“范围外”条款遥相呼应——保持这两份文档作为后续开发的边界,能最大限度避免破坏仓库既有的质量门禁。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.15 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
931
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
605
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23