Tech Dive

소상공인 혁신 플랫폼 ‘사장이지’ 프론트엔드 개발 #1: Nx Monorepo와 MicroFrontend 도입기

들어가며


안녕하세요. KT Experience Engineering 본부에서 프론트엔드 개발을 담당하고 있는 김남일입니다.

앞서 소개했다시피 “사장이지”는 슈퍼앱 형태의 대규모 프로젝트로 모바일 앱 상에서 웹 서비스가 함께 동작하는 구조로 운영되고 있습니다. (소상공인 혁신 플랫폼 ‘사장이지’ 출시 App편 )

사장이지 프론트엔드 같은 경우 WebView 환경을 통해 사용자에게 주요 인터페이스 제공되고 있습니다.

그래서 이번 글에서는 사장이지 웹 프론트엔드 개발 과정에서 겪었던 고민들과 경험들을 소개하며, 특히 플랫폼 프론트엔드 아키텍처를 어떻게 설계하고 발전시켜 왔는지를 중심으로 이야기하려 합니다.

다음과 같은 내용을 중심으로 공유드립니다.

  • 사장이지 웹 서비스의 시작
  • Nx Monorepo 도입
  • MicroFrontend 도입

00.jpg



사장이지 웹 서비스의 시작


사장이지 웹 서비스는 다양한 도메인을 하나의 플랫폼에서 Webview 형태로 동작하게 시작했습니다.

이때 WebView 환경에서 동작하기 때문에 앱으로부터 전달받는 다양한 데이터를 기반으로 동작합니다. 사용자 인증 토큰, 매장 정보, 디바이스 정보 등 앱에서 전달되는 데이터를 여러 서비스 도메인이 함께 활용해야해서 도메인 간 공통 상태와 데이터 흐름을 어떻게 관리할 것인지가 중요한 고려사항이었습니다.

그래서 단순히 기능을 구현하는 것뿐만 아니라 다음과 같은 문제를 해결할 수 있는 구조가 필요했습니다.

  • 서비스 확장에 대응할 수 있는 구조
  • 도메인 간 의존성을 최소화할 수 있는 구조
  • 앱에서 전달받는 공통 데이터를 안정적으로 공유할 수 있는 구조

즉, 서비스 확장성과 의존성 관리, 그리고 앱과 웹 사이의 데이터 흐름을 어떻게 관리할 것인지가 가장 중요했습니다.

02.jpg



Nx Monorepo 도입


왜 Monorepo였는가?

개발은 시작했지만 여러 도메인이 지속적으로 확장되고 변경되는 상황이었습니다. 또한 위에서 언급한 것 처럼 WebView 환경에서 동작하기 때문에 인증 토큰, 매장 정보, 디바이스 컨텍스트와 같은 공통 데이터와 공통 로직을 도메인 간에 함께 사용해야했습니다. 이러한 상황에서 저희 팀은 Multirepo와 Monorepo를 고민했습니다.

Multirepo vs Monorepo

비교 관점
Multirepo
Monorepo
코드 독립성
서비스 단위로 완전히 독립된 저장소 운영 가능
하나의 저장소에서 여러 서비스 관리
서비스 배포
서비스 단위 독립 배포에 유리
서비스 간 구조 공유에 유리
공통 코드 관리
공통 패키지 관리 필요
동일 저장소에서 공유 가능
의존성 파악
서비스 간 관계 파악이 어려울 수 있음
프로젝트 간 의존성 관리가 비교적 쉬움
팀 운영
서비스별 팀 운영에 적합
하나의 팀이 여러 도메인을 관리할 때 유리
구조 확장
서비스 분리에는 자연스러움
코드 구조 관리에 유리


Multirepo는 도메인마다 서비스를 독립적으로 운영하는 보편적인 구조입니다. 도메인들이 물리적으로 분리되기에 필요한 도메인만 롤백하는등 서비스 운영에 강점이 있습니다.


하지만 사장이지의 경우에는 도메인간 동일한 WebView 런타임 환경을 공유하고 있었고, 자체적인 공통 컴포넌트 시스템도 구축 예정이었습니다. 그래서 독립된 저장소를 사용하기보다 여러 도메인을 하나의 워크스페이스에서 관리하는 구조가 더 적합하다고 판단했습니다.

또한 도메인이 계속해서 추가될 예정이라 초기부터 레포지터리를 지나치게 분리해 인프라 복잡성을 키우는 것 보단 하나의 레포지터리 안에서 확장 가능한 코드 관리 구조를 먼저 확보하는 것이 중요했기에 Monorepo 구조를 선택했습니다.


