Modern Web App Architecture (SPA/SSR/SSG/RSC)
概述
本技能为设计和构建现代 Web 应用(含 SPA 与混合渲染应用)提供系统指导,强调取舍(trade-offs)、明确边界以及生产环境实践(性能、可访问性、安全、测试、交付)。
核心原则:架构中的一切都是取舍。不存在“正确”答案,只有针对具体上下文的最不坏组合。
使用方式
技能被激活时,按以下顺序工作:
- 澄清上下文(最多 5–10 个问题)→ 用户、路由、SEO、交互性、数据、认证、团队、约束。
- 逐路由选择渲染策略(而不是“整个应用只用一种策略”)。
- 定义边界 → 功能/领域模块、共享库、归属与稳定接口。
- 规划状态与数据 → 本地/共享/全局/服务端/URL 状态、缓存策略、失效、乐观更新。
- 规划非功能性需求 → 性能预算与度量、可访问性方案、安全态势、可观测性。
- 产出工件 → 带明确取舍的简短建议,以及具体下一步(目录结构、ADR、检查清单)。
如果用户无法回答某个问题,给出合理假设并继续(不要阻塞)。
适用场景
- 启动新的 SPA 或 Web 应用
- 选择渲染策略(CSR、SSR、SSG、ISR、RSC)
- 实现状态管理
- 优化 Core Web Vitals(LCP/INP/CLS)
- 为多个前端团队做扩展
- 做架构取舍决策
- 从遗留前端迁移到现代前端
快速发现式问题(先问)
- 排名前三的用户旅程及其目标延迟是什么(例如“搜索 → 商品 → 结账”)?
- 是否有任何路由需要 SEO?哪些是公开的、哪些在认证之后?
- 数据形态如何:主要是 CRUD、实时、离线优先、大量表单、大表格/图表?
- 有哪些约束:浏览器支持、包体积限制、上市时间、合规(SOC2/HIPAA/PCI)?
- 团队拓扑如何:多少开发者/团队、发布节奏、归属边界?
- 偏好的技术栈(React/Vue/Angular/原生),是否愿意使用 TypeScript?
参考文件
| 主题 | 加载时机 |
|---|---|
| references/design-patterns.md | 实现 JS 模式(Module、Observer、Factory 等) |
| references/react-patterns.md | React 组件、hooks、状态、组合 |
| references/spa-fundamentals.md | SPA 架构、路由、模块组织 |
| references/micro-frontends.md | 团队扩展、独立部署 |
| references/performance.md | 包体积、加载、Core Web Vitals |
| references/architecture-decisions.md | 取舍、耦合、适应度函数 |
| references/rendering-strategies.md | CSR vs SSR vs SSG vs ISR vs RSC |
| references/state-management.md | 本地、全局、服务端状态模式 |
| references/security-and-auth.md | 认证选择、令牌存储、XSS/CSRF、CSP、API 边界 |
| references/accessibility-and-i18n.md | WCAG 基础、SPA 焦点管理、包容性组件、i18n 陷阱 |
| references/testing-and-quality.md | 测试策略、CI 质量门禁、a11y 检查、契约测试 |
| references/tooling-and-delivery.md | 打包、环境、部署、可观测性、功能开关 |
架构决策速查树
项目需求?
├─ SEO 关键 + 动态内容 → SSR(或 SSR+流式)
├─ SEO 关键 + 主要是静态 → SSG/ISR(或混合)
├─ 主要在认证之后 + 类应用体验 → CSR SPA(或带预渲染壳的混合)
├─ 混合(营销 + 应用)→ 混合/岛屿(路由级策略)
│
团队规模?
├─ <5 开发者 → 模块化单体 SPA
├─ 5-15 开发者 → 结构良好的 SPA 或基于服务
├─ >15 开发者、多团队 → 考虑微前端
│
领域复杂度?
├─ 简单 CRUD → 分层架构
├─ 复杂工作流 → 领域分区(DDD)
├─ 多个限界上下文 → 微前端
默认产出(应产出什么)
根据用户请求,目标是输出:
- 路由策略映射:路由 → CSR/SSR/SSG/ISR/RSC 的小表格 + 原因
- 模块边界草图:功能文件夹、共享库、接口契约
- 状态映射:本地/共享/全局/服务端/URL,以及所选工具模式
- 数据方案:缓存/失效、错误状态、乐观更新、分页
- 质量方案:测试分层 + CI 门禁 + 可访问性检查
- 性能方案:预算 + 度量 + 具体加载策略(拆分点)
- 风险登记:前 5 风险 + 缓解措施(如 hydration 成本、认证态势、团队耦合)
必备模式速查
组件模式
| 模式 | 使用时机 |
|---|---|
| Container/Presentational | 将数据与 UI 分离 |
| Compound Components | 构建可组合 API(Select、Menu) |
| Hooks | 以无 HOC 方式共享有状态逻辑 |
| Provider | 避免为全局数据层层传递 props |
状态管理
| 方案 | 使用时机 |
|---|---|
| useState/useReducer | 本地组件状态 |
| Context | 主题、认证、低频全局状态 |
| Zustand/Jotai | 简单全局状态、最少样板代码 |
| Redux Toolkit | 复杂状态、时间旅行调试 |
| React Query/SWR | 服务端状态、缓存、后台刷新 |
| XState | 带显式状态机的复杂流程 |
性能要点
| 技术 | 影响 |
|---|---|
| 代码拆分 | 减少初始包体 |
| 懒加载 | 推迟非关键内容 |
| React.memo | 避免不必要的重渲染 |
| useMemo/useCallback | 稳定引用 |
| 虚拟列表 | 处理大数据集 |
架构特征(选 3–7 项)
| 特征 | 要问的问题 |
|---|---|
| 可扩展性 | 多少并发用户?增长速度? |
| 性能 | 可接受的 TTI 是多少?LCP 目标? |
| 可部署性 | 多久发布一次?是否独立部署? |
| 可测试性 | 变更验证有多容易? |
| 可维护性 | 预期生命周期多长? |
| 模块化 | 需求变化多频繁? |
应避免的反模式
| 反模式 | 问题 | 修复 | | --- | --- | | Prop drilling | 紧耦合 | Context 或状态管理 | | 上帝组件 | 职责过多 | 按关注点拆分 | | 过早优化 | 无证据的复杂度 | 先做性能剖析 | | 共享可变状态 | 竞态条件、缺陷 | 不可变模式 | | 单体式打包 | 初始加载慢 | 代码拆分 | | 过度获取 | 浪费带宽 | GraphQL 或 BFF | | 默认用 LocalStorage 存令牌 | XSS 变成账号接管 | 优先 httpOnly cookie + CSP(见安全参考) | | 用全局 store 存服务端数据 | 缓存失效痛苦 | 服务端状态用 React Query/SWR |
性能预算
预算必须根据用户/设备校准,但以下是“默认快”的良好起点:
| 指标 | 目标 | 需改进 |
|---|---|---|
| LCP | <2.5s | 2.5–4s |
| INP | <200ms | 200–500ms |
| CLS | <0.1 | 0.1–0.25 |
| TTFB | <800ms | 800ms–1.8s |
| 路由 JS(初始) | <170KB gzip | <300KB gzip |
来源
综合自:
- Learning JavaScript Design Patterns (Osmani, 2023)
- React in Depth (Barklund, 2024)
- SPA Design and Architecture (Scott)
- Single Page Web Applications (Mikowski & Powell)
- Building Micro-Frontends (Mezzalira)
- Micro Frontends in Action (Geers)
- Responsible JavaScript (Wagner)
- High Performance Browser Networking (Grigorik)
- Web Performance in Action (Wagner)
- Frontend Architecture for Design Systems (Godbolt)
- Fundamentals of Software Architecture (Richards & Ford)
- Software Architecture: The Hard Parts (Ford et al.)
- Patterns.dev