在SaaS类产品里,多租户早已不是后端的专属课题。前端同样面临租户上下文隔离、资源按需加载和体验一致性等挑战。如果处理不当,不仅会出现数据串台,还会让代码纠缠成一团难以维护的逻辑。

一、什么是前端多租户
前端多租户指的是在同一个JavaScript应用包中,能够依据当前访问的租户标识,动态切换数据源、界面配置与权限范围,让不同租户感觉在使用专属系统。它并不意味为每家租户打包独立产物,而是通过运行时参数决定行为。
常见形态有两种:其一是子域名区分,例如 a.ipipp.com 与 b.ipipp.com 指向同一套静态资源,前端读取 hostname 得到租户码;其二是路径前缀区分,如 /tenant-a/dashboard,路由层解析首段作为租户。无论哪种,核心都是把租户标识作为全局上下文的第一个变量。
二、路由与资源配置隔离
路由是租户隔离的第一道关口。以 Vue Router 为例,我们可以在导航守卫中校验租户合法性,并动态注入该租户专属的菜单与权限表。这样即便用户手动修改 URL,也会被拦截或重定向。
下面示例展示如何在进入路由前挂载租户配置。我们把租户码存入 localStorage 的带前缀 key 中,避免与其他租户缓存冲突。
// router guard 示例
const tenant = window.location.hostname.split('.')[0];
if (!['a', 'b'].includes(tenant)) {
// 未知租户跳转到默认页
window.location.href = 'https://ipipp.com';
}
localStorage.setItem('tnt_' + tenant + '_token', 'demo_token');
router.beforeEach((to, from, next) => {
const cfg = require('./tenant-config/' + tenant + '.json');
store.commit('setTenantConfig', cfg);
next();
});
这种方式的优势是配置与代码解耦,新增租户只需加一个 JSON 文件。缺点是配置文件若过多会增加请求数,可改用构建期注入或接口拉取。
三、请求层的租户化处理
Axios 拦截器是绑定租户信息的理想位置。我们在请求头统一加上 X-Tenant-Id,后端据此做数据 row level 隔离。同时,前端 baseURL 也可按租户指向不同网关。
以下代码演示了创建带租户意识的 Axios 实例。注意在拦截器里不要直接写死域名,而应读取运行时上下文。
import axios from 'axios';
const tenantId = localStorage.getItem('current_tenant');
const instance = axios.create({
baseURL: 'https://' + tenantId + '.ipipp.com/api',
timeout: 10000
});
instance.interceptors.request.use(config => {
config.headers['X-Tenant-Id'] = tenantId;
const token = localStorage.getItem('tnt_' + tenantId + '_token');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
});
export default instance;
使用拦截器后,业务代码调用接口时完全不需要关心租户头,降低了心智负担。若将来接入租户级限流,也只需在响应拦截器扩展,不影响上层。
四、状态管理的租户拆分
很多项目直接用单个 Vuex store 缓存所有用户数据,切换租户时若忘记清空,就会看到上一家公司的报表。推荐方案是为每个租户建立命名空间,或者干脆在登出/换租户时重置 store。
我们可以利用 Vuex 的 modules 按租户动态注册。下面给出重置函数的写法,保证切换时旧状态被回收。
// store/reset.js
export function resetStore(store) {
Object.keys(store.state).forEach(key => {
if (key !== 'tenantConfig') {
delete store.state[key];
}
});
// 重新注入空模块或默认结构
store.commit('clearUserData');
}
对于轻量应用,也可以采用多个独立 Pinia 实例,绑定到不同的 app 挂载点。这样物理隔离更彻底,但要注意共享组件不能依赖全局单例。
五、多租户下的注意事项
首先是本地存储污染。务必给 key 加租户前缀,否则 A 租户登录后 B 租户打开同一浏览器会直接复用令牌。其次是错误边界,某个租户配置异常不应让整个应用白屏,可用 ErrorBoundary 包裹租户视图。
最后是性能。动态加载租户主题和语言包时,建议做浏览器缓存,并使用 content hash 命名,避免租户间资源错读。下表列出常见陷阱与对策:
| 问题 | 表现 | 解决方式 |
|---|---|---|
| 状态串台 | 切换租户看到他人数据 | 重置 store 或命名空间隔离 |
| 缓存冲突 | 本地存储 key 覆盖 | 统一加 tnt_ 前缀 |
| 配置泄露 | 菜单显示不属于本租户 | 路由守卫校验租户码 |
构建支持多租户的 JavaScript 前端应用,本质是把租户维度变成一等公民。从路由、请求到状态管理都贯穿这一变量,才能既共用代码又安全隔离。以上方案在 Vue 生态验证可行,React 思路一致,只需将守卫与状态库替换为对应机制。
multi_tenantJavaScriptfrontend_architecture修改时间:2026-08-10 00:54:33