테마
마이크로프론트엔드 통합 방식 6가지 개요
서버 조합부터 모듈 페더레이션까지, 마이크로프론트엔드를 하나의 화면으로 합치는 여섯 가지 전략을 분류하고 비교한다.
학습 목표
- 마이크로프론트엔드의 6가지 통합 방식을 분류 기준(서버/빌드/클라이언트)과 함께 설명할 수 있다
- 각 통합 방식의 동작 원리, 장점, 단점을 비교할 수 있다
- Linked SPA와 Unified SPA의 차이를 이해하고, 프로젝트에 적합한 통합 전략을 판단할 수 있다
- Module Federation의 Host/Remote 개념을 이해하고 왜 가장 현대적인 방식인지 설명할 수 있다
1. 통합 방식 분류 체계
마이크로프론트엔드를 구현할 때 가장 먼저 결정해야 하는 것은 "어디서, 언제 통합하는가" 이다. 통합이 일어나는 시점과 위치에 따라 크게 세 가지 범주로 나눌 수 있다.
| 범주 | 통합 시점 | 대표 방식 |
|---|---|---|
| 서버사이드 | 요청 시(런타임) | SSI (Server-Side Include) |
| 빌드타임 | 빌드 시(컴파일) | 빌드타임 통합 |
| 클라이언트사이드 | 브라우저 런타임 | iframe, Web Components, JS 통합, Module Federation |
아래 도식은 이 분류를 한눈에 보여준다.
2. 방식 1 - Server-Side Template Composition (SSI)
개념
SSI(Server-Side Include) 는 웹 서버(주로 Nginx)가 HTML 응답을 보내기 직전에 여러 프래그먼트(HTML 조각)를 합성하는 기술이다. 각 마이크로앱 팀은 자기 영역의 HTML을 독립적으로 배포하고, 서버가 요청 시점에 이를 하나로 조합한다.
동작 흐름
템플릿 예시
html
<!-- Nginx SSI 템플릿 -->
<html>
<body>
<!--# include virtual="/fragments/header" -->
<!--# include virtual="/fragments/content" -->
<!--# include virtual="/fragments/footer" -->
</body>
</html>장단점
| 장점 | 단점 |
|---|---|
| 검색엔진 최적화(SEO)에 유리 | 매 요청마다 서버에서 조합 -> 서버 부하 |
| 첫 화면 로딩이 빠름 (완성된 HTML) | SPA 느낌이 아님 (페이지 전환 시 새로고침) |
| 프래그먼트 간 기술 스택 독립 | 클라이언트 상호작용이 복잡해지면 한계 |
| Nginx 설정만으로 구현 가능 | 프래그먼트 간 통신이 어려움 |
3. 방식 2 - 빌드타임 통합
개념
각 마이크로앱을 npm 패키지로 배포하고, 통합 앱이 이를 의존성으로 설치한 뒤 빌드 시점에 하나의 번들로 합치는 방식이다.
예시
json
{
"name": "container-app",
"dependencies": {
"@team-a/header": "^2.1.0",
"@team-b/product-list": "^1.5.0",
"@team-c/checkout": "^3.0.0"
}
}장단점
| 장점 | 단점 |
|---|---|
| 구현이 매우 단순 | 독립 배포 불가 - 하나만 바뀌어도 전체 재빌드 |
| 타입 안전성 확보 (빌드 시 검증) | 버전 관리가 복잡해짐 |
| 런타임 오버헤드 없음 | 엄밀히 MFE라 보기 어려움 (독립 배포 원칙 위배) |
핵심 포인트: 마이크로프론트엔드의 가장 중요한 원칙은 독립적 배포이다. 빌드타임 통합은 이 원칙을 위반하므로, "마이크로프론트엔드를 위한 공통 라이브러리 배포"에는 적합하지만 마이크로앱 자체의 통합 방식으로는 권장하지 않는다.
4. 방식 3 - iframe 통합
개념
HTML <iframe> 태그를 사용하여 다른 페이지(마이크로앱)를 현재 페이지 안에 삽입하는 방식이다. 브라우저가 제공하는 가장 강력한 격리(isolation) 를 얻을 수 있다.
예시
html
<div id="app-shell">
<nav>공통 네비게이션</nav>
<iframe src="https://team-a.example.com/product"
title="상품 목록"
style="width:100%; height:600px; border:none;">
</iframe>
</div>격리 수준
- CSS 완전 분리 (Shadow DOM 없이도 스타일 충돌 없음)
- JavaScript 실행 컨텍스트 완전 분리
- 전역 변수, 이벤트 리스너 격리
장단점
| 장점 | 단점 |
|---|---|
| CSS/JS 완전 격리 | 성능 저하 (iframe마다 별도 문서 로딩) |
| 구현 난이도 최하 | 접근성(a11y) 문제 |
| 기술 스택 완전 독립 | SEO 불리 (검색엔진이 iframe 내부 인덱싱 어려움) |
| 보안 격리 (sandbox 속성) | 반응형 레이아웃 구현 어려움 |
| iframe 간 통신은 postMessage에 의존 | |
| 라우팅 동기화 복잡 |
5. 방식 4 - Web Components 통합
개념
Custom Elements와 Shadow DOM이라는 웹 표준 API를 사용하여 캡슐화된 컴포넌트를 만드는 방식이다. 각 마이크로앱을 커스텀 엘리먼트로 정의하면, 프레임워크와 무관하게 HTML 태그처럼 사용할 수 있다.
예시
javascript
// 마이크로앱 팀에서 정의
class ProductList extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>/* 이 스타일은 Shadow DOM 안에서만 적용 */</style>
<div class="product-list">...</div>
`;
}
disconnectedCallback() {
// 정리 로직
}
}
customElements.define('micro-product-list', ProductList);html
<!-- 앱쉘에서 사용 -->
<micro-product-list category="electronics"></micro-product-list>장단점
| 장점 | 단점 |
|---|---|
| 웹 표준 (프레임워크 중립) | 브라우저 호환성 이슈 (구형 브라우저) |
| Shadow DOM으로 스타일 격리 | SSR(서버사이드 렌더링) 지원 제한적 |
| HTML 태그처럼 직관적 사용 | 개발 도구 지원이 React/Vue에 비해 부족 |
| 라이프사이클 콜백 제공 | Shadow DOM 안팎 통신이 까다로움 |
6. 방식 5 - JavaScript 통합
개념
각 마이크로앱이 mount()와 unmount() 같은 JavaScript 함수를 노출하고, 앱쉘(컨테이너)이 라우팅에 따라 해당 함수를 호출하여 마이크로앱을 동적으로 마운트/언마운트하는 방식이다. single-spa 프레임워크가 대표적이다.
예시
javascript
// 마이크로앱이 노출하는 인터페이스
export function mount(container) {
ReactDOM.render(<App />, container);
}
export function unmount(container) {
ReactDOM.unmountComponentAtNode(container);
}javascript
// 앱쉘에서 라우팅에 따라 호출
const apps = {
'/products': () => import('microapp-products'),
'/checkout': () => import('microapp-checkout'),
};
async function navigate(path) {
const { mount } = await apps[path]();
mount(document.getElementById('app-container'));
}장단점
| 장점 | 단점 |
|---|---|
| 가장 유연한 방식 | 앱쉘의 오케스트레이션 로직이 복잡해질 수 있음 |
| 프레임워크 독립적 | 공유 의존성 관리 필요 (React 중복 로딩 등) |
| SPA 수준의 부드러운 전환 | 글로벌 상태/스타일 충돌 가능 |
| 점진적 마이그레이션에 적합 | 명확한 인터페이스 계약 필요 |
7. 방식 6 - Module Federation (모듈 페더레이션)
개념
Webpack 5에서 도입된 기능으로, 런타임에 다른 애플리케이션의 모듈을 동적으로 로딩하는 방식이다. 빌드 시에는 각 앱이 독립적으로 빌드되지만, 실행 시에는 필요한 모듈을 네트워크를 통해 가져와 마치 로컬 모듈처럼 사용한다.
Host/Remote 구조
핵심 키워드
| 용어 | 설명 |
|---|---|
| Host | 다른 앱의 모듈을 소비(consume)하는 앱. 주로 앱쉘 |
| Remote | 자신의 모듈을 외부에 노출(expose)하는 앱 |
| remoteEntry.js | Remote 앱의 모듈 매니페스트. Host가 이 파일을 통해 모듈에 접근 |
| shared | 중복 로딩을 방지하기 위해 공유하는 라이브러리 (예: React) |
| exposes | Remote가 외부에 공개하는 모듈 목록 |
| remotes | Host가 참조하는 Remote 앱 목록 |
Webpack 설정 예시
javascript
// Remote 앱 (상품 서비스) webpack.config.js
new ModuleFederationPlugin({
name: 'productApp',
filename: 'remoteEntry.js',
exposes: {
'./ProductList': './src/components/ProductList',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});
// Host 앱 (앱쉘) webpack.config.js
new ModuleFederationPlugin({
name: 'shell',
remotes: {
productApp: 'productApp@https://product.example.com/remoteEntry.js',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});장단점
| 장점 | 단점 |
|---|---|
| 독립 빌드 + 독립 배포 | Webpack 5 이상 필수 (Vite는 별도 플러그인) |
| 런타임 동적 로딩 | 설정 복잡도가 높음 |
| 공유 의존성으로 중복 제거 | 버전 불일치 시 런타임 에러 가능 |
| 가장 현대적이고 실용적 | 디버깅이 어려울 수 있음 |
| 점진적 도입 가능 |
8. Linked SPA vs Unified SPA
마이크로프론트엔드를 구성하는 방법은 크게 Linked SPA와 Unified SPA 두 가지 패러다임으로 나눌 수 있다. 이 구분은 "사용자 경험이 얼마나 매끄러운가"에 직접적인 영향을 준다.
비교
상세 비교표
| 기준 | Linked SPA | Unified SPA |
|---|---|---|
| 전환 방식 | 하이퍼링크 이동 (새로고침 발생) | 클라이언트 라우팅 (새로고침 없음) |
| 사용자 경험 | 페이지 전환 시 깜빡임 | SPA처럼 부드러운 전환 |
| 구현 난이도 | 매우 낮음 (각 앱 독립 배포만) | 높음 (앱쉘, 오케스트레이션 필요) |
| 상태 공유 | 어려움 (URL 파라미터, 쿠키 등) | 가능 (메모리 공유, 이벤트 버스) |
| 기술 스택 자유도 | 완전 자유 | 일부 제약 (공유 런타임 필요할 수 있음) |
| 독립 배포 | 완전 독립 | 앱쉘과의 계약 필요 |
| 대표 구현 | Nginx 리버스 프록시, SSI | Module Federation, single-spa |
선택 가이드
- Linked SPA가 적합한 경우: 서비스 간 이동 빈도가 낮고, 기술 스택이 완전히 다르며, 빠르게 분리만 하면 되는 상황
- Unified SPA가 적합한 경우: 하나의 서비스처럼 느껴져야 하고, 서비스 간 이동이 빈번하며, 공유 상태가 필요한 상황
9. 6가지 방식 종합 비교
| 기준 | SSI | 빌드타임 | iframe | Web Components | JS 통합 | Module Federation |
|---|---|---|---|---|---|---|
| 통합 시점 | 서버 | 빌드 | 클라이언트 | 클라이언트 | 클라이언트 | 클라이언트 |
| 독립 배포 | O | X | O | O | O | O |
| 격리 수준 | 없음 | 없음 | 최고 | 높음 (Shadow DOM) | 낮음 | 낮음 |
| SPA 경험 | X | O | 부분적 | O | O | O |
| SEO | 우수 | 우수 | 불리 | 보통 | 보통 | 보통 |
| 성능 | 보통 | 최고 | 낮음 | 보통 | 높음 | 높음 |
| 기술 자유도 | 높음 | 낮음 | 최고 | 높음 | 높음 | 보통 |
| 구현 복잡도 | 낮음 | 최저 | 최저 | 보통 | 높음 | 높음 |
| 공유 의존성 관리 | N/A | 빌드 시 | N/A | 수동 | 수동 | 자동 |
핵심 정리
통합 방식은 크게 서버사이드, 빌드타임, 클라이언트사이드 세 범주로 나뉜다. 각 범주는 통합이 일어나는 시점과 위치가 다르며, 이에 따라 성능, 격리, 배포 유연성 특성이 달라진다.
빌드타임 통합은 독립 배포가 불가능하여 엄밀한 의미의 MFE가 아니다. 공통 라이브러리 배포에는 적합하지만, 마이크로앱 자체의 통합 방식으로는 부적절하다.
iframe은 격리가 가장 강력하지만 성능, 접근성, SEO에서 대가를 치른다. 레거시 통합이나 보안 요구가 극히 높은 경우에 한정적으로 사용한다.
JavaScript 통합(single-spa 등)이 가장 유연하고, Module Federation이 가장 현대적이고 실용적이다. 두 방식 모두 Unified SPA를 구현할 수 있으며, Module Federation은 공유 의존성 관리까지 자동화해준다.
Linked SPA는 간단하지만 페이지 전환이 끊기고, Unified SPA는 복잡하지만 매끄러운 사용자 경험을 제공한다. 프로젝트의 UX 요구사항에 따라 선택한다.
다음 단계
각 통합 방식을 선택했다면, 여러 마이크로앱이 일관된 사용자 경험을 제공하도록 공통 모듈과 앱쉘(App Shell) 을 설계해야 한다. 다음 문서에서는 빌드타임 공통 모듈의 종류와 관리 원칙, 앱쉘의 라우팅 구조와 마운트 방식을 다룬다.