Cloudflare Workers におけるマルチサイトアーキテクチャ設計

OSS ライブラリのドキュメントを公開したい、社内ダッシュボードを段階的に統合したい、ポートフォリオサイトの一部だけを別アプリケーションにしたいなど、同一ドメインで複数のサイトやアプリケーションを束ねたくなる場面は、運用 […]

広告ここから
広告ここまで

目次

    OSS ライブラリのドキュメントを公開したい、社内ダッシュボードを段階的に統合したい、ポートフォリオサイトの一部だけを別アプリケーションにしたいなど、同一ドメインで複数のサイトやアプリケーションを束ねたくなる場面は、運用していると意外と多く発生します。

    Cloudflare Workers でこれをやろうとすると、Routes、Service Bindings、マイクロフロントエンド構成と選択肢が並び、それぞれ設計上のトレードオフが異なります。本稿では3つのパターンを整理し、ユースケースに応じて何を選ぶべきかを述べます。


    静的ファイルの配信はWorkers Static Assets を起点にする

    新規プロジェクトであれば、静的ファイル配信は Workers Static Assets を採用するのが現時点の標準です。Cloudflare Pages はサポートが続いていますが、新機能の投資は Workers 側に集約されると公式に表明されています(Migrate from Pages to Workers)。Pages は「維持はするが、これからは Workers」というポジションだと理解しておくのが正確です。

    基本的な構成は以下のとおりです。wrangler.jsonc が現在の Wrangler 推奨形式で、wrangler init 系コマンドはこの形式を自動生成します。

    // wrangler.jsonc
    {
      "name": "my-site",
      "main": "src/index.ts",
      "compatibility_date": "2026-04-29",
      "assets": {
        "directory": "./dist/",
        "binding": "ASSETS"
      }
    }

    Worker コードからは env.ASSETS.fetch(request) を呼ぶことで、静的ファイルを配信できます。binding を省略すると Worker から ASSETS を参照できなくなる点だけ注意してください(静的ファイルのみを配信する場合は省略してもよい仕様です)。

    この前提を踏まえたうえで、複数サイトの統合方法を見ていきます。


    パターン 1: Routes による振り分け

    最もシンプルかつ高速なのが、Cloudflare の Routes 機能による振り分けです。

    構成概要

    同一ドメインの異なるパスを、それぞれ独立した Worker にマッピングします。たとえばポートフォリオサイトと OSS ライブラリのドキュメントを同一ドメインで公開する場合、以下のように分割します。

    example.com/*           → Portfolio Worker
    example.com/docs/sdk/*  → SDK Documentation Worker
    example.com/docs/cli/*  → CLI Documentation Worker

    Cloudflare の Routes は、複数のパターンが該当した場合に「より具体的な(specific)パターン」を優先します。具体的とは、ワイルドカード(*)が少なく、より長く明示的に書かれているパターンを指します。/docs/sdk/*/* の両方が候補になる場合、前者が選ばれます(Routes ドキュメント)。「最長一致」と表現される場面もありますが、公式仕様としては specificity ベースだと理解しておくほうが安全です。

    実装

    各ドキュメントサイトは独立した Worker としてデプロイします。TypeDoc や VitePress などで生成した静的サイトであれば、Worker 本体は最小限のコードで済みます。

    // docs-sdk/src/index.ts
    export default {
      async fetch(request: Request, env: Env): Promise<Response> {
        return env.ASSETS.fetch(request);
      }
    };

    親サイトのコードに一切手を加える必要がない点が、このアプローチの最大の利点です。子サイトのデプロイパイプラインは独立して回せて、親サイトに影響を与えません。

    適用すべきケース

    このパターンは以下を満たす場合に最適です。

    • 配信するコンテンツが静的ファイルまたは SPA である
    • 親子間でランタイムの連携(認証情報の共有・共通ヘッダー注入など)が不要である
    • セッション共有が必要ない

    典型的なユースケースは、TypeDoc による API 仕様書、Storybook のコンポーネントカタログ、VitePress による技術ドキュメント、デモ用 SPA などです。


    パターン 2: Service Binding によるオーケストレーション

    親 Worker から子 Worker を呼び出す Service Binding を使うと、より複雑な要件にも対応できます。

    構成概要

    親 Worker がリクエストをインターセプトし、Service Binding 経由で子 Worker を呼び出します。

    // portfolio/src/index.ts
    export default {
      async fetch(request: Request, env: Env): Promise<Response> {
        const url = new URL(request.url);
    
        if (url.pathname.startsWith('/docs/sdk/')) {
          return env.DOCS_SDK.fetch(request);
        }
    
        return env.ASSETS.fetch(request);
      }
    };

    wrangler.jsonc では、子 Worker へのバインディングを宣言します。

    {
      "name": "portfolio",
      "main": "src/index.ts",
      "compatibility_date": "2026-04-29",
      "assets": {
        "directory": "./dist/",
        "binding": "ASSETS"
      },
      "services": [
        {
          "binding": "DOCS_SDK",
          "service": "docs-sdk"
        }
      ]
    }

    Routes との違い

    Service Binding では、親 Worker がリクエストとレスポンスの両方を加工できます。認証情報の注入、共通ヘッダーの付与、レスポンスの後処理といった横断的関心事を親に集約できる点が Routes との決定的な違いです。

    一方で、Worker 呼び出しが1段増える分のレイテンシは避けられません。静的ファイルの配信だけが目的であれば、この追加コストを正当化できるケースは限定的で、Routes のほうが適しています。


    パターン 3: マイクロフロントエンド構成

    Service Binding をさらに発展させると、マイクロフロントエンド(MFE)の原則を適用したアーキテクチャになります。

    設計思想

    MFE 構成では、各サブアプリケーションが独立したデプロイ単位となり、親サイトが App Shell として機能します。App Shell が担う責務は次の3つです。

    リクエストパスに基づいて適切な子 Worker を選択するルーティング決定。認証情報やユーザー設定を子 Worker に伝播させるコンテキスト注入。ヘッダーやフッターといった共通要素をサーバーサイドで組み立てる共通 UI 層。

    実装例

    App Shell では、MFE レジストリを保持してリクエストをディスパッチします。

    export default {
      async fetch(request: Request, env: Env): Promise<Response> {
        const url = new URL(request.url);
    
        const mfeRegistry: Record<string, keyof Env> = {
          '/app/dashboard/': 'DASHBOARD_MFE',
          '/app/settings/': 'SETTINGS_MFE',
          '/app/analytics/': 'ANALYTICS_MFE',
        };
    
        for (const [basePath, binding] of Object.entries(mfeRegistry)) {
          if (url.pathname.startsWith(basePath)) {
            const mfeContext = {
              user: await authenticateUser(request, env),
              basePath: basePath,
              theme: getUserTheme(request),
            };
    
            const mfeRequest = new Request(request, {
              headers: {
                ...Object.fromEntries(request.headers),
                'x-mfe-context': JSON.stringify(mfeContext),
              },
            });
    
            const mfeWorker = env[binding] as Fetcher;
            const response = await mfeWorker.fetch(mfeRequest);
    
            return injectCommonLayout(response);
          }
        }
    
        return env.ASSETS.fetch(request);
      }
    };

    子 Worker 側では、注入されたコンテキストを参照してレンダリングします。

    export default {
      async fetch(request: Request, env: Env): Promise<Response> {
        const contextHeader = request.headers.get('x-mfe-context');
        const context = contextHeader ? JSON.parse(contextHeader) : {};
    
        // basePath を考慮したルーティング
        const url = new URL(request.url);
        const internalPath = url.pathname.replace(context.basePath, '/');
    
        return handleRequest(internalPath, context, env);
      }
    };

    適用すべきケース

    MFE 構成は、以下のいずれかが要件として明確にある場合に検討します。

    • 複数チームが独立して開発・デプロイを行う体制になっている
    • 認証やセッション管理を一元化したい(各 MFE が認証ロジックを個別実装したくない)
    • MFE ごとに異なるフレームワーク(Next.js / Remix など)を採用する必要がある

    「将来こうなるかも」で導入すると、得られる柔軟性に対して App Shell の保守コストが見合いません。


    どのパターンを選ぶか

    選択の判断基準を整理します。

    まず、配信するコンテンツが完全に静的かを確認します。TypeDoc や Storybook、VitePress でビルドされた静的サイトであれば、Routes による振り分けで十分です。Service Binding を導入する理由はありません。

    次に、親子間でランタイム連携が必要かを検討します。認証情報の共有、共通ヘッダーの注入、レスポンス加工といった要件が一つもなければ、ここでも Routes が最適解になります。

    動的なサーバーサイド処理があり、かつ複数アプリケーション間で共通の関心事がある場合に、初めて Service Binding を検討します。さらに「複数チームの独立デプロイ」「認証一元化」「フレームワーク多様性」のいずれかが本当に必要であれば、MFE 構成まで進みます。

    重要なのは、アーキテクチャの複雑性は常にコストだという認識です。MFE 構成は設計上のエレガンスがありますが、静的サイト配信に対しては明らかに過剰です。パフォーマンス・保守性・チーム構成を見たうえで、要件を満たす最小の構成を選んでください。



    まとめ

    Cloudflare Workers でマルチサイトを構成する際の選択肢は、Routes による単純な振り分けから MFE 構成まで段階的に存在します。技術的な可能性と実際の要件は分けて考え、ユースケースに応じた最小限の複雑性を選ぶことが、長期的な保守性とパフォーマンスの両立につながります。

    静的サイトには Routes、ランタイム連携が必要なら Service Binding、それ以上の組織的要件があれば MFE。この順序で必要性を確認していくと、多くのケースで適切なパターンを選べます。

    参照

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark