



WithCodeMedia-1-pc
WithCodeMedia-2-pc
WithCodeMedia-3-pc
WithCodeMedia-4-pc




WithCodeMedia-1-sp
WithCodeMedia-2-sp
WithCodeMedia-3-sp
WithCodeMedia-4-sp








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



Module Federationを使えばそれが実現できるんじゃ!複数のフロントエンドアプリをランタイムで結合して、あたかも1つのアプリのように動かせるぞ。ViteではvitePlugin-federationで同様のことができる。MFE間通信・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-spa | MFEオーケストレーターライブラリ | フレームワーク混在・ルートアプリ管理 | 学習コスト高・設定量が多い | React/Vue/Angular混在 |
| npmパッケージ | 共通コンポーネントをnpm公開 | 型安全・バージョン管理が明確 | デプロイのたびに再ビルドが必要 | デザインシステム・UIライブラリ |
>【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
>// 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
// 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
>// 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
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
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 を使った型定義の自動生成(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間でのデータ共有」です。以下の3つのアプローチを状況に応じて使い分けましょう。
| アプローチ | 結合度 | 向いているケース | 注意点 |
|---|---|---|---|
| カスタムイベント | 疎結合 | MFE同士が独立している場合 | イベント名の一元管理が必要 |
| 共有ストア(Zustand等) | 密結合 | 頻繁に状態共有が必要な場合 | singletonとして共有設定が必須 |
| URLパラメーター | 最も疎結合 | ページ遷移と連動する場合 | URLの肥大化に注意 |
>// 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}
}
>// 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' },
// }
>// 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}
}
Module Federationはwebpack 5で最初に導入された機能です。Viteの vite-plugin-federation と webpack Module Federationの主な違いを理解しておきましょう。
| 比較項目 | webpack Module Federation | vite-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 },
},
}),
],
}
>// 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',
},
},
})
>// app-shell/index.html で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`,
},
}),
],
})
># .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 を作成して型宣言を追加する
lazy() + <Suspense> でコンポーネントを遅延ロードすれば、必要なMFEだけを必要なタイミングで読み込めるため初期ロードへの影響を最小化できます。remoteEntry.jsの取得にネットワークラウンドトリップが発生するため、CDNを使った配信・HTTP/2多重化・プリロードの組み合わせで改善できます。
shared の設定で singleton: true を指定することで1つのReactインスタンスを共有できます。ただしメジャーバージョンが大きく異なる場合(React 17 vs 18など)はランタイムエラーが発生する可能性があります。MFE間でフレームワークのメジャーバージョンを揃えることを強く推奨します。どうしても異なるバージョンを使いたい場合は、Web ComponentsベースのアーキテクチャやiFrameを検討してください。
現時点(2026年3月)では vite-plugin-federation のSSRサポートは実験的です。SSR環境でModule Federationを使いたい場合はwebpack Module Federation(特に Module Federation 2.0)の使用を推奨します。Next.js 14以降では @module-federation/nextjs-mf が使用できます。
テスト戦略は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でも統一的なスタイルを適用できます。
build.target: 'esnext' が必須lazy(() => import('overviewApp/Overview')) でリモートコンポーネントを通常のインポートと同様に使えるremote-modules.d.ts に型宣言を追加することでTypeScriptの型エラーを解消Cache-Control: no-cache で必ず最新バージョンを提供するよう設定するModule Federationはフロントエンドの大規模化に対応する有力なアーキテクチャです。まず小さなサンプルプロジェクト(ホスト1つ+リモート1つ)で動作を確認してから、実プロダクトへの導入を検討しましょう。チーム規模とドメインの分離度を見極めてマイクロフロントエンドを採用するかどうかを判断することが成功の鍵です。
公式サイト より
今すぐ
無料カウンセリング
を予約!