콘텐츠로 이동

포탈 통합 셸을 먼저 못 만든 것

처음엔 모듈마다 독립 앱으로 쪼개는 “마이크로 프론트엔드”가 멋져 보였습니다. 큰 회사들이 쓴다는 패턴이었으니까요. 그래서 11개 업무 모듈을 11개의 독립 앱으로 나누는 인프라부터 깔았습니다. 화면 한 장 만들기도 전에요. 하지만 1인 개발 + 한 대 서버라는 현실에서, 그건 멋이 아니라 과설계였습니다. 결국 모든 모듈을 단일 포탈 하나로 다시 합쳤습니다.

프로젝트 초기, 대기업 사례로 자주 언급되는 MFE 패턴에 끌렸습니다. 모듈을 독립적으로 배포하고, 모듈마다 별도 포트와 경로 접두사(예: /m/<모듈>)를 두고, 필요하면 따로 떼어 굴린다 — 청사진은 그럴듯했습니다. 그래서 업무 모듈들을 각각 독립 앱으로 분리하고 모듈마다 별도 포트와 경로 접두사를 박는 작업을 했습니다. 화면을 채우기도 전에 “쪼개는” 인프라부터 깐 것입니다.

그런데 깔고 나니 곧바로 마찰이 시작됐습니다. 불과 이틀 뒤에는 URL에서 모듈 접두사(/m/)를 제거해 경로를 단순화하는 작업을 했습니다. 쪼개기 위해 붙였던 구조가 벌써 거추장스러워지기 시작한 것입니다. 그리고 약 한 달 뒤, 결국 분리해 둔 route group을 평탄화(flatten)해 단일 구조로 합치는 큰 정리를 했습니다.

시점한 일방향
2026-04-2411개 업무 모듈을 독립 앱으로 분리 (MFE 구축)쪼개기 →
2026-04-26URL에서 모듈 접두사 /m/ 제거, 경로 단순화← 되돌리기 시작
2026-05-22route group 평탄화 — 분리 wrapper 제거, 단일 구조로 합침← 단일 포탈로 회귀

표를 보면 흐름이 분명합니다. 한 달 남짓 동안 “쪼개기 → 단순화 → 통합”으로 방향이 한 바퀴 돌았습니다. 쪼개는 데 쓴 시간과 다시 합치는 데 쓴 시간이 둘 다 들었고, 그 사이 새 가치는 거의 늘지 않았습니다.

핵심은 패턴 자체가 나쁘다는 게 아닙니다. MFE는 좋은 패턴입니다 — 그 전제가 맞는 곳에서는요. MFE가 빛나는 전제는 “여러 팀이 서로 다른 속도로, 서로 독립적으로 배포해야 하는” 상황입니다. 그런데 이 프로젝트의 전제는 정반대였습니다. 개발자는 한 명이고, 배포가 돌아가는 곳은 개발 서버 한 대였습니다. 독립 배포할 팀이 없는데 독립 배포 인프라를 깐 것입니다.

독립 앱이 여러 개가 되면 거의 모든 것이 곱절이 됩니다. 그런데 이 비용은 한눈에 안 보입니다. 사용자에게 보이는 화면은 그대로인데, 뒤에서 유지비만 조용히 불어나기 때문입니다. 그래서 “보이지 않는 세금”이라 부를 만합니다.

그때 — 독립 앱 N개 모듈1 앱 모듈2 앱 모듈3 앱 모듈N 앱 빌드 N벌 · 인증 N벌 · 토큰 N벌 앱 경계 넘는 화면 이동이 깨지기 쉬움 지금 — 단일 포탈 셸 포탈 (네비·인증·레이아웃·토큰) (어드민) (전자결재) 모듈 그룹 모듈 그룹 route group = 폴더로 경계만 유지 빌드 1벌 · 인증 1벌 · 토큰 1벌
왼쪽(그때): 모듈마다 독립 앱이라 빌드·인증·디자인 토큰이 앱 수만큼 곱해지고, 앱 경계를 넘는 화면 이동에서 깨지는 지점이 생깁니다. 오른쪽(지금): 단일 포탈 셸 하나에 모듈을 route group(폴더)으로 담아 경계는 유지하되 인프라는 전부 공유합니다.

