Micro Frontend와 Module Federation 도입 판단 기준

Micro Frontend는 화면 하나를 여러 팀이 따로 만들고 따로 배포하게 해 주지만, 그 대가로 런타임 통합·공유 의존성·디자인 일관성이라는 새 문제를 가져온다. 이 글은 Micro Frontend가 실제로 필요한 조건과 필요 없는 조건을 가르는 기준, Module Federation의 동작 방식과 설정 예시, 운영에서 자주 깨지는 지점을 정리한다.

도시 계획가가 큰 탁자 위 지도에 서로 다른 동네 모형 블록을 배치하고 가는 길로 잇는 작업실

한눈에 보기

Micro Frontend는 하나의 기술이 아니라 "프론트엔드를 어느 시점에 합치느냐"에 대한 선택지 묶음이다. 합치는 시점이 늦을수록 팀은 독립적이 되고, 운영은 복잡해진다.

통합 방식 합치는 시점 독립 배포 대표 수단 맞는 경우
단일 앱 + 모듈 경계 빌드 불가 폴더 규칙, 린트 경계 팀 1~2개, 배포 주기 같음
빌드 타임 통합 빌드 불가(셸 재배포 필요) 사내 npm 패키지, 모노레포 공통 부품 재사용이 목적
페이지 단위 분리 라우팅 가능 경로별 별도 앱, iframe 화면 간 상태 공유가 거의 없음
런타임 통합 브라우저 실행 가능 Module Federation, single-spa 한 화면에 여러 팀 모듈이 섞임

아래 흐름은 이 표를 질문 세 개로 줄인 것이다. 앞 질문에서 "아니오"가 나오면 더 단순한 방식에서 멈춘다.

독립 배포가 필요한 팀이 여럿인지, 한 화면 안에서 런타임 통합이 필요한지, 공유 의존성 버전을 합의할 수 있는지를 차례로 물어 Module Federation 도입 여부를 정하는 흐름

Micro Frontend가 푸는 문제는 조직 문제다

Micro Frontend를 고려하게 되는 신호는 대부분 코드가 아니라 일정에서 나온다.

  • 주문 팀의 수정이 결제 팀의 회귀 테스트를 기다리느라 배포가 일주일씩 밀린다.
  • 프론트엔드 저장소 하나에 여러 팀의 PR이 몰려 충돌 해결이 일과가 된다.
  • 한 팀이 프레임워크를 올리려면 모든 팀이 같은 날 맞춰야 한다.

반대로 팀이 하나거나, 여러 팀이 있어도 같은 릴리스 주기로 움직인다면 Micro Frontend는 문제를 풀지 않고 비용만 더한다. 이때는 코드 경계를 먼저 세운다. 백엔드에서 서비스를 쪼개기 전에 모놀리식 시스템을 Modular Monolith로 전환하는 설계 전략을 거치는 것과 같은 순서다. 폴더 경계와 의존 방향 린트만으로도 충돌 대부분이 줄어든다.

도입 판단 기준 다섯 가지

1. 독립 배포가 필요한 팀 수
배포 일정이 서로 다른 프론트엔드 팀이 셋 이상이고, 각 팀이 화면 영역을 소유한다면 검토할 가치가 있다. 둘 이하라면 배포 일정을 맞추는 비용이 런타임 통합 운영 비용보다 대체로 싸다.

2. 배포 독립성의 실제 가치
"독립 배포"가 주 몇 회의 릴리스를 막고 있는지 센다. 셸 앱 재배포가 하루 한 번 자동으로 나가는 조직이라면 빌드 타임 통합으로 충분하다.

3. 공유 의존성 버전을 합의할 수 있는가
React 같은 라이브러리는 한 페이지에 한 벌만 있어야 한다. 팀마다 메이저 버전이 다르고 맞출 계획이 없다면 런타임 통합은 맞지 않는다. 이때는 페이지 단위로 앱을 나눈다.

4. 번들 중복을 감당할 수 있는가
공유 설정이 빠진 모듈은 자기 의존성을 따로 싣는다. 같은 라이브러리를 세 팀이 각각 내려받으면 첫 화면이 그만큼 무거워진다. 렌더링 방식별 성능 영향은 SSR·SSG·ISR·CSR 렌더링 전략과 성능 비교에서 따로 다룬다.

