我看过团队花费数月时间讨论多租户与多站点架构,最后选错了,又花费另外六个月进行迁移。这是那种看起来很抽象的决策,直到你开发了三个sprint后才意识到你的内容编辑可能会意外发布到错误的品牌,或者你的部署流程需要45分钟,因为你在重建12个网站时只有一个发生了变化。

这不是理论性的比较。我构建过这两种模式——有时候甚至是为同一个客户构建,因为第一次选择不合适。让我向你走一遍什么才是做出这个决策时真正重要的。

目录

多租户与多站点架构:如何决策

明确定义术语

这些术语经常被随意使用,所以让我们把它们明确下来。

多租户架构

一个应用实例为多个租户(品牌、客户、地区)提供服务。他们共享相同的代码库、同一个数据库(通常)和同一次部署。租户隔离发生在应用层——通过中间件、数据库模式或行级过滤。

把它想象成一座公寓楼。每个人共享结构、管道和电路。但每个单元都有自己的锁。

多站点架构

每个网站是一个独立的应用实例,拥有自己的代码库(或共享代码库的分支)、自己的数据库和自己的部署流程。他们可能共享设计系统或组件库,但它们是独立部署和运营的。

这更像一个住宅开发区。同一个建筑商、相似的蓝图,但每栋房子都站在自己的地基上。

混合区域

说实话,大多数生产系统落在两者之间的某个地方。你可能有一个多租户的CMS为独立部署的前端供应内容。或者一个共享的代码库,针对每个租户部署为单独的实例。真正的问题不是"选择哪一个",而是"在这个范围内处于什么位置"。

多租户架构胜出的情况

当你的网站相似度高于差异度时,多租户架构表现出色。

强大的品牌一致性要求

如果你正在管理同一品牌的15个地区网站,并且设计需要保持锁定,多租户是你的好朋友。一个代码库意味着组件、布局和交互模式有一个单一的信息源。当品牌团队更新按钮样式时,它会在所有地方推出。

快速扩展到新租户

我曾与一个特许经营平台合作,他们需要每周推出新位置。使用多租户,添加新网站只需一条数据库记录和一条DNS记录。没有新的基础设施,没有新的部署。上市时间从两周减少到约30分钟。

集中化内容操作

当你有一个小的内容团队管理多个属性时,多租户将所有内容保留在一个地方。编辑登录到一个系统,切换上下文,并跨所有租户管理内容。不需要为一堆CMS实例处理多个凭证。

共享功能开发

你构建的每个功能都使所有租户同时受益。错误修复、性能改进、新集成——部署一次,到处受益。开发工作的投资回报率是倍增的。

多站点架构胜出的情况

当你的网站需要显著分歧时,多站点架构胜出。

根本不同的用户体验

如果品牌A是电子商务店面,品牌B是内容出版,将它们挤入一个代码库会产生混乱。我见过多租户代码库,其中60%的代码都在租户特定的条件语句后面。到那时,你不是拥有一个应用——你有几个坏的应用共享一个代码仓库。

不同的技术要求

也许一个网站需要Next.js以获得其动态、类似应用的体验,而另一个是完美适合Astro的内容丰富的网站。多站点让每个属性使用正确的工具。我们构建了各种投资组合,其中一些网站在Next.js上运行,其他在Astro上运行,所有这些都来自一个共享的无头CMS

独立的发布周期

当不同的业务部门拥有不同的网站并且他们需要按自己的计划发布时,多租户会造成瓶颈。租户A的新功能部署不应该需要对租户B到Z进行回归测试。多站点给每个团队自主权。

监管或数据隔离

某些行业需要硬数据隔离——不仅仅是应用层分离,而是物理上独立的数据库,可能在不同的地区。医疗保健、金融和政府项目通常强制要求这一点。多站点使合规直观明了,因为隔离是架构上的,而不仅仅是逻辑上的。

不同的性能配置

如果一个租户获得1000万月度访问量,而另一个获得5万,共享基础设施意味着你要么为小租户过度配置,要么为大租户配置不足。单独的部署让你适当调整每个属性的规模。

