architecture2025-06-10·14 min·36/348

Next.js에서 마이크로 프론트엔드 구현하기

Next.js를 활용하여 마이크로 프론트엔드 아키텍처를 구현하고 독립적인 팀 간 협업을 가능하게 하는 방법을 알아봅니다.

Next.js에서 마이크로 프론트엔드 구현하기

Introduction

마이크로 프론트엔드는 모놀리식 프론트엔드를 독립적으로 배포 가능한 작은 애플리케이션으로 분리하는 아키텍처 패턴입니다. Next.js와 Module Federation을 활용하면 여러 팀이 독립적으로 개발하고 배포할 수 있습니다. 이 글에서는 마이크로 프론트엔드 구현 방법을 다룹니다.

Environment

# 프로젝트 구조
micro-frontends/
├── shell/                    # 메인 앱 (호스트)
│   ├── app/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── next.config.js
│   └── package.json
├── remote-dashboard/         # 대시보드 앱 (리모트)
│   ├── app/
│   │   └── page.tsx
│   ├── next.config.js
│   └── package.json
├── remote-store/             # 스토어 앱 (리모트)
│   ├── app/
│   │   └── page.tsx
│   ├── next.config.js
│   └── package.json
└── packages/
    └── shared/               # 공유 컴포넌트
        ├── components/
        └── package.json

# 기술 스택
Next.js: 14.2.5
Module Federation: 2.0
TypeScript: 5.3

Problem

모놀리식 아키텍처의 문제점:

  • 모든 팀이 같은 리포지토리에서 작업
  • 배포 의존성으로 인한 병목
  • 기술 스택 통일 강제

Analysis

마이크로 프론트엔드 장점:

  1. 독립적 배포: 각 팀이 독립적으로 배포
  2. 기술 스택 다양성: 팀별로 다른 기술 사용 가능
  3. 점진적 마이그레이션: 기존 앱 점진적 전환 가능
  4. 장애 격리: 한 컴포넌트 장애가 전체에 영향 안 함

Solution

1. Module Federation 설정

// shell/next.config.js
const { NextFederationPlugin } = require('@module-federation/nextjs-mf');

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack(config, options) {
    config.plugins.push(
      new NextFederationPlugin({
        name: 'shell',
        filename: 'static/chunks/remoteEntry.js',
        remotes: {
          dashboard: `dashboard@http://localhost:3001/_next/static/chunks/remoteEntry.js`,
          store: `store@http://localhost:3002/_next/static/chunks/remoteEntry.js`,
        },
        shared: {
          react: { singleton: true },
          'react-dom': { singleton: true },
        },
      })
    );
    
    return config;
  },
};

module.exports = nextConfig;

2. 리모트 앱 설정

// remote-dashboard/next.config.js
const { NextFederationPlugin } = require('@module-federation/nextjs-mf');

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack(config, options) {
    config.plugins.push(
      new NextFederationPlugin({
        name: 'dashboard',
        filename: 'static/chunks/remoteEntry.js',
        exposes: {
          './Dashboard': './app/page.tsx',
          './Chart': './components/Chart.tsx',
        },
        shared: {
          react: { singleton: true },
          'react-dom': { singleton: true },
        },
      })
    );
    
    return config;
  },
};

module.exports = nextConfig;

3. 동적 컴포넌트 로드

// shell/app/components/MicroFrontend.tsx
'use client';

import { lazy, Suspense, useState, useEffect } from 'react';

interface MicroFrontendProps {
  name: string;
  component: string;
  fallback?: React.ReactNode;
  props?: Record;
}

export function MicroFrontend({ 
  name, 
  component, 
  fallback = 
로딩 중...
, props = {} }: MicroFrontendProps) { const [Component, setComponent] = useState(null); const [error, setError] = useState(null); useEffect(() => { const loadComponent = async () => { try { // @ts-ignore const module = await import(`${name}/${component}`); setComponent(() => module.default); } catch (err) { console.error(`Failed to load ${name}/${component}:`, err); setError(err as Error); } }; loadComponent(); }, [name, component]); if (error) { return (

컴포넌트를 불러올 수 없습니다.

); } if (!Component) { return <>{fallback}; } return ; }

4. 레이아웃에서 마이크로 프론트엔드 사용

// shell/app/layout.tsx
import { MicroFrontend } from './components/MicroFrontend';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    
      
        
{children}
); }

5. 공유 상태 관리

// shell/lib/shared-state.ts
'use client';

import { createContext, useContext, useState, ReactNode } from 'react';

interface SharedState {
  user: {
    id: string;
    name: string;
    email: string;
  } | null;
  theme: 'light' | 'dark';
  language: 'ko' | 'en';
}

const SharedStateContext = createContext<{
  state: SharedState;
  setState: (state: Partial) => void;
} | undefined>(undefined);

export function SharedStateProvider({ children }: { children: ReactNode }) {
  const [state, setState] = useState({
    user: null,
    theme: 'light',
    language: 'ko',
  });
  
  const updateState = (partial: Partial) => {
    setState(prev => ({ ...prev, ...partial }));
  };
  
  return (
    
      {children}
    
  );
}

export function useSharedState() {
  const context = useContext(SharedStateContext);
  if (!context) {
    throw new Error('useSharedState must be used within SharedStateProvider');
  }
  return context;
}

6. 라우팅 통합

// shell/app/page.tsx
'use client';

import { MicroFrontend } from './components/MicroFrontend';
import { usePathname } from 'next/navigation';

export default function HomePage() {
  const pathname = usePathname();
  
  // 경로에 따라 적절한 마이크로 프론트엔드 렌더링
  const renderContent = () => {
    if (pathname.startsWith('/dashboard')) {
      return (
        
      );
    }
    
    if (pathname.startsWith('/store')) {
      return (
        
      );
    }
    
    return (
      

홈페이지

셀 애플리케이션

); }; return renderContent(); }

7. 독립적 배포 설정

# .github/workflows/shell.yml
name: Deploy Shell

on:
  push:
    branches: [main]
    paths:
      - 'shell/**'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Deploy Shell
        run: |
          cd shell
          npm install
          npm run build
          # 배포 스크립트
# .github/workflows/dashboard.yml
name: Deploy Dashboard

on:
  push:
    branches: [main]
    paths:
      - 'remote-dashboard/**'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Deploy Dashboard
        run: |
          cd remote-dashboard
          npm install
          npm run build
          # 독립적 배포

Lessons Learned

  1. 공유 라이브러리 관리: React 등 핵심 라이브러리는 singleton으로 공유
  2. 에러 격리: 마이크로 프론트엔드 간 에러 전파 방지
  3. 성능 모니터링: 각 컴포넌트의 로딩 시간 개별 추적
  4. 점진적 도입: 기존 앱에 점진적으로 마이크로 프론트엔드 적용

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.