PushUlinkSaaS 架构多租户 SaaSslug vs subdomain

多租户 SaaS 先用 slug 还是 subdomain?早期应该怎么选

给早期 SaaS 团队的一份实用判断:什么时候用路径 slug,什么时候上 tenant subdomain,什么时候再支持 custom domain。

快速答案

多租户 SaaS 早期可以先用 slug,但要尽早把租户解析集中到一个位置。slug、租户子域名和自定义域名不只是 URL 写法,它们会变成 onboarding、路由、客服和生命周期管理问题。好的设计应该允许产品未来从多种入口模式解析同一个租户。 如果你正在做一个早期多租户 SaaS,通常建议先用路径 slug,除非你已经有很明确的理由必须让每个租户都有独立 hostname。 比如一个餐厅扫码点餐系统,早期可以先这样设计: 这通常比一开始就做下面这种结构更容易开发、测试、部署和排查问题: 关键不是永远只用 slug。关键是从第一天开始,就把 tenant resolution 集中到一个函数或 middleware 里。这样你现在可以先用路径 slug,之后可以支持 tenant subdomain,再之后也能接 custom domain。

重点章节

先看这几部分

快速回答

多租户 SaaS 早期可以先用 slug,但要尽早把租户解析集中到一个位置。slug、租户子域名和自定义域名不只是 URL 写法,它们会变成 onboarding、路由、客服和生命周期管理问题。好的设计应该允许产品未来从多种入口模式解析同一个租户。

如果你正在做一个早期多租户 SaaS,通常建议先用路径 slug,除非你已经有很明确的理由必须让每个租户都有独立 hostname。

比如一个餐厅扫码点餐系统,早期可以先这样设计:

order.in/{restaurant}/menu
order.in/{restaurant}/table/{token}
order.in/{restaurant}/kitchen
order.in/{restaurant}/admin

这通常比一开始就做下面这种结构更容易开发、测试、部署和排查问题:

restaurant.order.in
restaurant2.order.in

关键不是永远只用 slug。关键是从第一天开始,就把 tenant resolution 集中到一个函数或 middleware 里。这样你现在可以先用路径 slug,之后可以支持 tenant subdomain,再之后也能接 custom domain。

决策对比表

选择适合场景注意点
路径 slug早期产品、二维码流程、简单部署不要把 slug 解析散落在各处。
租户子域名租户品牌感更强、路由更清晰要考虑 wildcard DNS、证书和客服流程。
客户自定义域名客户需要自己的品牌 hostname需要验证、状态追踪和生命周期管理。

先给结论

下面这些情况,先用路径 slug 更合适:

  • 产品还在验证阶段。
  • 早期只有少量租户。
  • 主要流量来自二维码、直接链接或登录后的入口。
  • 还没有强烈的租户品牌化 URL 需求。
  • 希望本地开发、测试和部署尽量简单。

下面这些情况,subdomain 开始变得更有价值:

  • 租户身份需要在 hostname 里直接体现。
  • 租户希望链接看起来更像自己的品牌。
  • 未来计划支持客户自定义域名。
  • 希望不同租户有更清晰的 cookie 或 session 边界。
  • 租户会公开分享链接,并且很在意 URL 外观。

不要因为很多成熟 SaaS 都用了 subdomain,就在第一版也默认上 subdomain。很多产品是等租户生命周期成熟以后,才逐步演进到这一步。

为什么大家会纠结这个问题

这个问题通常出现在产品刚过原型期的时候。

一开始,系统只有一个客户、一个后台、一个菜单页。路径 slug 很自然:

  • 一个主域名。
  • 一个部署。
  • 一套路由规则。
  • 一个地方排查问题。

后来你看到很多 SaaS 产品都在用租户子域名:

  • acme.example.com
  • team.example.com
  • restaurant.example.com

于是就会担心:如果我现在先用 slug,是不是给未来挖坑?

大多数情况下,不是。

只要你没有把 tenant 解析逻辑散落到各个 controller、组件、数据库查询、统计事件和后台任务里,先用 slug 没问题。真正危险的不是 slug,而是每个地方都自己解析一遍 slug。

餐厅点餐系统里的真实场景

假设你在做一个基于二维码的餐厅点餐产品。

顾客扫码后打开:

order.in/sushi-house/table/12

厨房看订单用:

order.in/sushi-house/kitchen

店长进后台用:

order.in/sushi-house/admin

这个阶段,餐厅最关心的通常是菜单能不能打开、订单能不能到厨房、后台好不好用。他们不一定会强烈在意链接到底是:

order.in/sushi-house/menu

还是:

sushi-house.order.in/menu

如果用户路径主要是桌上的二维码,而不是手动输入网址,那么路径 slug 完全可以作为第一版方案。

为什么早期 slug 更适合

路径 slug 最大的好处是减少变量。

你不需要一开始就配置 wildcard DNS,不需要为本地开发准备一堆租户 hostname,也不需要更早处理证书、子域名解析、预览环境和 DNS 异常。

它的实际好处很直接:

  • 路由更简单。
  • 本地开发更简单。
  • 预览环境更简单。
  • 排查问题更简单。
  • 客服解释更简单。
  • DNS 和 SSL 边界问题更少。

