“矮化”这个词,做前端的人一般都不太爱听,但说得直白一点,不少SaaS项目里的前端,处境就是被矮化的——需求评审不带你,接口后端先定死,你照着做页面就行。这种模式短平快,可副作用也明显:业务规则不断往前端代码里沉淀,但前端架构本身却一直没有成长,最后只能靠加班和返工来维持交付。
谷雨SaaS平台的前端改造,就是从这种处境里长出来的。这个系列前面七篇一直在聊后端、平台和交付流程,这一篇我想专门把视角转到前端:讲讲谷雨SaaS的前端架构,是怎么从一个被“矮化”的辅助模块,逐步蜕变成平台可持续交付核心能力的一部分的。如果你正带着前端团队做企业级项目,或者你所在的SaaS产品正在被“页面多、复用难、交付慢”这三个问题折磨,这篇文章应该能给你一些可以直接拿去用的思路和方案。
1. 先厘清问题:SaaS前端为什么会被“矮化”
聊架构之前,得先承认一个现实:绝大部分SaaS项目的前端,一开始并没有被当作核心资产来对待。老板眼里前端是成本中心,后端是逻辑中心,运维是稳定中心。这种认知直接决定了资源投入和团队定位,前端自然就被压到了交付链条的最末端。
1.1 传统模式下前端的三个死穴
我在谷雨SaaS早期梳理代码库的时候,发现前端的问题非常典型,基本可以归结成三类。
第一类是业务逻辑严重沉淀在前端,但前端代码没有分层。权限判断、按钮显隐、状态流转这些本该由平台统一管控的规则,大量散落在各个页面组件里。同一个“订单能否取消”的判断逻辑,在列表页、详情页、操作日志页各写了一遍,而且写法还不一样。改一个业务规则,得全局搜索所有相关页面,漏改一个就是线上事故。
第二类是组件复用率低得可怜。表面上项目里有一个components目录,实际上里面的组件大多是页面级私有组件,换个项目就搬不走。公共的业务组件、布局组件、权限组件没有沉淀,每个新业务线都在重复造轮子。新项目启动看起来很“快”,但那是靠复制粘贴堆出来的快,后面改需求时每一处复制粘贴的代码都要跟着改一遍,成本翻好几倍。
第三类是前端发布无法独立。传统模式下前端代码经常和后端接口耦合在一起,前端要上线一个页面改动,得等后端一起联调、一起发布。前端发布的节奏完全被后端绑架,说好的“小步快跑”,实际上被拖成了一周一大版的“憋大招”。可持续交付在SaaS场景里要求的是前端能独立于后端发布、独立灰度、独立回滚,但传统架构下这三件事全都做不到。
1.2 可持续交付对前端提出了哪些硬要求
后来我们在谷雨内部复盘时,给“可持续交付”下了个定义:在不牺牲质量的前提下,让每次变更都能以最小的代价、最快地到达用户,并且可以随时撤销。这个定义放在后端,大家都能接受;放在前端,很多人的第一反应是“前端改动不用那么重吧”。
但SaaS产品恰恰相反。SaaS的租户多、版本杂、使用场景差异大,前端任何一次小改动,都可能影响几十上百个租户的操作体验。可持续交付对前端至少提出了四个硬要求:
- 独立交付能力:前端的构建、测试、发布流程必须自成体系,不依赖后端发版窗口。
- 向后兼容能力:老版本页面不能因为新功能上线就失效,接口契约要做到向前兼容。
- 灰度与回滚能力:新功能只对部分租户生效,出问题能在一分钟内把流量切回旧版本。
- 可观测能力:前端要有自己的错误监控、性能监控和业务埋点,不能一出问题就让后端帮忙查日志。
这四条里,前两条靠流程和工程化解决,后两条靠平台基建解决。而要把这四件事同时做好,前端架构就不能再停留在“按照页面堆代码”的阶段。
2. 架构演进思路:把前端当作独立交付单元
想清楚问题之后,谷雨SaaS前端就明确了一个核心思路:不再把前端当作后端的附属物,而是当作一个独立的交付单元来设计。这里面有两个层面的变化,一个是视角变化,一个是分层模型的变化。
2.1 从功能模块到业务模块的视角转变
很多前端团队做架构,习惯按照“功能模块”来划分。菜单里有什么就建什么目录,用户管理、订单管理、商品管理、报表管理,一个菜单对应一个模块。这种划分方式开发起来很直观,但它本质上是被后端接口和页面菜单驱动的,不是被业务驱动的。
谷雨SaaS切换到了“业务模块”的视角。所谓业务模块,不是看“页面有什么功能”,而是看“用户要完成什么任务”。比如说,同样是订单列表,运营人员看的是订单流转和异常处理,财务人员看的是对账和结算状态,客服人员看的是用户咨询和售后入口。这三类人看到的功能有重叠,但业务流程完全不一样。如果前端按菜单划分,这些人都会挤在同一个订单页里,页面越来越臃肿,权限判断越来越混乱。
换成业务模块之后,我们把页面拆成了“流程片段”。同一个订单列表,可以根据角色渲染不同的操作列、不同的状态筛选、不同的快捷入口。每个业务模块内部自治,模块之间通过标准接口通信。这个转变带来的直接好处是:需求的粒度变小了,变更的影响范围变小了,交付的自然频率也就上来了。
2.2 三层前端架构模型
视角转过来之后,代码结构就不能再按页面平铺了。谷雨SaaS最终落地的是三层前端架构模型:
- 基础层:负责与业务无关的通用能力,包括设计系统、基础UI组件库、工具函数库、请求封装、埋点SDK、错误监控SDK。
- 业务层:负责与业务相关的可复用资产,包括业务组件、页面模板、流程编排器、权限校验组件、租户配置组件。
- 应用层:负责最终呈现给用户的应用入口,包括路由、状态管理、布局框架、模块加载和运行时骨架。
这三层的关系,用一句大白话概括就是:基础层管“长得怎么样”,业务层管“干什么事”,应用层管“怎么组装”。基础层不感知业务,业务层不感知页面,应用层不写具体业务逻辑。这样划分之后,每一层的职责都是单一的,改动某一层的内容,不会波及其他层。
这套模型的底层逻辑其实很简单:可持续交付的本质是“变更局部化”。你改一个按钮颜色,不应该影响订单状态流转;你改一个订单状态流转,不应该影响租户配置页面。只有把不同变化频率的代码放到不同的层里,才能让高频改动和低频改动互不干扰。这就跟装修房子一样,水电管线和家具软装的变化频率完全不同,把它们分开处理,后续维护才不折腾。
3. 核心落地实践:谷雨SaaS前端的关键设计
思路层面的东西讲再多,不如直接看落地。这一部分我挑了几个谷雨SaaS前端架构里最关键的设计点,基本都是可以直接抄作业的级别。
3.1 技术选型和目录结构
谷雨SaaS前端最终选了Vue 3 + TypeScript + Vite + pnpm monorepo的组合。选这套组合不是因为它最新潮,而是因为它最稳妥地满足了前面说的四个硬要求。Vue 3的Composition API对业务逻辑的抽取很友好,TypeScript提供了一定程度的契约约束,Vite解决构建速度问题,pnpm monorepo解决多业务线共享依赖和独立构建的问题。
目录结构上,我们没有采用传统的“一个项目一个大目录”模式,而是用了monorepo多包管理:
packages/ ├── core/ # 运行时骨架,路由、状态、布局 ├── ui/ # 基础组件库和设计系统 ├── request/ # 请求封装和接口网关 ├── business/ # 业务层,按业务模块拆包 │ ├── order/ │ ├── user/ │ ├── settlement/ │ └── report/ ├── portal/ # 管理员入口 ├── tenant/ # 租户端入口 └── shared/ # 跨包共享的类型和工具每个业务包内部又做了两层区分:组件层放可复用的业务组件,页面层放可供路由直接加载的页面组件。这样做的好处是,业务包可以独立构建、独立版本、独立发布。新来的业务线要复用订单组件,直接引包就行,不需要把代码复制过来。不同的应用入口(管理后台、租户端、开放平台)共享同一套业务包,从源头上避免了多端割裂。
3.2 权限控制从“页面级”下沉到“操作级”
SaaS的权限体系是所有前端架构里最绕不开的一环。很多项目的做法是后端返回一个菜单列表,前端根据角色判断显示哪些菜单。这种做法只能管到“页面级”,同一个页面里的按钮和字段,还是得靠前端代码里写死判断。
谷雨SaaS做了一个关键调整:权限不跟菜单绑定,跟操作绑定。后端返回的是一个权限点集合,包含模块编码、操作编码和字段编码。前端通过一个统一的权限指令和方法来判断当前用户是否拥有某个操作权限。比如“取消订单”这个按钮,不再是写死的v-if,而是通过权限点校验来决定是否渲染。
这套设计带来的最大好处是:权限调整不需要发版。运营人员想要某个角色新增“导出报表”的权限,管理员在配置平台操作一下即可,前端代码不用动。权限判断逻辑全部收敛到一个权限校验模块里,后来又配合后端做成了实时权限推送,前端页面根本不用关心权限数据从哪来,只需要关心“当前用户是否有权限做这件事”。这在多租户场景下尤其重要,因为不同租户对同一功能的可见性要求是动态变化的,写死在代码里迟早出事故。
3.3 模块化扩展能力:让第三方业务线可以接入
SaaS平台做到一定规模后,一定会遇到“第三方业务方要接入主平台”的需求。可能是内部新业务线,也可能是外部生态伙伴。如果前端架构不支持模块化扩展,每次接入都是一次伤筋动骨的改造。
谷雨SaaS在应用层做了一个轻量级的模块注册机制。第三方业务包构建后,会生成一个模块清单文件,里面声明了模块的路由、菜单和依赖资源。主应用启动时会动态拉取这些模块清单,注册对应的路由和菜单项。这样第三方业务线只需要按照约定的模块规范开发,构建后把产物部署到指定路径,就能接入主应用,不需要改主应用代码。
这个机制的核心,是把“集成”从代码层面的耦合,变成了规范层面的对齐。大家不用在同一个代码仓库里开发,只需要遵守同一套接口规范。实测下来,新业务线的首次接入时间从以前的几周缩短到了几天,而且主应用发版完全不受第三方业务线影响。
4. 自动化与可持续交付:把架构能力变成日常习惯
架构设计得再好,如果发布还是要靠人肉点击、靠手动运维,可持续交付就是一句空话。谷雨SaaS前端把自动化流水线作为架构的一部分来建设,而不是等架构搭完了再补流水线。
4.1 一套完整的CI流水线
前端仓库统一接入了CI流水线,每个合并请求都会自动触发检查,主分支构建成功后自动进入发布流程。流水线分四步:
- 静态检查:ESLint跑一遍,TypeScript类型检查一遍,提交信息规范检查一遍。任何一步不通过,合并请求会被直接拦截。
- 单元测试与构建验证:跑核心业务包的单元测试,同时执行一次完整的构建,确保“代码能合并”和“代码能构建”是两回事。
- 产物上传与版本标记:构建产物上传至对象存储,同时生成带版本号的标记文件,CDN从此只认带hash的文件。
- 环境自动部署:上传成功后自动部署到测试环境,并推送消息到IM群,通知测试人员开始回归。
这条流水线的核心价值不在于“自动”,而在于把质量门槛前置到了合并请求阶段。以前是代码合并完、部署出问题再回头看,现在是问题在合并前就被拦截掉了。团队在这套流水线上磨合了两个月之后,前端线上故障率下降了不止一个量级。
4.2 CDN缓存策略与版本回滚
SaaS前端发布后,最怕的就是“用户还在用旧版”。CDN缓存策略如果配置不对,新版本上线了,用户刷新十次还是旧页面,然后相关的工单就来了。
谷雨SaaS的静态资源采用了指纹策略:所有带内容的文件(JS、CSS、图片)文件名都带hash,CDN配置长时间缓存。入口的index.html则配置为不缓存或短缓存。这样新版本发布时,index.html会第一时间更新,用户刷新页面拿到的就是新的页面入口,进而加载带新hash的静态资源。老版本的静态资源不主动删除,保留一段时间,用于回滚。
回滚操作也做了轻量化。发布系统里保存了每一个历史版本号,回滚时只需要把CDN上的index.html切到上一个版本,同时恢复对应的静态资源配置。整条链路走完不超过一分钟。有一次我们灰度中发现某个新功能在低版本浏览器上白屏,直接执行回滚,前后不到三十秒,用户体感上几乎没有感知到异常。
4.3 灰度发布的前端实现
灰度发布是SaaS可持续交付的重要一环。后端灰度通常靠网关和注册中心,前端灰度则需要一套独立机制。谷雨SaaS的前端灰度方案是基于“分流标识”实现的。
发布时,发布系统会生成一个灰度批次,绑定一批指定的租户ID或用户标识。index.html里内置了一段极小的分流脚本,根据标识判断当前用户是否命中灰度批次。命中则加载新版静态资源,未命中则加载旧版静态资源。因为分流逻辑是在页面入口阶段完成的,所以灰度过程对用户完全无感,后续切流也不需要用户清理缓存。
这个方案看起来简单,但有一个关键前提:灰度和正式环境的路由入口必须一致。很多项目做灰度喜欢用独立域名或独立路径,导致用户在正式环境完全无法看到灰度版本,测试效果很差。谷雨SaaS选择不区分URL、只区分流量的方式,灰度版本和正式版本共享入口,差异只在资源加载层。这样灰度测试看到的场景和真实环境完全一致,效果反馈才可靠。
5. 实战中遇到的问题与排查实录
架构落地过程中,坑肯定是少不了的。这一部分我挑几个典型的,按“现象-原因-解法”整理出来,希望你能少走一些弯路。
5.1 高频问题清单
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| 新版本上线后部分用户一直看到旧页面 | index.html被CDN或浏览器缓存 | 入口文件设置no-cache,并配置主动刷新机制 |
| 灰度批次内用户切换租户后功能错乱 | 分流标识只在页面加载时判断一次 | 把分流结果存到会话级变量,切换租户时重新计算 |
| monorepo中公共包改动导致所有业务包重新构建 | 依赖图没有做精确的变更检测 | 引入changesets,只发布受影响的包并自动标记版本 |
| 权限点新增后前端按钮仍不显示 | 权限码硬编码在组件中,未走统一校验 | 全量替换为权限指令和权限方法,禁止直接访问权限集合 |
| 第三方模块接入后样式相互污染 | 全局样式未做作用域隔离 | 业务包统一启用CSS Module,基础样式收敛到设计系统 |
5.2 一个线上事故的完整排查过程
这里多说一个具体案例。有一次灰度发布上线了新版结算页面,运营反馈说部分租户的结算单数据不显示了。因为灰度抽取的是指定租户,所以受影响的范围可控,但问题总得查清楚。
第一步,先确认分流状态。查了灰度配置,命中的租户ID范围没有变,说明分流逻辑本身没问题。第二步,看前端错误监控。监控平台上报了一个TypeError:无法读取undefined的某个属性。第三步,定位具体报错位置。根据sourcemap映射,错误出现在结算包的一个公共组件里,这个组件依赖某个异步加载的配置数据。第四步,查了接口网关日志,发现这个配置数据接口在灰度环境下返回了空数组,因为新配置中心还没有录入这批租户的结算参数。
问题根因清楚了:新功能上线时配置中心的数据没有同步,属于典型的前后端协作断层。后来我们做了一个防呆设计,前端在配置数据缺失时展示空状态占位和提示,不再直接抛异常;配置中心也增加了上线前数据完整性校验。这类问题的麻烦之处不在于修复,而在于排查链路长,如果不做前端监控和sourcemap映射,光靠猜可能要花整整半天时间。
5.3 几条实用的排查经验
排查前端线上问题时,我的建议是按照“先看流量分发,再看资源缓存,再看运行时错误,最后查接口数据”的顺序来。大部分问题都出在这四层里。
流量分发层看的是用户到底命中了哪个版本的资源,有灰度配置的话先确认灰度范围有没有扩大;资源缓存层看的是CDN命中率和资源版本号,确认用户拿到的静态资源是不是最新的;运行时错误层看监控平台有没有对应的报错,以及报错堆栈能不能映射到具体组件;接口数据层看请求是否正常发出、返回是否符合预期。按这个顺序排查,绝大多数问题都能在十五分钟内定位到根因。
6. 从“矮化”到“核心”:组织协作方式的变化
架构和技术方案说了这么多,最后想聊点“软件之外”的东西。谷雨SaaS前端能完成蜕变,不只是因为选了什么技术栈、搭了什么流水线,更关键的是整个团队对前端的协作方式发生了变化。
6.1 前后端协作从“接口先定”变成“契约先行”
以前的前后端协作模式是后端先写接口文档,前端照着文档调。接口字段缺什么、命名怎么样,前端基本上没有话语权。这种模式下,前端被矮化几乎是必然的,因为决策权完全不在自己手里。
改造之后,谷雨SaaS引入了契约先行的协作方式。新需求评审时,前后端一起定义接口契约,用统一的契约文件管理。字段的命名、类型、是否必填、默认值,在前端开发前全部对齐。后端还没开始实现的时候,前端已经可以用mock数据并行开发了。更重要的是,接口契约变更时必须有变更记录和兼容性评估,不能后端说改就改,前端被动全部返工。
以前前端被矮化,很大程度上是因为“你只能接受别人定义的东西”。当契约的定义过程变成双方共同参与时,前端的专业价值才真正被看见。这个转变的收益不只是交付更快,更是让前端的同事在评审会上有底气说出“这个交互撑不起这个业务,需要重新设计”——而不是默默回去改页面。
6.2 体验所有权从前端一个人扛变成整个平台的责任
以前产品的体验问题都是前端的问题,“页面不好看”找人资、招聘时也是“前端做得不够好”。谷雨SaaS把体验提级成了平台级的核心指标,前端架构为此提供了一个能力基础:统一的埋点体系。
所有关键交互行为都通过前端埋点SDK上报,包括点击、停留、跳失、异常。产品和运营可以直接在数据看板里看到每个功能的使用情况,不再依赖开发手动导数据。前端团队也可以基于这些数据做有针对性的性能优化,而不是拍脑袋判断“哪个页面慢”。当体验数据成为平台决策依据的一部分,前端就不再是“背锅”的一环,而是给平台提供增长判断依据的一环。
6.3 前端团队的工程文化从“做页面”变成了“做产品”
最后说点感受上的变化。以前前端团队被叫作“切图仔”,大家日常讨论的是“这个页面对不对”、“那个样式齐不齐”。架构改造推进到中期的时候,团队日常讨论的问题变成了“这个模块的复用边界在哪”、“这个权限点应该收敛到哪一层”、“这个业务包的改动会不会影响其他入口”。
话题的变化看似很小,但它的影响是深远的。当一个前端团队开始用架构思维、模块边界、交付安全来衡量工作,而不是仅仅用完成度来衡量工作,这个团队就真正从执行层进化到了设计层。谷雨SaaS前端从“矮化”到“核心”的蜕变,本质上不是某个技术方案的成功,而是整个团队角色认知的重建。
我个人在实际操作中的体会是:前端架构改造别指望一口气推完。从最痛的点切入,先做好权限收敛和组件沉淀,再搭流水线、上灰度,每一步都让团队看到实实在在的收益,后面推进的阻力就会越来越小。谷雨SaaS这一路走下来,最值钱的不是那套代码,而是团队建立起来的“变更可控”的信心——知道每一次交付都在自己手里,出问题了也有办法解决。这种信心,才是可持续交付真正落地的东西。