Skip to content

前端技术栈与应用结构

本章目标:

  • 讲清 DeerFlow 前端为什么选择 Next.js App Router,并理解 src/app(路由层)、src/core(业务逻辑层)、src/components(UI 层)三层划分的边界。
  • 掌握 (auth)[lang]/docsworkspaceblogmockapi 六大路由分区各自的职责与守卫机制。
  • 理解前端如何通过 next.config.js 的 rewrites 经 nginx 连到后端 Gateway,以及 NEXT_PUBLIC_* 环境变量与构建配置的解析路径。

TL;DR

DeerFlow 前端是一个 Next.js 16 + React 19 + TypeScript 5.8 + Tailwind 4 的有状态聊天应用。代码按 app/(路由与布局守卫)、core/(线程流、API 单例、i18n、设置等纯业务逻辑)、components/(ui/ai-elements 自动生成的原子组件 + workspace/landing 业务组件)三层划分。生产环境下后端 URL 默认留空,由 next.config.jsrewrites()/api/* 反代到内部 Gateway(经 nginx),前端无需感知后端地址。环境变量经 src/env.js(@t3-oss/env-nextjs + Zod)做构建期校验。

Overview

DeerFlow 前端选 Next.js App Router 而非传统 SPA,核心动机有三点:

第一,布局即守卫。聊天页需要登录、首次安装页需要拦截、文档页需要多语言外壳——这些都用 App Router 的嵌套 layout.tsx 在 Server Component 中完成。workspace/layout.tsx 在渲染前调用 getServerSideUser(),根据返回的 tag 直接 redirect("/login")redirect("/setup"),未鉴权用户连页面 JS 都拿不到 frontend/src/app/workspace/layout.tsx:15-29

第二,路由分区隔离关注点。落地页(静态营销)、文档/博客(Nextra MDX)、工作区(动态聊天)在交互模型上完全不同。App Router 用路由组 (auth)、动态段 [lang]、并行嵌套把它们物理隔开,每个分区有独立的 layout 和数据获取策略。

第三,core/components 分层让业务逻辑可测试。所有线程流、流式解析、API 调用都收敛进 src/core,以 React Hook(useThreadStreamuseThreads)对外暴露 frontend/src/core/threads/hooks.ts:161;组件只负责渲染。这样 tests/unit/ 能直接对 core 做单元测试,而无需挂载 React 树。

Architecture

前端代码全部位于 frontend/src/,顶层目录是稳定的三层结构外加若干辅助目录:

  • app/ — Next.js App Router 路由与布局,每个分区有独立 layout.tsx/page.tsx frontend/src/app/layout.tsx:15-28
  • core/ — 业务逻辑层,21 个子模块(threads/api/i18n/auth/settings/memory/skills/mcp 等),每个带 index.ts 桶导出。
  • components/ — 四类组件:ui/(45 个 Shadcn 原子)、ai-elements/(27 个 Vercel AI SDK 元素)、workspace/(聊天业务组件)、landing/(落地页区块)。
  • hooks/ — 跨页面共享 Hook(use-mobileuse-global-shortcuts)。
  • lib/ — 工具函数,cn()(clsx + tailwind-merge)与输入法处理。
  • content/ — Nextra 文档/博客的 MDX 源,按 en/zh 分语言。
  • styles/ — 全局 CSS,Tailwind v4 @import 语法 + CSS 变量主题。

Source 列表:

路径职责
路由层frontend/src/app/路由、布局守卫、API mock/proxy
业务层frontend/src/core/线程流、API 单例、i18n、设置
UI 层frontend/src/components/原子组件 + 业务组件
配置frontend/src/env.js环境变量 Zod 校验

Components / Subsystems

src/app 路由分区

根布局 app/layout.tsx 是全局外壳:它在 Server Component 中调用 detectLocaleServer() 解析语言 Cookie,再用 ThemeProvider + I18nProvider 包裹整个应用 frontend/src/app/layout.tsx:15-28

src/core 业务逻辑层

core 把所有非 UI 逻辑收敛,关键模块:

src/components UI 层

四类组件职责泾渭分明:ui/(45 个)与 ai-elements/(27 个)由 Shadcn / Vercel AI SDK registry 自动生成,ESLint 显式忽略,不可手改 frontend/eslint.config.js:10-16;workspace/ 是聊天业务组件(messages/artifacts/input-box/command-palette/workspace-sidebar 等);landing/ 是落地页区块,app/page.tsx 顺序组合 Header/Hero/CaseStudySection/SkillsSectionfrontend/src/app/page.tsx:10-24。registry 来源在 components.json 中声明 frontend/components.json:21-25

Data Flow

前端发起的 API 调用在生产环境下不直连后端,而是经 Next.js rewrites + nginx 反代到 Gateway。下面是一次聊天请求的端到端解析:

关键点:next.config.jsrewrites()getInternalServiceURL()DEER_FLOW_INTERNAL_GATEWAY_BASE_URL(缺省 http://127.0.0.1:8001),当 NEXT_PUBLIC_LANGGRAPH_BASE_URL 未设置时,把 /api/langgraph${gatewayURL}/api,并用兜底规则 /api/:path*${gatewayURL}/api/:path* 覆盖 models/threads/memory 等所有未单独开关的路由;此兜底规则必须排在 langgraph 规则之后以保留公共前缀 frontend/next.config.js:24-74

速查表

src/app 路由分区矩阵

路由类型守卫/数据源Source
/(landing)静态营销无,纯组件组合page.tsx:10-24
(auth)/login (auth)/setup鉴权getServerSideUser 重定向(auth)/layout.tsx:16-46
[lang]/docsNextra MDXgetPageMap + 静态参数docs/layout.tsx:27-50
workspace/chats/[thread_id]动态聊天登录守卫 + 上下文链chats layout.tsx:7-19
workspace/agents/...指定 agent 聊天同上agent layout.tsx:7-19
blogNextra MDXgetBlogIndexDatablog/layout.tsx:8-22
api/memory服务端代理透传到后端api/memory/route.ts:10-35
mock/api/*假后端硬编码响应mock models/route.ts:1-25

src/core 模块矩阵

模块职责Source
apiLangGraph SDK 单例 + CSRF 注入api-client.ts:34-72
config后端/LangGraph base URL 解析config/index.ts:21-44
threads线程流 Hook 与状态threads/hooks.ts:161
i18n多语言解析与 Provideri18n/server.ts:6-30
auth服务端用户判定与守卫workspace/layout.tsx:15-29
settings/memory/skills/mcp/uploads各领域逻辑(见第 31 章)core/ 桶导出

Configuration

NEXT_PUBLIC_* 与服务端环境变量

环境变量经 src/env.js@t3-oss/env-nextjs + Zod 在构建期校验,SKIP_ENV_VALIDATION=1 可跳过(Docker 构建用) frontend/src/env.js:4-50

变量作用域默认说明Source
NEXT_PUBLIC_BACKEND_BASE_URLclient空(走 nginx)后端 REST 直连地址,api/memory 代理也用它env.js:22
NEXT_PUBLIC_LANGGRAPH_BASE_URLclient空(走 rewrites)LangGraph 流式直连地址env.js:23
NEXT_PUBLIC_STATIC_WEBSITE_ONLYclient"true" 时禁用输入框,演示站模式env.js:24
DEER_FLOW_INTERNAL_GATEWAY_BASE_URLserverhttp://127.0.0.1:8001rewrites 反代目标next.config.js:26-29
NODE_ENVserverdevelopment运行环境枚举env.js:11-13

构建与质量配置

配置关键点Source
next.config.jsi18n locales en/zh、Nextra 包裹、rewrites 反代next.config.js:18-77
tsconfig.jsonstrict@/*src/*moduleResolution: Bundlertsconfig.json:12-33
eslint.config.js忽略 ui//ai-elements/,强制 import 排序eslint.config.js:10-82
vitest.config.ts单测匹配 tests/unit/**,@src 别名vitest.config.ts:5-14
playwright.config.tsChromium,启动时 SKIP_ENV_VALIDATION=1 + DEER_FLOW_AUTH_DISABLED=1playwright.config.ts:24-33
package.jsonNext 16 / React 19 / pnpm 10.26.2,pnpm check = lint + tscpackage.json:6-20

Common Pitfalls / Tips

  • 生产别配 NEXT_PUBLIC_* 后端地址。配了就绕过 nginx/rewrites 直连,容易跨域和 CSRF 失败;默认留空让 next.config.js 反代是推荐路径 frontend/next.config.js:31-71
  • 创建线程后不要用 router.push 跳转。聊天页用 history.replaceState 改 URL,用 Next.js 路由会导致组件重挂载、丢失流式状态 frontend/src/app/workspace/chats/[thread_id]/page.tsx:89-91
  • 别手改 ui/ai-elements/。它们由 registry 生成且被 ESLint 忽略,手改会在下次重新生成时被覆盖 frontend/eslint.config.js:10-16
  • 状态变更请求必须走封装的 fetch/SDK client。裸 fetch() 不带 X-CSRF-Token,Gateway 双提交校验会返回 403 frontend/src/core/api/fetcher.ts:39-55
  • i18n 用 locale Cookie 单一来源。服务端 detectLocaleServer 与客户端 I18nProvider 都读写同名 Cookie,保持 SSR/CSR 一致避免水合不匹配 frontend/src/core/i18n/context.tsx:23-26
  • 业务逻辑写进 core,组件只渲染。这样 tests/unit/ 能脱离 React 树直接测,且 import 别名统一用 @/

References

页面关系
03-系统整体架构本章是前端侧落地,03 章给出前后端整体边界与 nginx 拓扑
29-AI消息流与流式渲染承接本章 useThreadStream,深入流式事件解析与渲染
30-工作区与聊天界面展开本章 workspace 分区的聊天 UI 组件细节
31-前端核心服务层展开本章 src/core 各业务模块的实现

公司内部参考 · 由 claude-wiki-gen 基于源码自动生成的二次分析