WithCodeMedia-1-pc
previous arrowprevious arrow
next arrownext arrow

WithCodeMedia-1-sp
previous arrowprevious arrow
next arrownext arrow

マイクロフロントエンド完全ガイド|Module Federationの仕組みからVite・webpack実装・MFE間通信・CI/CDデプロイ戦略まで徹底解説

生徒

フロントエンドが大規模化してきて、チームごとに独立してデプロイできるようにしたいんですが……マイクロフロントエンドって実際にどう実装するんですか?

ペン博士

Module Federationを使えばそれが実現できるんじゃ!複数のフロントエンドアプリをランタイムで結合して、あたかも1つのアプリのように動かせるぞ。ViteではvitePlugin-federationで同様のことができる。MFE間通信・TypeScript型共有・CI/CDパイプラインの設計まで全部解説するぞ!

目次

この記事でわかること

  • マイクロフロントエンドの概念とモノリスの課題の解決策
  • Module FederationのHost/Remote構造とランタイム共有の仕組み
  • Vite(vite-plugin-federation)とwebpack Module Federationの設定コード全文
  • MFE間通信(カスタムイベント・共有ストア・URLパラメーター)の実装方法
  • TypeScript型共有・CI/CDパイプライン・パフォーマンス最適化のベストプラクティス

マイクロフロントエンドはフロントエンドを機能単位で分割し、それぞれを独立して開発・デプロイできるアーキテクチャです。Module Federationを使えば、複数のフロントエンドアプリをランタイムで結合し、あたかも1つのアプリのように動かせます。本記事では概念から実装コード全文・CI/CD設計まで一気通貫で解説します。


マイクロフロントエンドとは|モノリスの限界と解決策

マイクロフロントエンドとモノリスアーキテクチャの比較図
>【マイクロフロントエンドが解決する問題】

モノリシックなフロントエンドの課題:
❌ 大規模チームで同じコードベースへの変更が衝突
❌ 一部機能の更新でもアプリ全体をビルド・デプロイが必要
❌ 技術スタックを変更しにくい(全部Reactなど縛りが発生)
❌ ビルド時間がどんどん長くなる(10分超えも)
❌ バグ修正がどこかに影響を与えないか毎回不安

マイクロフロントエンド(MFE)の恩恵:
✅ 機能ごとに独立したリポジトリ・デプロイサイクル
✅ 各チームが好みのフレームワーク/バージョンを選択可能
✅ 障害の影響範囲をMFE単位に限定できる
✅ 段階的なリプレースが可能(既存システムを少しずつ置き換え)
✅ ビルド時間が各MFEに分散するため全体ビルドが高速化

マイクロフロントエンドが向いている/向いていないケース

>【MFEに向いているケース】

✅ 大規模組織(50人以上のエンジニア)で複数チームが開発
✅ ドメイン(機能)が明確に分かれているアプリケーション
   例)ECサイト:商品カタログ・カート・決済・マイページが独立
✅ 既存モノリスを段階的に刷新したい場合
✅ 各機能のデプロイ頻度が異なる場合(一部だけ頻繁に更新)

【MFEに向いていないケース】

❌ 小〜中規模のプロジェクト(チーム5人以下)
   → オーバーエンジニアリングになりやすい
❌ 機能の境界が明確でないアプリケーション
❌ 高い一貫性のUIが求められる(デザインシステムの管理が複雑化)
❌ SEOが最重要の場合(SSRとの組み合わせが複雑)
❌ 開発者のインフラ知識が不足している場合

マイクロフロントエンドの実装アプローチ比較

MFEを実装する方法には複数のアプローチがあります。Module Federationはその中でも特に柔軟で強力な方法ですが、選択の前に全体像を把握しておきましょう。

アプローチ概要メリットデメリット向いているケース
iFrame各MFEをiframeで埋め込む完全な分離・どんな技術でも動くURLルーティング・サイズ・パフォーマンスが困難既存ページの部分埋め込み
Web Componentsカスタム要素でMFEを実装フレームワーク非依存・ブラウザネイティブShadow DOMとの兼ね合い・SSR非対応フレームワーク混在環境
Module Federationビルドツールでランタイム共有依存関係共有・共通コンポーネント利用・TypeScript対応同じフレームワーク推奨・設定が複雑同一技術スタックの大規模開発
single-spaMFEオーケストレーターライブラリフレームワーク混在・ルートアプリ管理学習コスト高・設定量が多いReact/Vue/Angular混在
npmパッケージ共通コンポーネントをnpm公開型安全・バージョン管理が明確デプロイのたびに再ビルドが必要デザインシステム・UIライブラリ