Monorepo 도구 선택: Turborepo vs Nx

Monorepo를 선택한 이후, 운영하기 위한 도구로 Turborepo와 Nx를 검토했습니다.

Turborepo vs Nx

비교 기준
Turborepo
Nx
프로젝트 의존성 확인
별도 도구 또는 팀 규칙으로 관리
nx graph로 의존 관계 시각화 가능
구조 관리
구조 규칙은 팀 규칙에 의존
프로젝트/라이브러리 경계 관리 기능 제공
Monorepo 활용 방식
빌드 파이프라인 최적화 중심
앱 + 라이브러리 구조 관리 중심
변경 영향 범위 실행
태스크 기반 실행
의존성 그래프 기반 affected 실행

Nx와 Turborepo는 모두 Monorepo 환경을 구축할 수 있는 훌륭한 도구입니다. 하지만 두 도구는 워크스페이스를 관리하는 철학에서 차이가 존재합니다.

Turborepo는 각 애플리케이션이 독립적인 패키지 단위로 구성되는 구조를 기본으로 합니다. 각 애플리케이션은 자체 package.json을 가지며, root의 turbo.json을 통해 태스크 실행을 조율하는 방식입니다. 이 구조는 패키지 단위의 독립성을 높이고 다양한 기술 스택을 함께 운영하는 환경에서는 큰 장점이 될 수 있습니다.

하지만 위 장점들은 사장이지 프로젝트의 경우에는 다음과 같은 이유로 오히려 복잡성을 높일 수 있다고 판단했습니다.

  • 모든 애플리케이션이 Next.js + React 기반의 동일한 기술 스택을 사용
  • 여러 package.json으로 의존성을 분산 관리할 필요성이 크지 않음
  • 사내 보안 스캔 및 의존성 관리 정책을 고려할 때 패키지 관리 지점이 많아질수록 관리 비용이 증가

즉, 패키지 단위의 자유로운 관리보다는 의존성과 구조를 중앙에서 통제할 수 있는 형태가 더 적합하다고 판단했기에 Nx를 선택했습니다.
image (1).png


image (2).png



Nx 의존성 그래프를 활용한 아키텍쳐 변화기


Nx를 선택했던 또 다른 이유는 nx graph를 통한 아키텍쳐 관리였습니다.
현재는 Turborepo에서도 지원하지만, 당시에는 Nx에서만 제공되던 기능이었습니다.

아래는 저희 팀이 nx graph를 활용해 아키텍쳐 구조화를 했던 경험을 공유드리겠습니다.
Nx 모노레포는 기본적으로 apps와 libs로 이루어져있습니다.

  • apps: 실행 단위이며, 같은 Docker로 묶여 배포되는 단위
  • libs: 도메인 및 공통 로직을 담당하며, 규모에 따라 레이어를 분리

처음부터 모든 도메인을 분리해서 개발하지는 않고 Webview 환경과 인프라 리소스 제약을 고려해 microepbus라는 하나의 application으로 시작했습니다.

03.jpg



이후 서비스를 확장하면서 microepbus-e2e, microepbus-e2e-api와 같은 application을 추가했습니다. 하지만 이 시점부터 문제가 발생했습니다. 도메인이 확장되고 공통 모듈이 증가하면서 의존성이 복잡해졌고, 특정 모듈 사이간 순환 참조나 sibling 참조가 발생하기 시작했습니다.

04.jpg



문제점은 microebpus에서 GA 호출을 위해 core-ga 모듈을 사용했지만, core-ga에서도 비지니스 로직(Flutter 연동 모듈)을 호출하는 경우가 있어 발생했습니다. 그렇기에 모든 비즈니스 로직들은 libs의 ./domains라는 신규 폴더로 그룹화 했으며 공통 모듈들은 ./shared로 계층화했습니다.

05.jpg

./domains와 ./shared로 계층화된 이미지


06.jpg

상세 내부 구조



현재 사장이지 웹에는 총 10개의 도메인이 있습니다. 도메인이 많다보니 신규 개발 진행 시 새로운 도메인을 수정 후 배포할때마다 기존 서비스에 영향이 가는 문제가 발생했습니다.

특히 사장이지 feed 도메인의 경우 상품을 소개하는 페이지라 이미지 교체가 잦았습니다. 이미지 교체는 단순한 작업이지만, 모두 같은 application에 속해있다보니 개발중인 로직이 흘러들어가 안정적으로 운용되는 도메인에 문제를 일으키는 경우가 발생했습니다.