多租户与多站点架构:如何决策 - 架构

决策框架

这是我实际上与客户一起使用的框架。对你的情况对每个因素打分:

因素 有利于多租户 有利于多站点
网站相似度 80%+共享UI/功能 少于50%共享
属性数量 10+个网站 少于5个网站
增长率 经常添加网站 稳定,很少添加
团队结构 一个中央团队 每个网站独立团队
内容模型 相同或接近相同 显著不同
合规需求 标准要求 严格的数据隔离
技术栈 处处相同的框架 需要不同的框架
发布周期 协调发布可以 需要独立发布
定制深度 主题级别(颜色、标志) 结构性(布局、功能)
预算 为效率优化 为灵活性优化

如果你在一列中的得分大多数,决策是清楚的。如果你在中间分裂,你可能在看一个混合方法——这很好。

实践中的架构模式

让我用代码具体说明这些看起来像什么。

使用Next.js的多租户

我使用的最常见的模式是基于中间件的租户解析:

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

const TENANT_MAP: Record<string, string> = {
  'brand-a.com': 'brand-a',
  'brand-b.com': 'brand-b',
  'brand-c.com': 'brand-c',
};

export function middleware(request: NextRequest) {
  const hostname = request.headers.get('host') || '';
  const tenantId = TENANT_MAP[hostname] || 'default';
  
  // 通过头部传递租户上下文
  const response = NextResponse.next();
  response.headers.set('x-tenant-id', tenantId);
  
  // 如果需要,重写为租户特定的路径
  const url = request.nextUrl.clone();
  url.pathname = `/${tenantId}${url.pathname}`;
  
  return NextResponse.rewrite(url);
}

然后你的页面组件拉取租户特定的配置:

// lib/tenant-config.ts
export async function getTenantConfig(tenantId: string) {
  // 可以来自数据库、CMS或配置文件
  return {
    theme: await fetchTheme(tenantId),
    navigation: await fetchNavigation(tenantId),
    features: await fetchFeatureFlags(tenantId),
    locale: await fetchLocaleConfig(tenantId),
  };
}

这很有效,直到你的租户开始需要不同的页面结构、不同的数据获取策略或不同的第三方集成。到那时,条件逻辑开始偷偷摸摸地出现。

使用共享包的多站点

对于多站点,我使用带有共享包的单一代码库:

├── apps/
│   ├── brand-a/          # Next.js应用
│   ├── brand-b/          # Astro应用
│   └── brand-c/          # Next.js应用
├── packages/
│   ├── ui/               # 共享组件库
│   ├── cms-client/       # 共享CMS集成
│   ├── analytics/        # 共享分析包装器
│   └── config/           # 共享TypeScript/ESLint配置
├── turbo.json
└── package.json
// turbo.json
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**"]
    },
    "deploy": {
      "dependsOn": ["build"]
    }
  }
}

Turborepo(或Nx)处理依赖图,所以你只重新构建改变了的内容。品牌A获得新功能?只有品牌A重新构建和部署。共享UI包被更新?所有依赖它的内容都重新构建。

混合方法:多租户CMS、多站点前端

说实话,这是我对大多数场景最喜欢的模式。你获得集中的内容管理以及独立的前端部署:

┌─────────────────────┐
│   无头CMS          │
│  (Sanity/Contentful)│
│   多租户           │
│   内容空间         │
└──────┬──────┬───────┘
       │      │
   ┌───┘      └───┐
   ▼              ▼
┌──────┐    ┌──────┐
│网站A │    │网站B │
│Next.js│   │Astro │
└──────┘    └──────┘

内容编辑获得一个登录名,可以管理所有属性。开发人员获得独立的代码库和部署流程。对于有预算的团队来说,这是两全其美。

CMS考虑因素

你的CMS选择显著限制——或启用——你的架构决策。

多租户CMS支持