Module Federationとは|Host/Remoteの概念を理解する

>【Module Federationの2つの概念】

Host(ホスト):
→ マイクロフロントエンドをロードするコンテナアプリ
→ 「App Shell」とも呼ばれる
→ 他アプリのコンポーネントを自アプリかのように使う
→ ナビゲーション・ルーティング・認証を担当することが多い

Remote(リモート):
→ ホストに読み込まれるコンポーネントを提供するアプリ
→ 自身のコンポーネントを remoteEntry.js として公開
→ 個別にビルド・デプロイ可能
→ ホストなしでも独立して動作できることが理想

【ランタイム共有の仕組み】

ビルド時:
Remote → remoteEntry.js を生成(コンポーネントのメタデータ)

ランタイム(ブラウザ):
1. ホストアプリがブラウザで起動
2. lazy(() => import('remoteApp/Component')) が評価される
3. ホストが remoteEntry.js をネットワーク越しにフェッチ
4. remoteEntry.js からコンポーネントの実際のチャンクを取得
5. コンポーネントがレンダリングされる

→ Remoteが独立してデプロイされてもホストの再デプロイ不要!

プロジェクト構成と環境構築

># プロジェクト構成(モノレポ例)
mfe-monorepo/
├── app-shell/         ← ホストアプリ(ポート: 4000)
│   ├── src/
│   │   ├── App.tsx
│   │   └── main.tsx
│   └── vite.config.ts
├── overview/          ← リモートMFE1(ポート: 5000)
│   ├── src/
│   │   └── components/Overview.tsx
│   └── vite.config.ts
├── stats/             ← リモートMFE2(ポート: 5001)
│   ├── src/
│   │   └── components/Stats.tsx
│   └── vite.config.ts
├── details/           ← リモートMFE3(ポート: 5002)
│   ├── src/
│   │   └── components/Details.tsx
│   └── vite.config.ts
├── shared/            ← 共有の型定義・ユーティリティ
│   ├── src/
│   │   ├── types/
│   │   └── utils/
│   └── package.json
└── package.json       ← ワークスペース設定
># モノレポのpackage.json(pnpm workspaces)
{
  "name": "mfe-monorepo",
  "private": true,
  "workspaces": [
    "app-shell",
    "overview",
    "stats",
    "details",
    "shared"
  ],
  "scripts": {
    "dev": "concurrently \"pnpm -F overview dev\" \"pnpm -F stats dev\" \"pnpm -F details dev\" \"pnpm -F app-shell dev\"",
    "build": "pnpm -F overview build && pnpm -F stats build && pnpm -F details build && pnpm -F app-shell build",
    "preview": "concurrently \"pnpm -F overview preview\" \"pnpm -F stats preview\" \"pnpm -F details preview\" \"pnpm -F app-shell preview\""
  },
  "devDependencies": {
    "concurrently": "^8.0.0"
  }
}

# インストール
pnpm install
# 各アプリにvite-plugin-federationをインストール
pnpm -F overview add -D @originjs/vite-plugin-federation
pnpm -F stats add -D @originjs/vite-plugin-federation
pnpm -F details add -D @originjs/vite-plugin-federation
pnpm -F app-shell add -D @originjs/vite-plugin-federation

実装コード全文|Remote(リモート)側の設定

overview/vite.config.ts(リモート側)

>// overview/vite.config.ts

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import federation from '@originjs/vite-plugin-federation'

export default defineConfig({
  plugins: [
    react(),
    federation({
      name: 'remote_overview_app',   // モジュール名(全MFEで一意であること)
      filename: 'remoteEntry.js',    // エントリーファイル名(ホストが参照する)
      exposes: {
        // 外部に公開するコンポーネントのマッピング
        // '公開名': '実ファイルのパス'
        './Overview': './src/components/Overview',
        './Header': './src/components/Header',
        './useOverviewData': './src/hooks/useOverviewData',  // カスタムHookも公開可能
      },
      shared: {
        // 共有する依存関係の詳細設定
        react: {
          singleton: true,    // 1インスタンスのみ(バージョン不一致を防ぐ)
          requiredVersion: '^18.0.0',
        },
        'react-dom': {
          singleton: true,
          requiredVersion: '^18.0.0',
        },
      },
    })
  ],
  build: {
    target: 'esnext',  // vite-plugin-federationはesnextが必要
    minify: false,     // 開発時はfalseが推奨(デバッグしやすい)
  },
  server: {
    port: 5000,
    cors: true,  // ホストからの異なるオリジンのアクセスを許可
  },
  preview: {
    port: 5000,
    strictPort: true,
    cors: true,
  }
})

overview/src/components/Overview.tsx(公開するコンポーネント)

>// overview/src/components/Overview.tsx
// sharedパッケージから共通の型を参照(型共有の例)

export interface MonsterData {
  id: string
  name: string
  type: string
  rarity: 'common' | 'rare' | 'epic' | 'legendary'
  description: string
}

interface OverviewProps {
  monster: MonsterData
  onSelect?: (id: string) => void
}

export function Overview({ monster, onSelect }: OverviewProps) {
  const rarityColor = {
    common: '#9e9e9e',
    rare: '#1565c0',
    epic: '#6a1b9a',
    legendary: '#f57f17',
  }[monster.rarity]

  return (
    

{monster.name}

{monster.rarity.toUpperCase()}

タイプ: {monster.type}

{monster.description}

{onSelect && ( )}
) } export default Overview

実装コード全文|Host(ホスト)側の設定

app-shell/vite.config.ts(ホスト側)

>// app-shell/vite.config.ts

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import federation from '@originjs/vite-plugin-federation'

export default defineConfig({
  plugins: [
    react(),
    federation({
      name: 'app_shell',
      remotes: {
        // 'ローカル参照名': 'リモートのremoteEntry.jsのURL'
        // 開発環境
        overviewApp: 'http://localhost:5000/assets/remoteEntry.js',
        statsApp: 'http://localhost:5001/assets/remoteEntry.js',
        detailsApp: 'http://localhost:5002/assets/remoteEntry.js',
        // 本番環境(環境変数で切り替え)
        // overviewApp: `${process.env.VITE_OVERVIEW_URL}/assets/remoteEntry.js`,
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
      },
    })
  ],
  build: {
    target: 'esnext',
  },
  server: {
    port: 4000,
  }
})

app-shell/src/App.tsx(リモートコンポーネントを利用)

>// app-shell/src/App.tsx

import { Suspense, lazy, useState, useCallback } from 'react'
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'

// リモートコンポーネントを動的インポート(遅延ロード)
// 'overviewApp/Overview' = vite.config.ts の remotes.overviewApp + exposes './Overview'
const Overview = lazy(() => import('overviewApp/Overview'))
const Stats = lazy(() => import('statsApp/Stats'))
const Details = lazy(() => import('detailsApp/Details'))

// リモートコンポーネントのTypeScript型宣言(d.tsファイルで管理)
// 型については後述

// エラーバウンダリでMFEのエラーを隔離する
import { ErrorBoundary } from 'react-error-boundary'

function MFEErrorFallback({ error, resetErrorBoundary }: {
  error: Error
  resetErrorBoundary: () => void
}) {
  return (
    

コンポーネントの読み込みに失敗しました

{error.message}

) } // ローディングスケルトン function Skeleton() { return (
) } const monster = { id: 'dragon-001', name: 'エルダードラゴン', type: '炎', rarity: 'legendary' as const, description: '太古の時代から生き続ける龍。その炎は鋼鉄すら溶かすとされる。', hp: 9999, attack: 850, defense: 600, speed: 120, } function App() { const [selectedId, setSelectedId] = useState(null) const handleSelect = useCallback((id: string) => { setSelectedId(id) }, []) return (
{}}> }> } /> {}}> }> } /> {}}> }>
} />
) } export default App

TypeScript型定義の共有|型宣言ファイルでMFEを型安全にする