처음에는 모든 도메인이 독립성을 가지는게 좋다고 판단해 따로 배포할 예정이었습니다. 하지만 현재 상황에서 모든 모듈을 따로 배포하는건 관리할 지점도 많아지고, 리소스 낭비라 생각해 오버스택으로 판단했습니다.

그래서 저희는 비슷한 성격의 도메인 그룹끼리 묶었습니다.

  • web-flexible-domains: feed 페이지처럼 자주 변경되는 도메인들의 집합
  • web-stable-domains: 이미 개발이 완료되고 안정적인 도메인들의 집합
  • microepbus: 전체 도메인들의 집합으로 주로 롤백용으로 사용
  • new-domain: 필요 시, 외부 인력들이 투입돼 개발할 수 있는 신규 도메인 (*필요에 따라 유동적으로 추가/삭제 가능)


위 4개의 application들은 각기 다른 모듈에 배포됩니다.
도메인의 성격은 계속 바뀌기때문에 필요에 따라 flexible ↔ stable간 자유롭게 옮길 수 있습니다.

web-flexible-domains / web-stable-domains / microepbus / new-domain 은 각각 다른 앱·이미지로 배포되고 로직들은 존재하지 않는 app으로 규정지었습니다. 

이러한 구조를 통해 로직들은 특정 앱에 묶이지 않고 보다 유연한 관리가 가능해졌습니다.

07.jpg



  현재는 다음과 같은 구조로 운영되고 있습니다.

구분
계층
설명
apps
web-stable-domains
Multi-zone(main) 안정적으로 운영중인 서비스 라우트

web-flexible-domains
Multi-zone(sub) 변경이 많거나 개발중인 서비스 라우트

microepbus
Single-zone 으로 모든 서비스 라우트

new-domain
새로운 도메인 추가에 따라 유동적으로 존재하는 서비스 라우트

mock-server
로컬 테스트시 사용하는 Mock API 서버

storybook
UI Component, Chart, Calendar Storybook

