把体验拆成边界:现代前端工程架构指南
从多页应用、SPA、SSR、组件体系、状态管理到性能预算,整理真实项目里前端架构应该如何选型、开发和上线。
前端工程最容易被误解成“选一个框架,然后写组件”。但真实项目里的前端,不只是页面。它是一套让用户完成任务的交互系统。
一个成熟前端架构要同时处理:
- 页面如何组织。
- 数据如何获取和缓存。
- 状态如何流动。
- 组件如何复用和约束。
- 错误、加载、空状态如何表达。
- 性能如何不在迭代中慢慢变坏。
- 如何和后端、设计、测试、部署协作。
这篇文章从工程角度整理现代前端的核心判断。
前端架构解决什么问题
前端不是把后端返回的数据“画出来”这么简单。它要把复杂系统变成用户可以理解的连续体验。
一个用户点击按钮时,前端至少要回答:
- 当前操作是否可用。
- 点击后发生了什么。
- 失败时如何恢复。
- 数据是否已经更新。
- 用户离开或刷新会不会丢状态。
- 这个页面在手机、慢网、旧设备上是否还能使用。
所以前端架构的中心不是组件库,而是体验闭环。
前端演进路线
多页应用
多页应用由服务器返回完整 HTML。每次跳转都会请求新页面。
优点:
- 简单。
- 首屏稳定。
- SEO 友好。
- 服务端和内容强绑定时效率很高。
适合内容站、文档站、传统后台、低交互页面。
SPA
SPA 把路由、状态和大量渲染逻辑放到浏览器。后端提供 API,前端自己组织页面。
优点:
- 交互流畅。
- 复杂状态可以在客户端管理。
- 前后端协作边界更清楚。
- 登录后应用体验更接近桌面软件。
代价:
- 首屏可能变慢。
- JS 包容易变大。
- 数据获取、权限、缓存和错误状态需要明确约定。
SSR 和 SSG
SSR 在请求时由服务器生成 HTML;SSG 在构建时生成 HTML。
它们解决的问题不同:
- SSR 更适合个性化、强 SEO、动态数据页面。
- SSG 更适合内容、文档、博客、营销页。
很多现代框架允许页面级混合。一个站点里,内容页静态生成,控制台客户端渲染,详情页服务端渲染,这是很正常的。
Server Components 和 Islands
近几年,前端架构的一个重要方向是减少不必要的客户端 JavaScript。
React Server Components 让部分组件只在服务端运行,把数据读取和渲染结果传给客户端。Astro Islands 则鼓励默认输出静态 HTML,只把需要交互的区域变成客户端组件。
这两个方向的共同点是:不要把所有东西都塞进浏览器。
现代前端的默认分层
一个可维护的前端项目,可以先按这几层组织:
routes / pages
-> 页面入口,负责路由和布局组合
features
-> 按业务能力组织表单、列表、详情、动作
components
-> 通用 UI 组件和展示组件
data
-> API client、query hooks、缓存策略
state
-> 跨页面状态、局部状态、URL 状态
styles / design tokens
-> 视觉变量、布局规则、基础样式
不是每个项目都需要严格目录,但每个项目都需要知道:页面、业务能力、通用组件、数据请求和状态边界不要全部混在一个文件里。
路由和页面
路由不是文件路径这么简单。它表达用户如何进入系统。
设计路由时,先按用户任务组织:
/notes
/notes/:slug
/projects
/projects/:slug
/settings/profile
/settings/billing
页面入口应该尽量负责组合,而不是塞满业务细节。一个页面文件如果同时包含 API 请求、表单校验、权限判断、复杂状态和大量 JSX,很快会变成维护灾区。
数据获取和缓存
前端数据层要先分清三类数据:
Server state
来自后端的数据,比如用户信息、项目列表、订单状态。它的事实来源在服务器。
常见处理方式:
- 请求库或框架 loader 获取。
- 使用 query cache 管理加载、错误和失效。
- 修改后让相关查询失效或更新缓存。
Client state
只在浏览器里存在的状态,比如弹窗是否打开、当前 tab、临时表单草稿。
这类状态不应该过度全局化。能放组件局部就放局部,能放 URL 就放 URL。
URL state
筛选条件、分页、搜索关键词,通常适合放在 URL 中:
/projects?status=active&page=2&q=search
这样用户可以复制链接、刷新页面、回退前进,也方便后续排查问题。
状态管理怎么选
状态管理不要从工具开始,而要从状态生命周期开始。
组件内部状态
-> useState / signals / local state
跨组件但不跨页面
-> context / feature-level store
服务器数据
-> query cache / framework data APIs
URL 可表达状态
-> search params / route params
复杂编辑器或画布
-> 专门状态机或领域 store
很多项目滥用全局 store,是因为没有区分 server state 和 client state。后端数据不应该被复制成一份难以同步的全局变量。
组件设计
组件可以分成三类:
基础组件
按钮、输入框、弹窗、菜单、表格单元格。它们应该稳定、可组合、可访问。
业务组件
项目卡片、订单状态、用户权限面板。它们理解业务字段,但不应该直接掌握全局流程。
页面组件
页面负责组织布局、拉取数据、组装业务组件。页面越薄,后续调整越容易。
好的组件边界有两个特征:
- 不需要读完整项目也能知道怎么用。
- 内部实现变化时,不会牵连很多调用方。
表单和错误状态
表单是前端复杂度集中地。
真实项目要处理:
- 初始值。
- 同步校验。
- 异步校验。
- 提交中状态。
- 服务端错误。
- 乐观更新或提交后刷新。
- 离开页面前是否提醒。
错误状态不要只写 alert(error.message)。更好的方式是区分:
- 字段错误:显示在字段旁边。
- 表单错误:显示在表单顶部。
- 权限错误:引导重新登录或申请权限。
- 网络错误:允许重试。
- 系统错误:记录日志,给用户可理解提示。
性能预算
前端性能不是最后压缩一下包就能解决。它应该从架构阶段进入约束。
关注几个指标:
- 首屏 HTML 是否尽快到达。
- 关键 CSS 和字体是否阻塞。
- JS 包是否过大。
- 图片是否按尺寸和格式优化。
- 列表是否虚拟化。
- 交互是否因为主线程阻塞而卡顿。
常见策略:
- 内容页尽量静态生成。
- 不需要交互的部分不要 hydrate。
- 大组件延迟加载。
- 图片使用合适尺寸。
- 避免把管理后台组件带进公共首页。
和后端协作
前后端协作最怕接口靠口头约定。
至少要约定:
- 请求字段。
- 响应字段。
- 错误结构。
- 鉴权方式。
- 分页和排序。
- 时间格式。
- 空值规则。
如果团队规模稍大,可以使用 OpenAPI、GraphQL schema、tRPC 或共享类型。工具不是重点,重点是接口变化可以被发现,而不是上线后才知道。
从 0 到上线的前端流程
一个真实项目可以这样走:
- 定义页面地图和核心用户路径。
- 选渲染模式:静态、服务端、客户端或混合。
- 建基础布局、设计 token、常用组件。
- 定 API contract 和错误格式。
- 先做最小闭环:列表、详情、创建、编辑。
- 补加载、空状态、错误、权限。
- 接入搜索、筛选、分页。
- 做性能检查和可访问性检查。
- 接 CI、预览环境和生产部署。
前端不是最后“套 UI”,而是从第一天就参与系统边界设计。
我的默认建议
如果是个人站、内容站、技术博客:优先 Astro 或 Next.js 静态生成,少量交互用 islands 或客户端组件。
如果是后台管理系统:React 或 Vue 都可以,重点放在表单、权限、表格、数据请求和设计系统一致性。
如果是 SaaS 产品:选择能支持 SSR/SSG/CSR 混合的框架,尽早设计鉴权、错误、缓存、日志和埋点。
如果是内部工具:不要过度工程化。快速交付、权限清楚、部署稳定,比框架先进更重要。