5. 디자인 시스템을 누가 소유하는가
버튼 하나가 팀마다 다른 버전으로 그려지면 사용자는 한 화면에서 두 개의 제품을 본다. 디자인 시스템 전담 소유자와 버전 정책이 없다면 런타임 통합 전에 그것부터 만든다.

Module Federation이 동작하는 방식

Module Federation은 Webpack 5에 들어간 기능으로, 빌드 결과물 하나가 다른 빌드의 모듈을 브라우저에서 실행 중에 불러오게 한다. 역할은 세 가지다.

  • Host(셸): 페이지 뼈대와 라우팅을 맡고, 어떤 Remote를 어디서 불러올지 안다.
  • Remote: 팀이 소유한 화면 모듈을 remoteEntry.js라는 진입 파일로 노출한다. 자기 일정대로 배포한다.
  • Shared scope: Host와 Remote가 같이 쓰는 라이브러리를 버전 범위를 맞춰 한 벌만 올리는 공간이다.

셸 앱이 주문·결제 Remote의 remoteEntry.js를 런타임에 불러오고, 각 Remote는 팀 CI가 독립 배포하며, React는 shared scope에서 한 벌만 공유되는 구조

Remote 팀이 새 버전을 배포하면, 셸을 다시 빌드하지 않아도 다음 페이지 로드부터 새 모듈이 실행된다. 이 성질이 독립 배포의 핵심이고, 동시에 "셸이 모르는 사이에 화면이 바뀐다"는 운영 위험의 출처다. 배포 단계를 나눠 위험을 줄이는 방법은 Blue-Green·Canary·Rolling 배포 전략 비교의 원칙을 Remote 단위에 그대로 적용한다.

설정 예시: Host와 Remote

Webpack 5의 ModuleFederationPlugin 기준 최소 설정이다. 주소와 버전은 예시다.

// order 팀 (Remote) webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'order',
      filename: 'remoteEntry.js',
      exposes: { './OrderPage': './src/OrderPage' },
      shared: {
        react: { singleton: true, requiredVersion: '^18.2.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
      },
    }),
  ],
};
// 셸 (Host) webpack.config.js
new ModuleFederationPlugin({
  name: 'shell',
  remotes: { order: 'order@https://cdn.example.com/order/remoteEntry.js' },
  shared: {
    react: { singleton: true, requiredVersion: '^18.2.0' },
    'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
  },
});

Host의 진입 파일은 import('./bootstrap') 한 줄로 두고 실제 시작 코드는 bootstrap.js로 옮긴다. 공유 모듈은 비동기로 협상되므로, 진입 파일에서 바로 React를 불러오면 "Shared module is not available for eager consumption" 오류가 난다.

Webpack 밖의 선택지도 있다. Module Federation 2.0은 @module-federation/enhanced 패키지로 제공되며 Rspack에서도 쓸 수 있고, Vite용 플러그인도 있다. 지원 범위와 설정 키는 도구마다 다르므로 쓰려는 버전의 공식 문서로 확인한다.

공유 의존성과 디자인 시스템 버전 관리

singleton: true는 "이 라이브러리는 한 벌만 올린다"는 뜻이다. Remote가 요구한 범위(requiredVersion)와 실제로 올라간 버전이 맞지 않으면 기본은 콘솔 경고를 남기고 이미 올라간 버전을 쓴다. 경고를 놓치면 지원하지 않는 버전 위에서 Remote가 돌아간다. strictVersion: true를 주면 이때 오류를 던지므로 범위 위반을 바로 알 수 있다.

무엇을 정하나 누가 소유하나 없으면 생기는 일
공유 라이브러리 목록과 버전 범위 셸(플랫폼) 팀 React가 두 벌 올라가 Hook 호출 오류, 번들 중복
디자인 시스템 메이저 버전 디자인 시스템 팀 한 화면에 버튼·간격이 두 가지로 섞임
Remote가 노출하는 모듈과 props 각 Remote 팀 셸 배포 없이도 화면이 깨짐
Remote 주소와 배포 채널 셸 팀 + 각 팀 스테이징 셸이 운영 Remote를 불러옴

Remote가 노출하는 모듈은 백엔드 API와 같은 계약이다. props 이름 하나가 바뀌면 셸은 빌드 없이 실행 중에 깨진다. 이 계약을 테스트로 묶는 방법은 Contract Testing으로 Frontend·Backend API 호환성 검증과 같은 원리로 적용한다. 화면별 데이터 조립이 팀마다 흩어진다면 API Gateway와 BFF 도입 기준 및 보안 설계처럼 BFF를 Remote 팀이 함께 소유하는 구성이 깔끔하다.

운영에서 깨지는 지점

Remote 장애 격리
Remote 서버나 CDN이 응답하지 않으면 동적 import()가 실패한다. Remote마다 React.lazy + Suspense + Error Boundary로 감싸, 실패해도 그 영역만 대체 화면을 보이게 한다. 감싸지 않으면 Remote 하나의 장애가 페이지 전체를 하얗게 만든다.

첫 화면 성능
remoteEntry.js를 받은 뒤에야 Remote 모듈을 받으므로 요청이 한 단계 늘어난다. 첫 화면에 반드시 보이는 영역은 Remote로 빼지 않거나 미리 불러온다.

캐시와 버전 고정
remoteEntry.js를 긴 캐시로 두면 새 배포가 늦게 반영되고, 캐시를 끄면 매 방문마다 받는다. 진입 파일은 짧은 캐시, 해시가 붙은 청크는 긴 캐시로 나눈다.

SSR
서버 렌더링에서 Remote를 불러오려면 서버 쪽에서도 Remote를 실행할 수 있어야 해서 구성이 크게 복잡해진다. SSR이 필수인 페이지라면 런타임 통합 대신 페이지 단위 분리를 먼저 검토한다.

자주 하는 실수

  • 팀이 하나인데 Micro Frontend로 시작한다. 독립 배포로 얻을 것이 없어 통합·버전 관리 비용만 남는다.
  • shared 설정 없이 Remote를 붙인다. React가 두 벌 올라가 Hook 호출 오류가 나거나 번들이 불어난다.
  • Remote마다 Error Boundary를 두지 않는다. 한 팀의 배포 사고가 전체 화면 장애로 번진다.
  • 디자인 시스템을 각 팀이 복사해 쓴다. 몇 달 뒤 같은 버튼이 팀마다 다르게 생긴다.
  • Remote가 노출하는 props를 문서로만 관리한다. 바뀐 계약은 운영에서 실행될 때 처음 드러난다.

점검표

  • 배포 일정이 다른 프론트엔드 팀 수와 각 팀이 소유한 화면 영역을 적었다
  • 빌드 타임 통합이나 페이지 단위 분리로 충분하지 않은 이유를 한 문장으로 설명할 수 있다
  • 공유 라이브러리 목록과 버전 범위를 셸 팀이 소유하고 문서화했다
  • React 등 싱글턴 대상에 singleton: true와 requiredVersion이 Host·Remote 양쪽에 있다
  • Host 진입 파일이 import('./bootstrap')으로 비동기 경계를 둔다
  • Remote마다 Error Boundary와 대체 화면이 있고, Remote를 내려 실제로 확인했다
  • remoteEntry.js와 해시 청크의 캐시 정책을 따로 정했다
  • 스테이징 셸이 스테이징 Remote만 불러오도록 주소가 환경별로 분리돼 있다
  • Remote가 노출하는 모듈의 props 변경을 잡는 계약 테스트가 있다
  • 디자인 시스템 메이저 버전을 올리는 절차와 담당자가 정해져 있다

Micro Frontend는 팀이 따로 배포해야 하는 이유가 분명할 때만 비용보다 이득이 크고, Module Federation은 그 이유 위에 공유 의존성 합의와 장애 격리를 갖춘 뒤에야 안전하게 쓸 수 있다.

댓글 0개

아직 댓글이 없습니다. 첫 댓글을 남겨보세요.

댓글 쓰기

← 목록으로 돌아가기