sajangeasy-e2e
Playwright E2E 테스트 코드
libs
domains/*
도메인별 비즈니스 로직을 구현 (AI 전화, AI 이미지 등)

entities/*
domains에서 공통으로 사용하는 요소 (공통 컴포넌트, API Client 등)

shared/*
entities에서 공통으로 사용하는 요소 (Flutter 통신, Util, GA 등)

물론 이 구조가 정답이라 생각하지 않고 서비스 방향과 요구사항에 따라 프론트엔드 아키텍처는 계속해서 변화하고 있습니다.


MicroFrontend 도입


지금까지 Nx monorepo 활용해 어떻게 아키텍쳐를 다뤄왔는지 설명드렸습니다.

다만, 위 구조로 서비스를 운영하기 위해선 각 app(microepbus, web-flexible-domains, web-stable-domains)마다 모듈을 연결해 빌드/배포를 진행해야 합니다.

이를 위해 저희팀은 MicroFrontend를 도입했습니다.

MicroFrontend란 하나의 큰 프론트엔드를 기능/서비스 단위의 여러 모듈로 나누고, 이를 조합해서 하나의 서비스처럼 동작하게 만드는 아키텍처입니다. 즉, 고객 입장에선 하나의 서비스처럼! 개발 편의를 위해 개발은 분리해서!


장점: 독립성 증가

  • 서비스 단위로 독립 개발/배포가 가능해 요청에 따라 별도 배포가 가능합니다.
  • 변경 범위가 모듈로 제한되어 서비스간 영향도를 줄이고 장애 격리와 롤백이 쉬워집니다.
  • 공통 기능을 라이브러리로 분리해 다른 서비스를 개발할 때에도 사용할 수 있어 재사용성이 높아지고, 도메인 확장성이 좋아집니다.

단점: 관리 포인트 증가

  • 모듈을 나눌수록 빌드/배포/운영 대상이 늘어 관리가 복잡해질 수 있습니다.
  • 모듈 간 데이터에 따른 상태관리가 복잡해질 수 있습니다.


MicroFrontend 도구 선택: Moudule Federation vs Multi zone

MicroFrontend 아키텍처를 도입하는 과정에서 Module Federation과 Next.js Multi Zones를 함께 검토했습니다.

두 방식 모두 독립적인 배포와 병렬 개발을 가능하게 한다는 공통점이 있지만,
애플리케이션을 나누는 기준과 운영 방식에서 차이가 존재했습니다.


Moudule Federation vs Multizone

비교 기준
Module Federation (Webpack 5+)
Multi Zones (Next.js)
분리 단위
기능/컴포넌트 단위
앱(pod)/페이지 단위
통합 방식
런타임 모듈 로딩
경로 기반 통합
렌더링
CSR 중심
SSR + CSR
확장 방식
번들 단위 확장
Next 앱 단위 확장

선택 과정에서의 주요 관점

두 기술의 차이를 검토하면서, 단순한 기술 비교보다는 사장이지 서비스의 구조와 조직 방식에 더 적합한 방향이 무엇인지를 중심으로 판단했습니다.

1. 기능 단위 vs 도메인 단위


Module Federation은 기능 단위로 애플리케이션을 잘게 나누고,
각 기능을 독립적인 번들로 관리하는 데 강점이 있습니다.

하지만 사장이지의 경우 하나의 도메인이 비교적 큰 기능 단위로 이루어졌기 때문에,
이를 다시 세분화하여 관리하는 방식은 오히려 복잡도를 높일 수 있다고 판단했습니다.

반면 Multi Zones는 Next.js 애플리케이션(pod) 단위로 분리되기 때문에,
현재 서비스 구조처럼 도메인 단위로 책임이 나누는 형태와 일치했습니다.

08.jpg

세부 기능별 bundle.js을 호출하는 Module Federation



이미지경로에 따라 pod를 이동시켜버릴 수 있는 Muti zone


2. 확장 방식과 운영 구조


Module Federation은 기능 단위로 세밀하게 분리하여, 여러 팀이 동시에 개발할 수 있는 구조에 적합합니다.
Multi Zones는 Next.js 애플리케이션 단위로 확장되기 때문에, 서비스를 pod 단위로 확장하고 운영하는 구조에 더 적합했습니다. 
특히 기존에 사용하던 Next.js 기반 구조를 유지하면서 확장할 수 있다는 점도 중요한 고려 요소였습니다.

3. SSR / SEO 관점


Next.js Multi Zones는 기본적으로 SSR과 SEO 최적화를 그대로 활용할 수 있다는 장점이 있습니다.
다만 사장이지 서비스의 경우, 검색 유입 중심의 서비스가 아니기 때문에 SEO의 중요도가 높은 편은 아니었습니다.
그럼에도 불구하고 SSR 기반 구조는 초기 로딩 성능이나 일부 퍼블릭 페이지에서 활용될 가능성을 고려했을 때
추후 확장 여지를 열어두는 선택지로 판단했습니다.

실제로도 현재 사장이지에서는 webview 외에도 직원 초대, 신규 회원 가입을 위한 Deffered Deeplink 페이지는 web으로 제공하고있습니다.

4. 결론

Module Federation과 Next.js Multi Zones는 각각 다른 구조와 장점을 가진 유효한 선택지였습니다.
사장이지에서는 아래 요소를 고려해 Multi Zone를 선택했습니다.

  • 도메인 단위로 구성된 서비스 구조
  • Next.js 기반의 기존 스택
  • pod 단위 확장 및 운영 방식

사장이지는 MicroFrontend 를 어떻게 구성하였을까?


MultiZone 기반 독립 실행


  • 서비스 단위로 실행 영역(Zone)을 분리하고, 각 도메인을 별도 Pod로 띄운 뒤 서비스하도록 구성했습니다.

  • 외부 진입점은 단일화하여 라우팅/인증/정책을 중앙에서 처리하고, 내부적으로는 각 서비스가 독립적으로 배포 및 호출될 수 있도록 했습니다.
  • 하나의 진입점을 쓰기에 Store 공유가 가능하여 기존 앱의 상태관리 로직을 사용 할 수 있습니다.

next.config.js
JSON
rewrites: async () => {
    return [
      {
        source: '/domain-1/:path*',
        destination: '${process.env.pod-address-1}/:path*',
      },
      {
        source: '/domain-2/:path*',
        destination: '${process.env.pod-address-2}/:path*',
      },
      {
        source: '/domain-3/:path*',
        destination: '${process.env.pod-address-3}/:path*',
      },
    ];
  },​
12345678910111213141516

즉, 아래 그림처럼 사용자는 :3000/web이라는 하나의 서비스로 접속하지만 실제로는 MSA처럼 여러 Pod에서 동작하는 구조를 띄게됩니다.

09.jpg




Multi Zones 구성 시 추가 고려 사항: GitHub Actions Workflow

Multi Zones로 애플리케이션을 분리하면서,

애플리케이션 구조뿐만 아니라 CI/CD 파이프라인 구조도 함께 재설계할 필요가 있었습니다.

기존에는 단일 pod(microepbus) 구조였기 때문에 하나의 파이프라인에서 빌드와 배포를 처리하면 충분했습니다.
하지만 Multi Zones로 전환하면서 여러 pod를 독립적으로 배포해야 하는 상황이 되었고, 다음과 같은 고민이 발생했습니다.

1. 도메인별 Workflow 분리

각 도메인(pod)마다 GitHub Actions workflow를 별도로 구성하는 방식입니다.
구조는 직관적이지만, 서비스가 확장될수록 관리 비용이 증가하는 방식

장점
  • 도메인별 완전한 독립 실행 가능
  • 서로 다른 배포가 영향을 주지 않음
  • 파이프라인 단위로 책임이 명확함

단점
  • workflow 파일 수 증가로 관리 포인트 확대
  • 공통 로직 변경 시 모든 workflow 수정 필요
  • 중복 설정으로 인한 유지보수 비용 증가

2. 단일 Workflow + 옵션 기반

하나의 workflow에서 input 옵션을 통해 실행 대상을 선택하는 방식입니다.
JSON
​on:
  workflow_dispatch:
    inputs:
      deploy_stable:
        type: boolean
      deploy_flexible:
        type: boolean
1234567
장점
  • 하나의 workflow로 통합 관리 가능
  • 공통 로직 재사용으로 유지보수 효율 증가
  • 실행 시 필요한 도메인만 선택적으로 배포 가능

단점
  • 실행 제어를 잘못 설계할 경우 작업 간 간섭 발생
  • 병렬 실행 및 상태 관리에 대한 추가 고려 필요

도메인별 Workflow 분리하는 방안은 여러 도메인을 배포할 때 관리가 안될 것 같아 2. 단일 Workflow + 옵션 기반을 택했습니다.

하지만 적용해보니 Concurrency 이슈로 기존 작업이 중단되는 문제가 발생했습니다. cancel-in-progress 옵션은 있었지만, 큐에 쌓여 기존 action이 끝나야만 다음 action이 동작했기에 원하는 방향이 아니었습니다.

그렇기에 Job 단위를 분리하는 방법을 택했습니다. 도메인을 job 단위로 분리하고, input 값에 따라 실행 여부를 제어하도록 구성했습니다. 이를 통해 하나의 workflow 내에서도 여러 도메인을 동시에 선택하여 병렬로 실행할 수 있게 되었습니다.

JSON
build_stable:
  if: ${{ inputs.deploy_stable }}

build_flexible:
  if: ${{ inputs.deploy_flexible }}​
12345

배포 단계에서는 선택된 도메인만 반영되도록 구성하여, 하나의 배포 흐름 안에서도 여러 도메인을 안전하게 처리할 수 있도록 했습니다. 결과적으로 stable, flexible과 같은 각 도메인을 독립적으로 또는 동시에 배포할 수 있는 구조를 갖추게 되었습니다

하나의 workflow만 있던 버전

10.jpg

도메인마다 파이프라인이 존재해 개별적으로 실행 필요


이미지
하나의 workflow만 동작

Multi zone workflow 버전


11.jpg

하나의 파이프라인에서 여러 도메인 병렬적으로 배포 가능


이미지
stable만 선택해 단일 동작

이미지
flexible, stable 병렬 동작


이러한 구조를 통해 하나의 workflow로 전체 파이프라인을 관리하면서도, 도메인 단위의 독립성과 병렬 실행을 모두 확보할 수 있었습니다.


마무리


처음부터 완벽한 구조를 만드는 것은 불가능하며, 결국 점진적으로 개선해 나갈 수밖에 없다고 느꼈습니다. 특히나 Nx Monorepo 위에서 WebView, Flutter Web 애뮬레이터 등 다양한 클라이언트를 함께 다루다 보니 상황에 맞게 계속 적응해 나가는 과정이 필요했습니다.

위 과정들을 통해 저희가 얻은 교훈은 문제가 발생했을 때 이를 인지하는 것과 기존 아키텍쳐를 고치는 행위를 매몰비용이라 생각하지 않고 개선해나가는 자세라 생각합니다.

이상 Nx monorepo와 MicroFrontend 도입기를 마치겠습니다. 긴글 읽어주셔 감사드리며 다음 2편은 소상공인 혁신 플랫폼 ‘사장이지’ 프론트엔드 개발 #2: FSD로 찾아뵙도록 하겠습니다!


🔥 공동 기여자들: P-Web개발부서 (유현우, 백지경, 박지원, 김수현) 

김남일

KT에서 프론트엔드 개발자로 근무하고 있으며, 회고와 개선을 통해 더 나은 방향을 찾아가고 있습니다.

이전 글