Module Federationでリモートのコンポーネントをimportするとき、TypeScriptはリモートのコードを認識できないため型エラーが発生します。型定義ファイル(.d.ts)を手動または自動生成して共有することで型安全を維持できます。

>// app-shell/src/types/remote-modules.d.ts
// リモートモジュールの型宣言ファイル

declare module 'overviewApp/Overview' {
  import { FC } from 'react'

  export interface MonsterData {
    id: string
    name: string
    type: string
    rarity: 'common' | 'rare' | 'epic' | 'legendary'
    description: string
  }

  export interface OverviewProps {
    monster: MonsterData
    onSelect?: (id: string) => void
  }

  const Overview: FC
  export default Overview
}

declare module 'statsApp/Stats' {
  import { FC } from 'react'

  export interface StatsProps {
    monsterId: string
    hp: number
    attack: number
    defense: number
    speed?: number
  }

  const Stats: FC
  export default Stats
}

declare module 'detailsApp/Details' {
  import { FC } from 'react'

  export interface DetailsProps {
    monsterId: string
  }

  const Details: FC
  export default Details
}

@module-federation/dts-plugin を使った自動型生成

>// @module-federation/dts-plugin を使った型定義の自動生成(webpack MF 2.0)
// vite-plugin-federationでは現時点で未対応のため手動宣言が主流

# インストール(webpack Module Federation 2.0 の場合)
npm install @module-federation/dts-plugin --save-dev

// webpack.config.js での設定
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack')

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'overviewApp',
      exposes: {
        './Overview': './src/components/Overview',
      },
      dts: {
        generateTypes: true,   // 型定義を自動生成
        consumeTypes: false,   // ホスト側でtrueにして自動取得
      },
    }),
  ],
}

MFE間の通信方法|カスタムイベント・共有ストア・URLパラメーター

マイクロフロントエンドの最大の難しさの一つが「MFE間でのデータ共有」です。以下の3つのアプローチを状況に応じて使い分けましょう。

アプローチ結合度向いているケース注意点
カスタムイベント疎結合MFE同士が独立している場合イベント名の一元管理が必要
共有ストア(Zustand等)密結合頻繁に状態共有が必要な場合singletonとして共有設定が必須
URLパラメーター最も疎結合ページ遷移と連動する場合URLの肥大化に注意

アプローチ1:カスタムイベント(推奨・疎結合)

>// shared/src/events/mfe-events.ts
// MFE間で共有するカスタムイベントの定義

// イベント名の定数(タイポを防ぐ)
export const MFE_EVENTS = {
  MONSTER_SELECTED: 'mfe:monster-selected',
  CART_UPDATED: 'mfe:cart-updated',
  USER_LOGGED_IN: 'mfe:user-logged-in',
  NAVIGATION_CHANGE: 'mfe:navigation-change',
} as const

// カスタムイベント発火ヘルパー
export function dispatchMFEEvent(eventName: string, detail: T): void {
  const event = new CustomEvent(eventName, {
    detail,
    bubbles: true,     // 親要素にバブリング
    composed: true,    // Shadow DOMの境界を越える
  })
  window.dispatchEvent(event)
}

// カスタムイベント購読ヘルパー
export function subscribeMFEEvent(
  eventName: string,
  handler: (detail: T) => void
): () => void {  // アンサブスクライブ関数を返す
  const listener = (event: Event) => {
    handler((event as CustomEvent).detail)
  }
  window.addEventListener(eventName, listener)
  return () => window.removeEventListener(eventName, listener)
}
>// overview/src/components/Overview.tsx で使用
import { dispatchMFEEvent, MFE_EVENTS } from '../../shared/events/mfe-events'

function Overview({ monster }: OverviewProps) {
  const handleSelect = () => {
    // カスタムイベントで他のMFEに通知
    dispatchMFEEvent(MFE_EVENTS.MONSTER_SELECTED, {
      id: monster.id,
      name: monster.name,
      timestamp: Date.now(),
    })
  }

  return (
    

{monster.name}

) } // details/src/components/Details.tsx で受信 import { subscribeMFEEvent, MFE_EVENTS } from '../../shared/events/mfe-events' import { useEffect, useState } from 'react' function Details() { const [selectedMonster, setSelectedMonster] = useState(null) useEffect(() => { // イベントを購読してモンスターIDを受け取る const unsubscribe = subscribeMFEEvent<{ id: string }>( MFE_EVENTS.MONSTER_SELECTED, ({ id }) => setSelectedMonster(id) ) return unsubscribe // アンマウント時にクリーンアップ }, []) return
選択中: {selectedMonster}
}

