为大型前端应用设计可扩展的架构——不是"用什么框架",而是如何组织代码、划分模块、管理依赖,让项目在 2 年后还能维护。
不要一上来就给"最佳实践",先问清楚:
问题为什么重要影响的决策团队规模?3 人和 30 人的架构完全不同是否需要严格的模块边界、代码审查流程预期生命周期?3 个月 MVP 和 3 年产品的架构不同是否需要考虑扩展性、技术债管理性能要求?ToB 后台和 ToC 电商的性能要求不同SSR/SSG、代码分割策略、CDN 策略是否多团队?影响模块划分和发布策略Monorepo、微前端、API 契约现有技术栈?存量项目迁移成本高渐进式重构 vs 推倒重来
小型项目(< 3 人,< 6 个月):
text
src/
├── components/ # 所有组件
├── pages/ # 路由页面
├── hooks/ # 自定义 hooks
├── utils/ # 工具函数
├── api/ # API 请求
└── types/ # 类型定义
中型项目(3-10 人,6 个月 - 2 年):
text
src/
├── features/ # 按功能域划分
│ ├── auth/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── api/
│ │ ├── types.ts
│ │ └── index.ts # 导出公共接口
│ ├── dashboard/
│ └── settings/
├── shared/ # 跨 feature 共享
│ ├── components/ # 通用组件(Button、Modal)
│ ├── hooks/
│ ├── utils/
│ └── types/
├── app/ # 应用层(路由、布局、全局状态)
│ ├── routes/
│ ├── layouts/
│ └── providers/
└── lib/ # 第三方库封装
├── axios.ts
├── react-query.ts
└── analytics.ts
大型项目(10+ 人,2 年+):
text
packages/ # Monorepo
├── apps/
│ ├── web/ # 主应用
│ ├── admin/ # 管理后台
│ └── mobile/ # 移动端(可选)
├── features/ # 业务功能包
│ ├── auth/
│ ├── payment/
│ └── analytics/
├── ui/ # 设计系统
│ ├── components/
│ ├── tokens/
│ └── themes/
├── shared/ # 共享工具
│ ├── utils/
│ ├── types/
│ ├── hooks/
│ └── api-client/
└── config/ # 共享配置
├── eslint/
├── tsconfig/
└── tailwind/
分层架构(推荐):
text
app/ # 应用层:路由、布局、全局状态
↓ 可以依赖
features/ # 功能层:业务逻辑
↓ 可以依赖
shared/ # 共享层:通用组件、工具
↓ 可以依赖
lib/ # 基础设施层:第三方库封装
依赖规则(用 ESLint 强制):
js
// .eslintrc.js
{
rules: {
'no-restricted-imports': ['error', {
patterns: [
{
group: ['@/features/*'],
message: 'shared/ 不能依赖 features/'
},
{
group: ['@/features/auth/*'],
importNames: ['*'],
message: 'features 之间不能直接依赖,通过 shared/ 或事件通信'
}
]
}]
}
}
feature 之间的通信:
ts
// ❌ 错:feature A 直接导入 feature B
import { useUserStore } from '@/features/auth/store'
// ✅ 对:通过 shared 层暴露接口
// shared/stores/user.ts
export { useUserStore } from '@/features/auth/store'
// 或用事件总线解耦
// shared/events.ts
export const eventBus = mitt()
// feature A
eventBus.emit('user:logout')
// feature B
eventBus.on('user:logout', handleLogout)
分割粒度的决策:
场景策略原因路由页面每个页面一个 chunk用户不会一次访问所有页面大型依赖(echarts、pdf.js)单独 chunk不是所有页面都需要低频功能(导出、打印)懒加载大多数用户不会用通用组件(Button、Modal)打进主 chunk几乎每个页面都用,拆了反而多请求
Next.js App Router 的分割:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
import dynamic from 'next/dynamic'
// 路由级自动分割,不需要手动 dynamic
export default function DashboardPage() {
return <DashboardContent />
}
// 大型图表库按需加载
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => <ChartSkeleton />,
ssr: false, // 不在服务端渲染
})
// 低频功能懒加载
const ExportModal = dynamic(() => import('./ExportModal'))
Vite 的手动分包:
ts
// vite.config.ts
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor-react': ['react', 'react-dom', 'react-router-dom'],
'vendor-ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
'vendor-charts': ['echarts', 'recharts'],
}
}
}
}
}
不要凭感觉选,用决策矩阵:
维度权重方案 A方案 B方案 C学习曲线20%864生态成熟度25%975性能20%798团队熟悉度15%953社区活跃度10%876迁移成本10%689加权总分7.857.15****5.85
关键技术选型的决策点:
框架选择(React vs Vue vs Svelte):
状态管理(Zustand vs Redux vs Jotai):
样式方案(Tailwind vs CSS Modules vs CSS-in-JS):
技术债的分类:
类型特征处理策略架构债模块耦合、分层混乱渐进式重构,设边界代码债重复代码、命名混乱日常重构,code review测试债覆盖率低、测试脆弱新功能必须有测试,存量逐步补依赖债过时依赖、安全漏洞定期升级,用 Dependabot文档债文档过时、缺失重要决策必须记录 ADR
ADR(Architecture Decision Record)模板:
markdown
# ADR-001: 选择 Zustand 作为状态管理方案
## 状态
已决定
## 背景
项目需要全局状态管理,主要用于主题、用户信息、通知等 UI 状态。
## 决策
选择 Zustand,不用 Redux。
## 理由
- 代码量少(Redux 需要 action/reducer/selector,Zustand 直接 set)
- 性能好(细粒度订阅,不需要 reselect)
- 学习曲线低(团队新人多)
- 不需要 Redux 的中间件生态(没有复杂异步逻辑)
## 后果
- 正面:开发效率高,代码简洁
- 负面:如果未来需要时间旅行调试,需要额外配置
- 缓解:用 Redux DevTools 扩展监控 Zustand
什么时候重构,什么时候重写:
场景策略原因代码质量差但架构清晰渐进式重构风险低,可持续交付架构腐化严重绞杀者模式(Strangler Fig)新功能用新架构,旧功能逐步迁移技术栈过时且无法升级重写继续维护成本 > 重写成本
绞杀者模式示例(微前端):
text
旧应用(legacy.example.com)
├── /dashboard → 保留
├── /settings → 迁移到新应用
└── /reports → 迁移到新应用
新应用(app.example.com)
├── /settings → 新架构实现
└── /reports → 新架构实现
Nginx 路由:
/settings → app.example.com
/reports → app.example.com
/* → legacy.example.com
反模式问题正确做法过度设计3 人团队用微前端按当前规模设计,预留扩展点没有边界所有代码在 src/ 平铺按功能域或分层组织循环依赖A 导入 B,B 导入 A用 ESLint 检测,提取公共依赖到 shared全局状态滥用所有状态都放 store状态尽量下沉,能放组件就不放 store技术债不可见没有记录,靠口口相传用 ADR 记录重要决策,用 TODO 标记技术债
markdown
## 前端架构方案
### 项目背景
- 团队规模:X 人
- 预期生命周期:X 年
- 性能要求:[ToB 后台 / ToC 高性能]
- 现有技术栈:[新项目 / 存量迁移]
### 技术选型
| 维度 | 方案 | 理由 |
|------|------|------|
| 框架 | React / Vue / Svelte | … |
| 状态管理 | Zustand / Redux / Jotai | … |
| 样式 | Tailwind / CSS Modules | … |
| 构建 | Vite / Next.js | … |
### 项目结构
[目录树 + 分层说明]
### 模块边界
- 依赖规则:[分层图]
- ESLint 规则:[no-restricted-imports 配置]
### 代码分割策略
- 路由级:每个页面一个 chunk
- 大型依赖:[列出需要单独分割的库]
- 懒加载:[列出低频功能]
### 技术债管理
- ADR 存放位置:docs/adr/
- 重构策略:[渐进式 / 绞杀者模式]