구체적으로 어떤 것들이 곱절이 됐는지 봅시다.

  • 빌드 — 앱이 N개면 빌드도 N번. 한 줄 고쳐도 여러 앱을 다시 빌드해야 합니다.
  • 공유 인프라 — 인증, 공통 컴포넌트, 디자인 토큰을 앱마다 똑같이 심고 동기화해야 합니다. (이게 어긋나면 화면마다 다른 파란색 같은 사고로 이어집니다.)
  • 화면 이동 — 모듈에서 모듈로 넘어갈 때 앱 경계를 넘게 되는데, 그 경계에서 깨지기 쉬운 지점이 생깁니다.
  • 인지 부담 — 1인 개발자가 머릿속에 “지금 어느 앱을 만지고 있지?”를 항상 들고 있어야 합니다.
비용 항목독립 앱 N개단일 포탈
빌드 횟수앱 수만큼 (N번)1번
인증·토큰·공통 컴포넌트앱마다 심고 동기화한 벌을 전 모듈이 공유
모듈 간 화면 이동앱 경계를 넘어 깨지기 쉬움같은 앱 안 이동 — 경계 없음
얻는 이득(독립 배포)팀이 없어 못 누림(애초에 불필요)

단일 포탈로의 회귀 — 그리고 구조는 남긴다

섹션 제목: “단일 포탈로의 회귀 — 그리고 구조는 남긴다”

결국 방향을 틀었습니다. 단일 Next.js 앱 하나에 모든 모듈 화면을 route group으로 담는 구조로 회귀했습니다. 전자결재도 별도 앱이 아니라 포탈 안의 한 route group(예: (sign))이 됐고, 어드민과 업무 모듈도 각각의 그룹(예: (portal))으로 정리됐습니다. 모듈 경계는 폴더로 유지하되, 빌드·배포·인증·패키지는 전부 하나로 공유합니다.

처음에 깔았어야 할 건 “담는 셸”이었다

섹션 제목: “처음에 깔았어야 할 건 “담는 셸”이었다”

되돌아보면 첫날 우선순위가 뒤집혀 있었습니다. 가장 먼저 만들었어야 할 건 모듈을 쪼개는 인프라가 아니라, 모듈을 담는 통합 셸이었습니다. 순서를 바로 세우면 이렇습니다.

  1. 셸을 세운다. 네비게이션·인증·공통 레이아웃·디자인 토큰을 가진 단일 셸 하나를 먼저 만듭니다.
  2. 모듈을 폴더로 담는다. 새 모듈은 셸 안의 route group(폴더)으로 추가합니다. 경계는 폴더로 유지하되 인프라는 공유합니다.
  3. 분리는 보류한다. 독립 앱으로 떼어내는 일은 하지 않습니다. 대신 미래에 떼어낼 수 있도록 구조의 자리만 남겨 둡니다.
  4. 정말 아플 때 떼어낸다. 특정 모듈의 트래픽이 실제로 커졌을 때, 그때 그 모듈만 독립 영역으로 분리합니다.

네비게이션·인증·공통 레이아웃·디자인 토큰을 가진 셸을 하나 세우고 그 안에서 모듈을 한 장씩 늘려 갔다면, MFE를 깔았다가 걷어내는 한 달짜리 왕복은 애초에 없었을 것입니다.

분리는 언제 해도 늦지 않습니다. 아플 만큼 커진 다음에 떼어내면 됩니다. 반대로 처음부터 분리하면, 분리가 주는 이득(독립 배포)은 하나도 못 누리면서 분리의 비용(곱절의 유지비)만 먼저 냅니다. “나중에 합치면 되지”보다 “나중에 쪼개면 되지”가 거의 항상 더 쌉니다 — 합치는 것보다 쪼개는 게 늘 쉽기 때문입니다.


이 글은 SL.AIMS를 만들며 겪은 현장 회고 중 하나입니다. 전체 그림은 〈사례연구: SL.AIMS〉에, 관련 글은 〈디자인 시스템을 통일하지 않고 시작한 대가〉에 있습니다.