Vite + Module Federation 기반 React 마이크로 프론트엔드 구축과 독립 배포 파이프라인

거대한 모놀리식(Monolithic) 프론트엔드 애플리케이션은 팀 규모가 커짐에 따라 빌드 시간 증가, 코드 충돌, 단일 배포 실패로 인한 전체 서비스 장애라는 치명적인 한계에 직면합니다. 이러한 문제를 해결하기 위해 백엔드의 마이크로서비스 아키텍처(MSA) 개념을 프론트엔드에 적용한 **마이크로 프론트엔드(Micro-frontends)**가 주목받고 있습니다.
과거에는 Webpack 5의 Module Federation이 전유물이었으나, 최근에는 빠른 HMR과 ESM 기반 빌드 속도를 자랑하는 Vite 환경에서도 Module Federation 2.0 및 @originjs/plugin-module-federation (또는 @module-federation/vite)을 통해 완벽한 마이크로 프론트엔드 아키텍처를 구현할 수 있습니다.
이 글에서는 Host(Shell) 앱과 Remote 앱 간의 런타임 모듈 공유, 싱글톤 의존성 통제, TypeScript 타입 동기화, 그리고 Cloudflare Pages/GitHub Actions 기반 독립 배포 파이프라인 구축 기법을 실전 코드와 함께 다룹니다.
핵심 요약
- Module Federation 핵심 가치: 빌드 타임이 아닌 런타임에 원격 모듈(Remote)을 다운로드하여 셸(Host) 애플리케이션에 결합하므로, 각 마이크로 앱은 서로의 빌드 과정 없이 완전히 독립적으로 배포될 수 있습니다.
- Vite 런타임 공유:
react,react-dom과 같은 무거운 공통 라이브러리는shared설정을 통해 싱글톤(Singleton)으로 묶어 중복 로드를 차단하고 메모리 사용량을 최소화합니다.- 독립 배포 파이프라인: Remote 앱 배포 시 Host 앱을 다시 빌드하거나 재배포하지 않아도 되며,
remoteEntry.jsURL 매핑 변경만으로 Zero-Downtime 및 즉각적인 카나리(Canary) 배포가 가능합니다.- 오류 격리 (Error Isolation): 특정 Remote 앱에 런타임 예외가 발생하더라도 React
ErrorBoundary로 해당 영역만 처리하여 Host 전체 서비스가 마비되는 것을 차단합니다.
1. Vite 기반 Module Federation 아키텍처 구성
마이크로 프론트엔드는 애플리케이션의 뼈대와 레이아웃, 공통 인증을 담당하는 Host (Shell) App과, 개별 도메인 기능(결제, 상품 목록, 마이페이지)을 독립 제공하는 Remote App으로 구성됩니다.
+------------------------------------------------------------------+
| Host App (Shell) |
| - Layout Header / Sidebar |
| - Auth State / Router Core |
| |
| +--------------------------+ +--------------------------+ |
| | Payment Remote (App 1) | | Product Remote (App 2) | |
| | https://pay.domain.com | | https://prod.domain.com | |
| | /remoteEntry.js | | /remoteEntry.js | |
| +--------------------------+ +--------------------------+ |
+------------------------------------------------------------------+
2. Remote App 구성 (vite.config.ts)
Remote 앱은 외부에서 호출할 수 있는 컴포넌트 및 모듈을 exposes 옵션으로 노출합니다.
// remote-payment/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from '@originjs/plugin-module-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'paymentApp',
filename: 'remoteEntry.js',
exposes: {
'./CheckoutForm': './src/components/CheckoutForm.tsx',
'./paymentStore': './src/stores/paymentStore.ts',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0 || ^19.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0 || ^19.0.0' },
},
}),
],
build: {
target: 'esnext',
minify: false,
cssCodeSplit: false,
},
});
3. Host App 구성 및 Dynamic Lazy Loading
Host 앱은 Remote 앱의 remoteEntry.js 주소를 정의하고, React.lazy와 Suspense를 결합하여 컴포넌트를 Dynamic Import합니다.
// host-shell/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from '@originjs/plugin-module-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'hostShell',
remotes: {
paymentApp: 'https://payment-micro.pages.dev/assets/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
],
});
3.1 Host App에서 Remote Component 마운트 및 Error Boundary 처리
// host-shell/src/pages/CheckoutPage.tsx
import React, { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
// Remote 모듈 동적 임포트
const RemoteCheckoutForm = React.lazy(() => import('paymentApp/CheckoutForm'));
function RemoteFallback() {
return (
<div className="p-4 bg-red-50 text-red-600 rounded-lg">
결제 모듈을 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.
</div>
);
}
export function CheckoutPage() {
return (
<div className="max-w-xl mx-auto p-6 space-y-4">
<h1 className="text-2xl font-bold">주문 결제</h1>
<ErrorBoundary FallbackComponent={RemoteFallback}>
<Suspense fallback={<div className="animate-pulse h-40 bg-gray-200 rounded" />}>
<RemoteCheckoutForm orderId="ORD-2026-9901" amount={45000} />
</Suspense>
</ErrorBoundary>
</div>
);
}
4. 모듈간 싱글톤 의존성과 성능 비교
Module Federation 미적용 시와 적용 시의 번들 크기 및 독립 배포 효율 비교표입니다.
| 평가 항목 | 모놀리식 (Monolith) | Context/Iframe 방식 | Module Federation 2.0 |
|---|---|---|---|
| 개발 및 독립 배포 | 전체 재빌드 필요 | 독립 가능 | 독립 재빌드 & 런타임 결합 |
| React 싱글톤 보장 | 보장됨 | 메모리 분리 (이중 로드) | shared 옵션으로 보장 |
| 초기 로딩 속도 | 번들 크기 큼 | Iframe 개별 파싱 지연 | 필요 모듈만 Lazy Load |
| 런타임 에러 전파 | 앱 전체 렌더링 중단 | 완전 격리 | ErrorBoundary로 영역 격리 |
| CI/CD 배포 시간 | 10~20분 | 2~3분 | Remote당 1분 이내 완료 |
5. Cloudflare Pages 기반 CI/CD 독립 배포 파이프라인
Remote 앱의 독립 배포를 위해 GitHub Actions와 Cloudflare Pages CLI를 연동한 워크플로우 예시입니다.
# .github/workflows/deploy-payment-remote.yml
name: Deploy Payment Remote App
on:
push:
branches: [main]
paths:
- 'apps/remote-payment/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- name: Install Dependencies
run: pnpm install --filter remote-payment...
- name: Build Remote App
run: pnpm --filter remote-payment build
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: payment-micro
directory: apps/remote-payment/dist
gitBranch: main
이 파이프라인이 실행되면 Host 앱의 변경이나 배포 없이 payment-micro.pages.dev상의 remoteEntry.js가 최신 버전으로 갱신되며, 유저는 새로고침 시 즉시 업데이트된 결제 컴포넌트를 사용하게 됩니다.