早期产品最宝贵的是注意力。工程时间应该优先花在点餐、出单、后台、支付、通知这些核心流程上,而不是过早把 URL 架构做复杂。

什么时候 subdomain 开始有价值

当租户身份本身开始成为产品体验的一部分时,subdomain 就更有意义了。

比如餐厅开始把链接发到社媒、菜单卡、外卖包装、会员短信里:

sushi-house.order.in/menu

或者餐厅想要更像自己品牌的入口:

menu.sushihouse.com

这时 subdomain 就不只是路由选择,而是租户开通、品牌呈现、客服支持、访问统计和运营管理的一部分。

subdomain 尤其适合这些情况:

  • 租户 URL 会出现在广告、收据、海报、邮件或社媒里。
  • 客户希望链接更品牌化。
  • 你想为未来 custom domain 做准备。
  • cookie 或 session 需要更清楚的租户边界。
  • 每个租户入口需要更清楚的运营负责人。

但只要你前面把 tenant lookup 层设计好,后面加 subdomain 不应该变成重写整个产品。

保留两种方案的架构设计

早期最重要的设计,是在应用里只保留一个地方回答这个问题:

这个请求属于哪个租户?

这个函数或 middleware 可以叫:

resolveTenant(request)

它应该把 URL 结构隐藏起来,不让业务代码到处关心 tenant 是从 slug 来的,还是从 subdomain 来的。

function resolveTenant(request) {
  const host = request.headers.host;
  const path = new URL(request.url).pathname;

  if (isCustomDomain(host)) {
    return findTenantByCustomDomain(host);
  }

  if (isTenantSubdomain(host)) {
    return findTenantBySubdomain(host);
  }

  return findTenantBySlug(path);
}

应用里的其他部分只需要拿到 tenant 对象,然后继续处理业务。

这样你的演进路径会很清楚:

  • 先用 path slug。
  • 后面支持 tenant subdomain。
  • 等客户真的需要,再支持 custom domain。

目标不是今天就选出一个永远完美的 URL 方案,而是不要把自己写进很难改的角落里。

一个简单的 tenant mapping 表

你可以用一张很普通的映射表来支撑这个设计。

tenant_id
slug
subdomain
custom_domain
status
verification_status
default_destination
created_at
updated_at
last_seen_at

早期很多字段可以是空的。重点是让租户入口变成一个明确的对象。

一个餐厅第一版可能是:

slug: sushi-house
subdomain: null
custom_domain: null
status: active

之后可以演进成:

slug: sushi-house
subdomain: sushi-house
custom_domain: menu.sushihouse.com
status: active

餐厅身份没有变,只是入口方式变了。

SEO 会不会受影响

对于二维码点餐系统来说,SEO 往往不是第一决策因素。

如果用户主要是扫桌上的二维码进入菜单,那么比 SEO 更重要的是可靠、打开快、租户解析准确。

SEO 只有在这些场景下会变得更重要:

  • 餐厅菜单页需要被搜索引擎收录。
  • 餐厅页面希望参与本地搜索排名。
  • 租户会把链接放到自己官网或社媒主页。
  • 每个租户页面需要稳定 canonical URL。

这时你需要判断,租户页面应该继承主域名结构:

order.in/sushi-house/menu

还是更像独立 hostname:

sushi-house.order.in/menu

没有一个放之四海皆准的答案。关键要看这些页面到底是公开内容资产,还是主要给用户操作使用的应用页面。

性能和扩展性呢

slug 和 subdomain 本身通常不是性能瓶颈。

真正影响性能的通常是:

  • 数据库 tenant 索引。
  • 缓存 key 设计。
  • CDN 行为。
  • 后端路由方式。
  • 查询设计。
  • table token 和 session 校验方式。
  • 每次请求执行了多少业务逻辑。

subdomain 不会天然比 slug 更可扩展。slug 也不会天然比 subdomain 更慢。

只要 tenant resolution 集中,并且 lookup 足够轻,两个方案都可以支撑业务增长。

租户隔离是不是必须靠 subdomain

subdomain 可以帮助建立一些浏览器层面的边界,尤其是 cookie 和 session。

但 subdomain 本身不等于完整租户隔离。

真正的租户隔离来自:

  • 数据库查询里的 tenant scope。
  • 正确的权限判断。
  • 清晰的角色和权限模型。
  • 缓存隔离。
  • 后台操作流程。
  • 操作日志。

如果后端把请求解析到了错误租户,subdomain 也救不了。租户模型和授权层比 URL 形态更关键。

常见错误

第一个错误,是到处解析 slug。

如果每个页面、每个接口、每个查询都用自己的方式解析 restaurant slug,将来支持 subdomain 时会非常痛苦。

第二个错误,是把租户身份和桌号身份混在一起。

比如:

order.in/sushi-house/table/12

这里 sushi-house 是租户,table/12 是这个租户下面的资源。两者不要混在一个概念里。

第三个错误,是在没人明确需要的时候就做完整 custom domain 系统。