CMS 多租户模型 多站点支持 价格影响
Sanity 每个租户的数据集 优秀(一个项目内多个数据集) 免费层:2个数据集;付费从$99/月起
Contentful 每个租户的空间 很好(组织级管理) 每个空间计入计划;$300+/月
Strapi 单一数据库,按租户过滤 需要手动实现 自托管,随基础设施扩展
Hygraph 环境和阶段 原生多站点内容联合 团队计划从$199/月起
WordPress (无头) WordPress多站点 成熟但复杂 托管成本按网站扩展
Payload CMS 集合级多租户 不断增长的支持(v3.0+) 自托管、开源

Sanity的数据集模型对多租户设置特别优雅。每个租户获得自己的数据集,拥有自己的模式,但它们位于一个项目下。你可以在数据集间共享模式定义,同时允许租户特定的定制。在规模上(10+个租户),这保持了你的内容操作理智。

Contentful的单独空间方法有效,但成本迅速增长。团队计划上的每个空间花费真实的金钱,在你开始添加空间之前你在看$300/月的最低费用。

内容建模含义

使用多租户,你的内容模型需要容纳所有租户。这通常意味着:

  • 每个内容类型上的租户品牌字段
  • 租户特定的验证规则
  • 仔细的权限建模,使编辑只看到其租户的内容
  • 需要租户范围的共享内容类型(如"全局设置")

使用多站点,每个网站有自己的内容模型。每个站点更简单,但你失去了无需额外的内容聚合层就跨站点共享内容的能力。

性能和扩展

多租户性能特征

多租户最大的风险是"吵闹的邻居"问题。如果租户A运行病毒式活动并且流量增加10倍,所有租户都会感受到它。缓解策略:

  • 按租户的边缘缓存:使用Vercel或Cloudflare的边缘网络,缓存键包括租户标识符
  • 带租户感知重新验证的ISR:仅重新验证内容更改的租户的页面
  • 按租户的速率限制:保护共享资源免受任何单个租户压倒它们
// next.config.js - 带租户感知重新验证的ISR
export async function generateStaticParams() {
  const tenants = await getAllTenants();
  const paths = [];
  
  for (const tenant of tenants) {
    const pages = await getTenantPages(tenant.id);
    paths.push(...pages.map(page => ({
      tenant: tenant.slug,
      slug: page.slug,
    })));
  }
  
  return paths;
}

多站点性能特征

每个网站独立扩展。这是好消息。坏消息是你正在管理N个部署流程、N个监控仪表板和N个性能预算集。超过20+个网站,运营开销成为瓶颈,而不是应用性能。

根据我在2025年进行的基准测试,以下是Vercel上大致可以预期的:

指标 多租户(1个应用,10个租户) 多站点(10个应用)
冷启动(边缘) 20-50ms 每个网站20-50ms
构建时间 8-15分钟(所有租户) 每个网站2-4分钟
增量构建 30-90秒 每个网站30-90秒
每个实例内存 256-512MB共享 每个256-512MB
月度Vercel成本(专业版) ~$20 ~$200($20 × 10)

这个成本差异很大。多租户在单个Vercel专业版计划中以$20/月对比多站点需要企业或创意项目组织。

成本分析

让我们谈论过去12个月中10个网站投资组合的真实数字。

多租户成本估计

项目 月度成本 年度成本
Vercel专业版(1个项目) $20 $240
Sanity团队(1个项目,10个数据集) $99 $1,188
开发(初始构建) -- $40,000-60,000
维护(持续) $2,000 $24,000
第1年总计 -- $65,428-$85,428

多站点成本估计

项目 月度成本 年度成本
Vercel专业版(10个项目) $200 $2,400
Sanity团队(10个项目) $990 $11,880
开发(初始构建,共享包) -- $50,000-80,000
维护(持续,10个网站) $4,000 $48,000
第1年总计 -- $112,280-$142,280

多租户大约便宜40-60%,主要是因为减少的维护负担。但如果多租户复杂性导致更多错误、更慢的功能开发或之后痛苦的迁移,这些数字会翻转。

想要你具体情况的更精确估计?我们在发现过程中详细分解架构成本。

迁移策略

有时你开始采用一种方法,之后需要切换。以下是如何不烧毁所有内容的方法。

多租户→多站点

这是更常见的迁移方向。需要它的迹象:

  • 租户特定代码超过代码库的30%
  • 部署被跨租户回归测试阻止
  • 团队在彼此的变更上碰撞