アプローチ2:共有ストア(Zustand / Jotai)

>// shared/src/store/mfe-store.ts
// ZustandをModule Federationで共有する

import { create } from 'zustand'
import { devtools } from 'zustand/middleware'

interface GlobalState {
  selectedMonsterId: string | null
  user: { id: string; name: string } | null
  setSelectedMonster: (id: string | null) => void
  setUser: (user: GlobalState['user']) => void
}

export const useGlobalStore = create()(
  devtools(
    (set) => ({
      selectedMonsterId: null,
      user: null,
      setSelectedMonster: (id) => set({ selectedMonsterId: id }),
      setUser: (user) => set({ user }),
    }),
    { name: 'MFE Global Store' }
  )
)

// vite.config.ts で zustand を shared に追加して共有
// shared: {
//   zustand: { singleton: true, requiredVersion: '^4.0.0' },
// }

アプローチ3:URLパラメーター(最もシンプル)

>// URLパラメーターを使ったMFE間の状態共有
// ブラウザバック・リロードにも対応できるシンプルな方法

// overview MFE でURLを変更
function Overview({ monster }: OverviewProps) {
  const navigate = useNavigate()

  const handleSelect = () => {
    // URLパラメーターに選択状態を反映
    navigate(`/details?monsterId=${monster.id}`)
  }

  return 
}

// details MFE でURLパラメーターを読み取る
function Details() {
  const [searchParams] = useSearchParams()
  const monsterId = searchParams.get('monsterId')

  // monsterId を使ってデータフェッチ
  const { data } = useQuery({
    queryKey: ['monster', monsterId],
    queryFn: () => fetchMonsterDetails(monsterId!),
    enabled: !!monsterId,
  })

  return 
{data?.name}
}

webpack Module Federationとの比較

Module Federationはwebpack 5で最初に導入された機能です。Viteの vite-plugin-federation と webpack Module Federationの主な違いを理解しておきましょう。

比較項目webpack Module Federationvite-plugin-federation
成熟度高い(webpack 5で公式サポート)中(コミュニティプラグイン)
SSR対応完全対応制限あり(実験的)
ビルド速度遅い(webpack全体のビルド)速い(Viteのesbuild活用)
HMR完全対応対応(一部制限あり)
型生成@module-federation/dts-plugin手動宣言が必要
設定の複雑さ中〜高低〜中
>// webpack.config.js でのModule Federation設定(参考)
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack')

// Remote側
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'overviewApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Overview': './src/components/Overview',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
      },
    }),
  ],
}

// Host側
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'appShell',
      remotes: {
        overviewApp: 'overviewApp@http://localhost:5000/remoteEntry.js',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
}

依存関係の共有戦略(shared設定の詳細)

>// vite.config.ts の shared 詳細設定
federation({
  shared: {
    react: {
      singleton: true,        // 複数バージョンが混在してもインスタンスは1つ
      requiredVersion: '^18.0.0',  // 必要バージョンを指定
      strictVersion: false,   // trueにするとバージョン不一致でエラー
      eager: true,            // 非同期ではなく同期的にロード(初期ロード速度向上)
    },
    'react-dom': {
      singleton: true,
      requiredVersion: '^18.0.0',
    },
    // 共通ユーティリティも共有可能
    'date-fns': {
      requiredVersion: '^3.0.0',
      // singleton: false(デフォルト)→ 各MFEが独自バージョンを持てる
    },
    // デザインシステムも共有
    '@myapp/design-system': {
      singleton: true,
      requiredVersion: '^1.0.0',
    },
  },
})

パフォーマンス最適化とベストプラクティス

remoteEntry.jsのプリロード

>// app-shell/index.html でremoteEntry.jsをプリロード



  
  
  
  


  