custom domain 会带来验证、DNS 指引、SSL 状态、异常提示、客服流程和状态管理。可以提前设计未来路线,但没必要在第一个餐厅上线前就全部做完。

第四个错误,是没有入口生命周期。

后面每个租户入口都要回答这些问题:

  • 谁负责这个入口?
  • 它现在指向哪里?
  • 它是否还在启用?
  • 它什么时候改过?
  • 谁改的?
  • 什么时候应该下线?

PushUlink 可以帮助团队通过 Console 和 OpenAPI 创建、统计、替换和下线托管子域名转发入口。

推荐方案

如果是上面这个餐厅点餐系统,我会这样做。

第一阶段:先用 slug

使用:

order.in/{restaurant}/menu
order.in/{restaurant}/table/{token}
order.in/{restaurant}/kitchen
order.in/{restaurant}/admin

先把产品跑通。确认餐厅可以开通,顾客可以点餐,厨房可以收到订单,后台可以管理。

第二阶段:集中 tenant resolution

加一个统一的 tenant resolution 层,让所有请求都从这里过。

不要让每个页面自己判断 restaurant 是谁。这个工作应该交给一个 middleware 或函数。

第三阶段:当品牌化需求出现时支持 subdomain

当餐厅开始要求更清晰、更品牌化的 URL 时,再支持:

sushi-house.order.in/menu

这应该只是新增一种入口方式,而不是重新设计整个产品。

第四阶段:当客户真正需要时支持 custom domain

当餐厅希望使用:

menu.sushihouse.com

你再补上域名验证和路由工作流。到那时,你已经知道需求是否真实,也知道客服和运营最常遇到的问题是什么。

重点总结

  • 早期产品先用 slug 通常更合适。
  • 不要因为成熟 SaaS 用 subdomain,你也第一版就照抄。
  • 从第一天开始集中 tenant resolution。
  • 把租户 URL 当成生命周期对象,而不是单纯路由格式。
  • 当品牌化、公开分享、cookie 边界或 custom domain 需求出现时,再加 subdomain。
  • 只有当产品和客户基础真的需要时,再做 custom domain。

FAQ

slug 方案长期能用吗?

可以。很多产品长期使用 slug。问题不在 slug 本身,而在 tenant resolution 有没有设计干净。

subdomain 会更快、更容易扩展吗?

不会自动更快。subdomain 对某些路由和浏览器边界有帮助,但性能主要取决于数据库设计、缓存、请求处理和 tenant lookup 效率。

slug 会影响 SEO 吗?

不一定。二维码点餐这类产品,SEO 可能不是第一关注点。如果租户页面要公开排名,再重点考虑 canonical URL、公开内容结构,以及页面应该放在主域名路径下还是独立 hostname 下。

先用 slug 会不会导致以后很难支持 custom domain?

只有在 slug 解析逻辑散落到代码各处时才会。只要 tenant resolution 是集中的,后面加 subdomain 和 custom domain 都更现实。

什么时候应该从 slug 切到 subdomain?

当 hostname 里的租户身份真的带来价值时:更强品牌感、公开分享、更清楚的 cookie/session 边界、custom domain 准备,或者更清晰的入口运营管理。

最后一句

这个问题本质上不是“slug 和 subdomain 哪个永远正确”。

更好的问题是:

你的产品能不能从不同入口模式里解析出同一个租户,而不需要重写应用?

如果可以,你就能先简单上线,再随着真实需求逐步演进。

PushUlink 可以帮助团队通过 Console 和 OpenAPI 创建、统计、替换和下线托管子域名转发入口。

常见问题

早期 SaaS 应该从 slug 还是 subdomain 开始?

多数早期 SaaS 可以先从 slug 开始,只要租户解析是集中的,后续就不会堵死 subdomain 和 custom domain。

租户子域名什么时候有价值?

当租户品牌、公开分享、cookie 边界或自定义域名准备变重要时,租户子域名就更有价值。

PushUlink 可以帮助团队通过 Console 和 OpenAPI 创建、更新、统计和下线租户子域名转发入口。

FAQ

常见问题

slug 方案长期能用吗?

可以。很多产品长期使用 slug。问题不在 slug 本身,而在 tenant resolution 有没有设计干净。

subdomain 会更快、更容易扩展吗?

不会自动更快。subdomain 对某些路由和浏览器边界有帮助,但性能主要取决于数据库设计、缓存、请求处理和 tenant lookup 效率。

slug 会影响 SEO 吗?

不一定。二维码点餐这类产品,SEO 可能不是第一关注点。如果租户页面要公开排名,再重点考虑 canonical URL、公开内容结构,以及页面应该放在主域名路径下还是独立 hostname 下。

先用 slug 会不会导致以后很难支持 custom domain?

只有在 slug 解析逻辑散落到代码各处时才会。只要 tenant resolution 是集中的,后面加 subdomain 和 custom domain 都更现实。

什么时候应该从 slug 切到 subdomain?

当 hostname 里的租户身份真的带来价值时:更强品牌感、公开分享、更清楚的 cookie/session 边界、custom domain 准备,或者更清晰的入口运营管理。