策略:

  1. 首先提取共享代码到包中(UI组件、工具、CMS客户端)
  2. 在现有应用周围创建一个单一代码库结构
  3. 为最具有差异的租户fork应用
  4. 逐渐将其他租户移到他们自己的应用
  5. 当每个应用变成独立的时,从它删除租户切换逻辑

多站点→多租户

不太常见,但它确实发生,通常是当一个公司收购多个品牌并希望整合操作时。

  1. 审计所有网站的共享模式(你会发现比预期更多)
  2. 从最佳实现中构建共享组件库
  3. 用最简单的网站创建多租户应用
  4. 一次一个地迁移网站,在每个步骤验证奇偶性
  5. 当租户上线时,停用个别应用

两个迁移都是破坏性的。预算3-6个月和大量的测试工作。这正是为什么正确做出初始决策很重要——它不仅仅是一个架构选择,它是一个承诺。

如果你面临这个决策并想与曾经做过的人谈论它,与我们联系

常见问题

多租户和多站点架构之间有什么区别? 多租户使用单个应用实例为多个品牌或客户提供服务,租户隔离在应用层处理。多站点为每个网站部署单独的应用实例,可能通过单一代码库和组件库共享代码。关键区别是你运行一个应用还是许多应用。

我可以使用多租户CMS与多站点前端一起使用吗? 绝对可以,通常是最好的方法。一个无头CMS如Sanity带有多个数据集为你提供集中化的内容管理,而单独的前端部署在技术选择、部署计划和性能扩展方面给予每个网站独立性。这个混合模式特别有效,当你的网站共享内容结构但需要不同的用户体验时。

多租户架构如何影响SEO? 多租户应用根据域名或子域名提供不同的内容,只要你实现适当的规范URL、每个租户的独特元数据和单独的站点地图,搜索引擎就能很好地处理。风险是跨租户意外的内容重复——确保你的站点地图生成和robots.txt是租户感知的。从SEO角度来看,搜索引擎不关心你的网站是否共享代码库;他们关心他们接收的内容和标记。

多租户比多站点便宜吗? 一般来说是的,通常在托管和维护成本上便宜40-60%。你为一个部署、一个监控设置和一套基础设施付费。然而,如果你的租户显著分歧,多租户可能在开发时间中变得更昂贵,因为管理租户特定逻辑的复杂性呈非线性增长。平衡点取决于你的网站实际上有多相似。

多站点WordPress多站点的最佳方法是什么? WordPress多站点本质上是多租户——一个WordPress安装,多个网站。如果你作为无头CMS使用WordPress,多站点网络为你提供集中化的内容管理。你的前端可以要么是多租户(一个Next.js应用)要么是多站点(每个WordPress网站单独的应用)。对于大多数基于WordPress的项目,我建议混合方法:WordPress多站点作为CMS,每个网站单独的前端部署。

如何在多租户应用中处理共享身份验证? 使用集中化的身份验证提供商(Auth0、Clerk或NextAuth.js)以租户感知的会话管理。身份验证令牌应该包括租户上下文,你的中间件应该验证经过身份验证的用户是否有权访问请求的租户。数据库中的行级安全(Supabase和Neon都支持这个)添加第二层保护免受跨租户数据泄露。

一个多租户应用最多可以处理多少租户? 没有硬限制,但实际限制围绕构建时间和运营复杂性出现。使用Vercel上的Next.js ISR,我看过多租户应用有效处理50+个租户。超过100个租户,你会想看按需ISR而不是预生成所有页面,你需要老练的缓存策略。像Shopify这样的SaaS平台有效地运行数千个租户,但他们在那个基础设施中投资了多年。

我应该为多站点架构使用单一代码库吗? 几乎总是可以。一个带Turborepo或Nx的单一代码库为你提供多租户的代码共享好处(共享组件、工具、配置)以及多站点的部署独立性。关键是保持共享包定义明确和版本化。没有单一代码库,你最后会在代码库中复制代码,立即分歧并在几个月内成为维护噩梦。