CDNを使ったremoteEntry.jsの配信

>// 本番環境の vite.config.ts(環境変数でリモートURLを切り替え)

const isDev = process.env.NODE_ENV === 'development'
const OVERVIEW_URL = isDev
  ? 'http://localhost:5000'
  : process.env.VITE_OVERVIEW_CDN_URL || 'https://overview.cdn.example.com'

export default defineConfig({
  plugins: [
    federation({
      remotes: {
        overviewApp: `${OVERVIEW_URL}/assets/remoteEntry.js`,
      },
    }),
  ],
})

CI/CDパイプラインの設計|独立デプロイを実現する

># .github/workflows/deploy-overview.yml
# overview MFEの独立したCI/CDパイプライン

name: Deploy Overview MFE

on:
  push:
    branches: [main]
    paths:
      - 'overview/**'   # overview配下の変更時のみトリガー

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build Overview MFE
        working-directory: overview
        run: pnpm build
        env:
          NODE_ENV: production

      - name: Run tests
        working-directory: overview
        run: pnpm test

      # S3 + CloudFrontへのデプロイ例
      - name: Deploy to S3
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Upload to S3
        run: |
          aws s3 sync overview/dist/ s3://my-mfe-overview/ --delete
          # remoteEntry.js はキャッシュなし(即時反映)
          aws s3 cp overview/dist/assets/remoteEntry.js \
            s3://my-mfe-overview/assets/remoteEntry.js \
            --cache-control "no-cache, no-store, must-revalidate"

      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
            --paths "/assets/remoteEntry.js"
>【マイクロフロントエンドのデプロイ戦略 重要ポイント】

1. remoteEntry.js のキャッシュ設定
   → Cache-Control: no-cache(毎回最新を取得)
   → または Content-Hash付きファイル名 + 長期キャッシュ(非推奨)
   → remoteEntry.jsは必ず最新バージョンが反映されるようにすること

2. Blue-Greenデプロイとの組み合わせ
   → 新バージョンのRemoteをステージング環境でテスト後に本番切り替え

3. Feature Flagsとの組み合わせ
   → LaunchDarklyやUnleashでコンポーネント単位のフィーチャーフラグ管理

4. バージョニング戦略
   → remoteEntry.js は 常に最新 or バージョン付きURL (/v1.2.0/remoteEntry.js)
   → バージョン付きの場合はホスト側の設定変更が必要(デプロイ連携が必要)

開発時のデバッグとトラブルシューティング

>【よくあるエラーと解決策】

❌ エラー1: "Failed to fetch dynamically imported module"
→ リモートアプリが起動していない or URLが間違っている
→ 確認: curl http://localhost:5000/assets/remoteEntry.js が返ってくるか
→ 解決: vite preview は build 後に動作。先に各Remoteを build && preview

❌ エラー2: "Uncaught SyntaxError: Unexpected token '<'"
→ remoteEntry.js の代わりにHTMLが返っている(404ページ等)
→ 確認: ブラウザのNetworkタブでremoteEntry.jsの実際のレスポンスを確認

❌ エラー3: "Module not found: 'overviewApp/Overview'"
→ vite.config.ts の remotes 設定が間違っている
→ 確認: `overviewApp` のキー名と import('overviewApp/Overview') の名前が一致しているか

❌ エラー4: "React is not defined" または "Cannot read properties of null (reading 'useState')"
→ Reactが複数インスタンス存在している
→ 解決: shared に react を singleton: true で設定する

❌ エラー5: TypeScriptで "Cannot find module 'overviewApp/Overview'"
→ 型定義ファイルが存在しない
→ 解決: remote-modules.d.ts を作成して型宣言を追加する

よくある質問

Module Federationを使うと初期ロードが遅くなりますか?

lazy() + <Suspense> でコンポーネントを遅延ロードすれば、必要なMFEだけを必要なタイミングで読み込めるため初期ロードへの影響を最小化できます。remoteEntry.jsの取得にネットワークラウンドトリップが発生するため、CDNを使った配信・HTTP/2多重化・プリロードの組み合わせで改善できます。

異なるバージョンのReactを使っているMFE間でも動きますか?

