모노레포에서 독립적인 미니 프로젝트 관리하기: Shadow DOM에서 iframe까지

모노레포에서 독립적인 미니 프로젝트 관리하기: Shadow DOM에서 iframe까지

2026년 1월 24일

Vanilla CSS, React, Interaction 프레임워크 등 제각각인 미니 프로젝트들을 어떻게 하면 ‘독립적’이면서도 ‘통합적’으로 관리할 수 있을까요?

처음에는 단순히 모노레포 구조를 잡고 Shadow DOM으로 스타일을 격리하려 했지만, 실제 구현 과정에서 설계의 미숙함을 발견했습니다. 본 글에서는 Turborepo의 올바른 역할 분리, Shadow DOM 대신 iframe을 선택한 논리적 근거, 그리고 빌드타임 Registry 생성을 통한 자동화까지, 지속 가능한 실험실(Interaction Lab)을 구축하기 위해 고민했던 기술적 의사결정 과정을 공유합니다.

문제 상황

CSS 애니메이션, 마이크로 인터랙션 등을 실험한 미니 웹 프로젝트들을 어떻게 관리하고 전시할지 고민이 있었다. 가장 큰 목표는 각 웹을 독립적으로 관리하고 싶다는 것이었다. motion 혹은 gsap과 같은 인터랙션 프레임워크를 사용해서 인터랙션을 구현하기 이전에, vanilla css만을 가지고 연습하고 싶었다. 따라서 html로 된 코드와 리액트 코드로 구현된 웹이 혼재되어 있으며 이 웹들이 각기 독립적으로 존재하기를 원했다.

내가 처음 한 생각

매번 웹들을 만들 때마다 별도의 레포지토리를 생성하기에는 부담스러웠다. 그래서 하나의 레포지토리에서 이 모든 웹들을 관리하기를 원했다.

그래서 모노레포를 구축하여, 각 UX 인터랙션 웹들을 packages로 두고 이를 스크립트를 통해 registy를 생성하여, apps 내의 gallery에 등록하여 구경할 수 있게 구현하려고 했다. 이때 gallery에서 각 UX 인터랙션 웹들을 직접 체험하기를 원해서 shadow DOM을 주입하기로 생각했다.

실제로 해 본 선택

당시 구현에 집중하였기에, 위 방식을 채택하여 바로 구현에 들어갔다.

결과 (성공 / 실패)

그런데 모노레포에 대한 기본적인 이해가 부족했다. 먼저 apps와 packages의 역할인데, apps의 경우 application 그리고 packages는 libraries 역할을 수행한다. 따라서 각 UX 인터랙션 웹을 packages에 둔다는 내 생각은 이 레포의 목적과 turborepo가 전제하는 역할 분리가 맞지 않았다고 보았다.

또한 갤러리에서 직접 웹들을 사용할 수 있게 하기 위해 shadow DOM을 채택했는데 (해당 웹의 html, css, js를 gallery 코드에 영향을 주지 않고 그대로 주입시키기 위해), 이 역시 iframe으로 대체할 수 있음을 알게 되었다.

개선한 구조

구현 이후, 기존 코드를 남겨둔 채 새로운 코드는 apps에 추가하면서 우선 이 상태를 유지했다.

그러다가 이번에 리팩토링을 했는데, 우선 목적을 재정의하였다. 이 레포는 monorepo지만, 통합 앱이 아니라 독립 앱들의 컬렉션이기를 원했다. 따라서 기존의 apps와 packages에 혼재되어 있던 웹들을 모두 apps로 이동시켰다. 또한 두 가지 스크립트를 추가했는데, 하나는 registry 생성기로 파일 시스템 기준으로 스크립트가 registry를 생성하게 했다. 그리고 vite 앱 빌더를 두어, dev에서는 분산 실행을 하고, prod에서는 정적 결과를 iframe을 통해 소비하게 하였다.

FAQ

  • iframe?
    • 책임 분리 선택 하에 전시 대상이 host 앱의 상태·스타일·라이프사이클에 영향을 받지 않도록 하기 위해 iframe을 채택함
    • iframe은 완전 격리
    • shadowDOM은 JS는 공유
  • 모노레포 ?
    • 함께 진화한다면 monorepo, 완전히 독립되면 분리 레포가 적합함.
    • 하지만 추가/삭제/전시 방식은 공통구현은 제각각이기 때문에 관리 비용을 줄이기 위해 모노레포 채택
  • 앞으로 인터랙션이 3개 → 30개 → 100개 될 때, iframe / shadow DOM 중 누가 관리 비용이 낮은가?
    • shadow DOM은 실패가 host로 전파됨
    • 실패가 해당 iframe 내부에 격리됨
  • 이 구조에서 registry를 런타임에 안 만들고 빌드 타임에 만드는 이유
    • 앱 목록은 배포 전 결정되고, 메타데이터는 역시 파일 기준으로 생성되기에 굳이 런타임에서 만들 위험을 고수할 필요가 없음.
  • 왜 Next.js dynamic import가 아니라 iframe이었는지
    • React 트리 안에 넣고 싶다면 dynamic import가 적합하겠지만 UX 인터랙션 웹이 React일 수도 아닐 수도 CSS-only일 수도 있기 때문에 중립적인 iframe을 채택함.

실제 변경 내역

  • 인터랙션 웹들을 packagesapps로 이동 (13cbcd6, 5297811)
  • 갤러리 카드를 shadow DOM에서 iframe으로 변경 (c86a433)
  • 빌드타임 registry 생성 스크립트 추가 (0c3a04d)