ECサイトって、1店舗だけならそこまで難しく考えなくても作れます。

商品一覧があって、詳細があって、カートに入れて、購入する。

ただ、同じコードで複数ショップを動かしたいとなると、急に考えることが増えました。

最初は shopId みたいな値で分岐すれば良さそうと思っていたんですが、

ドメイン、商品取得、メニュー、テーマ、404、テストまで絡んでくると、

ああ、これは早めに境界を決めないと後でつらそうだなと。。。

まず分けたかったもの

一番最初に気になったのは、URL とテナントの対応です。

同じアプリケーションでも、アクセスされたドメインによって表示するショップが変わる。

ここを各ページで毎回考えると、たぶんすぐ壊れます。

なので、まずはこういう考え方に寄せました。

interface TenantConfig {
  tenantId: string;
  shopName: string;
  theme: ThemeConfig;
  commerce: CommerceConfig;
}

const domainToTenant: Record<string, string> = {
  'shop-a.example.com': 'shop-a',
  'shop-b.example.com': 'shop-b'
};

function resolveTenant(domain: string): TenantConfig | null {
  const tenantId = domainToTenant[domain];
  if (!tenantId) {
    return null;
  }

  return tenants[tenantId] ?? null;
}

コードとしては普通なんですが、

「ドメインからテナントを決める」と「テナントから表示設定を取る」を分けておくのが大事そうでした。

ここを混ぜると、あとで開発用ドメイン、確認用ドメイン、本番ドメインが増えた時にしんどい。

header で見るか、host で見るか

Next.js だと middleware や server component 側で request header を見られます。

なので、テナント解決は request の入口に寄せたい。

function getTenantDomain(headers: Headers): string {
  return (
    headers.get('x-tenant-domain') ??
    headers.get('host') ??
    ''
  );
}

ローカルやE2Eだと、実ドメインを毎回用意するのがめんどくさいです。

なので、テストでは header でテナントを差し替えられると便利でした。

本番は host を見る。

テストやプレビューは x-tenant-domain みたいなヘッダーで差し替える。

この逃げ道を作っておくと、開発がかなり楽になります。

unknown tenant はちゃんと落とす

意外と大事なのが、知らないドメインでアクセスされた時です。

なんとなく default tenant を出すと、一見便利なんですが、

テナント設定ミスに気づきにくくなります。

const tenant = resolveTenant(domain);

if (!tenant) {
  notFound();
}

ここは最初から 404 にした方がいいと思いました。

設定が足りていないなら、早く壊れてくれた方がいい。

ECだと別ショップの商品や表示が混ざる方が怖いので、

「見つからなかったら適当に出す」は避けたいです。

1店舗ならいらない抽象化

正直、1店舗だけならここまで分けなくていいです。

設定ファイルもいらないし、ドメイン解決もいらない。

ただ、2店舗、3店舗と増える前提があるなら、

最初にこのくらいの境界は作っておいた方が良さそうでした。

  • ドメインからテナントを解決する
  • テナントごとの設定を1箇所に寄せる
  • unknown tenant は 404 にする
  • テストでは header でテナントを切り替えられるようにする

このあたり。

全部を汎用化しようとすると重いけど、

毎回 if (shopId === '...') を足す構成にはしたくない。

おわり

最初は「ドメインで出し分けるだけでしょ」と思っていました。

でも実際に触っていくと、テナント解決はその後の設計全部に効いてくる感じがありました。

商品取得、テーマ、メニュー、テスト。

全部がここから始まる。

今なら、マルチテナントECを作る時は、最初にページを作るより先に、

「このリクエストはどのショップのものか」を安全に決めるところから始めると思います。