ZJH

把体验拆成边界:现代前端工程架构指南

从多页应用、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 到上线的前端流程

一个真实项目可以这样走:

  1. 定义页面地图和核心用户路径。
  2. 选渲染模式:静态、服务端、客户端或混合。
  3. 建基础布局、设计 token、常用组件。
  4. 定 API contract 和错误格式。
  5. 先做最小闭环:列表、详情、创建、编辑。
  6. 补加载、空状态、错误、权限。
  7. 接入搜索、筛选、分页。
  8. 做性能检查和可访问性检查。
  9. 接 CI、预览环境和生产部署。

前端不是最后“套 UI”,而是从第一天就参与系统边界设计。

我的默认建议

如果是个人站、内容站、技术博客:优先 Astro 或 Next.js 静态生成,少量交互用 islands 或客户端组件。

如果是后台管理系统:React 或 Vue 都可以,重点放在表单、权限、表格、数据请求和设计系统一致性。

如果是 SaaS 产品:选择能支持 SSR/SSG/CSR 混合的框架,尽早设计鉴权、错误、缓存、日志和埋点。

如果是内部工具:不要过度工程化。快速交付、权限清楚、部署稳定,比框架先进更重要。

延伸阅读