shared の設定で singleton: true を指定することで1つのReactインスタンスを共有できます。ただしメジャーバージョンが大きく異なる場合(React 17 vs 18など)はランタイムエラーが発生する可能性があります。MFE間でフレームワークのメジャーバージョンを揃えることを強く推奨します。どうしても異なるバージョンを使いたい場合は、Web ComponentsベースのアーキテクチャやiFrameを検討してください。

vite-plugin-federation でSSR(Next.js)は使えますか?

現時点(2026年3月)では vite-plugin-federation のSSRサポートは実験的です。SSR環境でModule Federationを使いたい場合はwebpack Module Federation(特に Module Federation 2.0)の使用を推奨します。Next.js 14以降では @module-federation/nextjs-mf が使用できます。

MFEのテスト戦略はどうすべきですか?

テスト戦略は3層で構成します。(1)各MFEの単体テスト:Vitest + Testing Libraryで通常どおり実施。(2)統合テスト:ホストアプリにすべてのMFEをマウントしてE2Eに近いテスト(Playwright推奨)。(3)コントラクトテスト:HostとRemote間のプロパティ型・インターフェースの一致を自動検証。MSW(Mock Service Worker)でAPIをモックしながらMFE単体でもテストできる環境を整えることが重要です。

マイクロフロントエンドでのデザインシステム管理はどうするべきですか?

デザインシステムは独立した共有パッケージとして管理するのが最もスケーラブルです。npmパッケージとして公開してすべてのMFEがインストールする方法、Module Federationの shared にランタイム共有する方法、Storybookで各MFEが独立してコンポーネント確認する方法を組み合わせます。CSSカスタムプロパティをプレーンCSSとして提供することで、どのフレームワークのMFEでも統一的なスタイルを適用できます。


まとめ

  • マイクロフロントエンド:大規模チームで有効なアーキテクチャ。小規模プロジェクトには過剰設計になりやすい
  • Module Federation:ビルド時ではなくランタイムで別アプリのコンポーネントを読み込む仕組み。各MFEを独立してビルド・デプロイできる
  • Viteでの実装:vite-plugin-federation を使い、Remote側でexposes・Host側でremotesを設定する。build.target: 'esnext' が必須
  • コンポーネント利用lazy(() => import('overviewApp/Overview')) でリモートコンポーネントを通常のインポートと同様に使える
  • 型安全remote-modules.d.ts に型宣言を追加することでTypeScriptの型エラーを解消
  • MFE間通信:カスタムイベント(疎結合)・共有ストア(密結合)・URLパラメーター(シンプル)の3つを使い分ける
  • エラーハンドリング:各MFEを ErrorBoundary でラップして障害の影響範囲を限定する
  • CI/CD:各MFEが独立したパイプラインを持ち、変更されたMFEだけを再デプロイできる構成にする
  • remoteEntry.jsのキャッシュCache-Control: no-cache で必ず最新バージョンを提供するよう設定する

Module Federationはフロントエンドの大規模化に対応する有力なアーキテクチャです。まず小さなサンプルプロジェクト(ホスト1つ+リモート1つ)で動作を確認してから、実プロダクトへの導入を検討しましょう。チーム規模とドメインの分離度を見極めてマイクロフロントエンドを採用するかどうかを判断することが成功の鍵です。


WithCodeを体験できる初級コース公開中!

この記事を書いた人

WithCode(ウィズコード)は「目指すなら稼げる人材」をビジョンに、累計400名以上のフリーランスを輩出してきた超実践型プログラミングスクールです。150社以上の実案件支援を特徴にWeb制作・Webデザインなどの役立つ情報を現場のノウハウに基づいて発信していきます。

– service –WithGroupの運営サービス

  • WithCode
    - ウィズコード -

    スクール

    「未経験」から
    現場で通用する
    スキルを身に付けよう!

    詳細はこちら
  • WithFree
    - ウィズフリ -

    実案件サポート

    制作会社のサポート下で
    実務経験を積んでいこう!

    詳細はこちら
  • WithCareer
    - ウィズキャリ -

    就転職サポート

    大手エージェントのサポート下で
    キャリアアップを目指そう!

    詳細はこちら

公式サイト より
今すぐ
無料カウンセリング
予約!

目次