하나의 도메인에 여러 Cloudflare Workers를 경로별로 매핑해 팀이 서로 독립적으로 개발·배포하면서도 사용자에게는 하나의 매끄러운 애플리케이션처럼 보이게 만드는 Vertical Microfrontends(VMFE) Worker 템플릿을 소개합니다.
URL: https://blog.cloudflare.com/vertical-microfrontends/
Title: Cloudflare 플랫폼에서 버티컬 마이크로프런트엔드 구축하기
2026-01-30
9 min read

오전 6:55 PT에 업데이트됨
오늘 우리는 버티컬 마이크로프런트엔드(Vertical Microfrontends, VMFE)를 위한 새로운 Worker 템플릿을 소개합니다. 이 템플릿을 사용하면 여러 개의 독립적인 Cloudflare Workers를 하나의 도메인에 매핑할 수 있어, 팀들이 완전히 분리된 사일로(silo)로 작업하면서도(마케팅, 문서, 대시보드를 각각 독립적으로 배포) 사용자에게는 하나의 매끄러운 애플리케이션으로 보이게 할 수 있습니다.
대부분의 마이크로프런트엔드 아키텍처는 “수평(horizontal)”입니다. 즉, 하나의 페이지에서 서로 다른 부분 을 여러 서비스에서 가져옵니다. 버티컬 마이크로프런트엔드는 URL 경로(path) 기준으로 애플리케이션을 분할한다는 점에서 다른 접근을 취합니다. 이 모델에서 /blog 경로를 소유한 팀은 단지 컴포넌트만 소유하는 것이 아니라, 그 라우트의 전체 버티컬 스택(프레임워크, 라이브러리 선택, CI/CD 등)을 소유합니다. 하나의 경로(또는 경로 집합)의 전체 스택을 소유하면, 팀은 작업에 대한 진정한 소유권을 갖고 확신을 가지고 배포할 수 있습니다.
팀이 성장함에 따라, 서로 다른 프레임워크가 각기 다른 사용 사례에 더 적합해지는 문제가 생깁니다. 예를 들어 마케팅 웹사이트는 Astro가 더 잘 맞을 수 있고, 대시보드는 React가 더 나을 수 있습니다. 또는 여러 팀이 하나의 모놀리식 코드베이스에서 함께 배포하는 상황을 생각해 보세요. 여러 팀의 새 기능을 추가하는 업데이트가, 어느 한 팀의 회귀(regression) 때문에 답답하게 롤백될 수 있습니다. 사용자에게는 기술 구현 세부 사항을 숨기고, 팀들이 각자의 도메인에 대해 완전한 자율성과 통제를 가지면서도 일관된 사용자 경험을 제공하게 하려면 어떻게 해야 할까요?
버티컬 마이크로프런트엔드가 해답이 될 수 있습니다. 이제 함께 더 깊이 들어가서, 이들이 개발자들의 고충을 어떻게 해결하는지 살펴보겠습니다.
버티컬 마이크로프런트엔드는 하나의 독립 팀이 UI부터 CI/CD 파이프라인에 이르기까지 애플리케이션 기능의 “한 조각(slice)” 전체를 소유하는 아키텍처 패턴입니다. 이러한 조각은 도메인의 경로로 정의되며, 특정 경로에 개별 Worker를 연결할 수 있습니다.
/ = 마케팅
/docs = 문서
/blog = 블로그
/dash = 대시보드
여기서 더 나아가, 대시보드처럼 더 세분화된 하위 경로에 Worker를 연결하는 것도 가능합니다. 대시보드 내부에서는 보통 URL 경로에 깊이를 더해(예: /dash/product-a) 다양한 기능이나 제품을 구분하고, 두 제품 사이를 이동하는 것은 완전히 다른 코드베이스로의 이동을 의미할 수 있습니다.
이제 버티컬 마이크로프런트엔드에서는 다음과 같은 구성도 가능합니다:
/dash/product-a = WorkerA
/dash/product-b = WorkerB
위 경로 각각은 서로 공유 코드가 전혀 없는 독립적인 프런트엔드 프로젝트입니다. product-a와 product-b 라우트는 별도로 배포된 프런트엔드 애플리케이션에 매핑되며, 각 애플리케이션은 고유한 프레임워크, 라이브러리, CI/CD 파이프라인을 가지고 해당 팀이 정의하고 소유합니다. 드디어.
이제 엔드 투 엔드로 자기 코드 전체를 소유할 수 있게 되었습니다. 하지만 이제는 이렇게 분리된 프로젝트들을 서로 “이어 붙여” 하나의 통합된 경험처럼 느껴지게 만들어야 합니다.
Cloudflare에서도 대시보드에 여러 팀이 각자 제품을 소유하고 있어 비슷한 고민을 겪고 있습니다. 팀은 자신이 통제할 수 없는 외부 변경이 사용자 경험에 영향을 미친다는 사실을 감당해야 합니다.
내부적으로 우리는 현재 대시보드에 유사한 전략을 적용하고 있습니다. 사용자가 코어 대시보드에서 ZeroTrust 제품으로 이동할 때, 실제로는 완전히 별개의 두 프로젝트이며 사용자는 경로 /:accountId/one로 라우팅되어 그 프로젝트로 이동하는 것뿐입니다.
개별 프로젝트들을 이어 붙여 통합된 경험처럼 보이게 만드는 일은 생각보다 어렵지 않습니다. CSS의 “약간의 마법” 몇 줄이면 됩니다. 우리가 절대로 원하지 않는 것은 구현 세부 사항과 내부 의사결정을 사용자에게 노출하는 것입니다. 사용자 경험을 하나의 응집된 프런트엔드처럼 느끼게 만드는 데 실패한다면, 이는 사용자에게 큰 잘못을 하는 것입니다.
이 손기술(sleight of hand)을 위해, 뷰 트랜지션(view transitions)과 문서 프리로딩(document preloading)이 어떻게 작동하는지 잠깐 살펴보겠습니다.
서로 다른 두 페이지 사이를 매끄럽게 이동하면서 사용자에게 부드럽게 느껴지게 하고 싶을 때, 뷰 트랜지션은 매우 유용합니다. 페이지에서 특정 DOM 요소를 다음 페이지가 표시될 때까지 유지하도록 정의하고, 변경 사항이 어떻게 처리될지 정의하면, 멀티 페이지 애플리케이션에서 강력한 “퀼트처럼 이어 붙이기” 도구가 됩니다.
다만, 경우에 따라서는 버티컬 마이크로프런트엔드들이 서로 다르게 느껴지도록 하는 것이 충분히 허용될 수도 있습니다. 예를 들어 마케팅 사이트, 문서, 대시보드가 각각 고유하게 정의되어 있다면, 사용자는 이 세 부분 사이를 이동할 때 모두가 완전히 동일한 느낌을 기대하지 않습니다. 하지만… 대시보드 같은 개별 경험 내부에 버티컬 슬라이스(예: /dash/product-a & /dash/product-b)를 도입했다면, 사용자는 절대로 내부적으로 두 개의 서로 다른 리포지토리/Worker/프로젝트가 존재한다는 사실을 알아차리면 안 됩니다.
자, 이제 말은 이쯤하고 시작해 봅시다. 두 개의 별도 프로젝트를 사용자에게 하나처럼 느끼게 만드는 게 “적은 노력”이라고 했는데, 아직 CSS View Transitions를 들어본 적이 없다면, 지금부터 생각이 바뀔 겁니다.
SPA든 MPA든, 서로 다른 뷰 사이의 애니메이션 전환을 “하나처럼” 만들 수 있다고 하면 어떨까요? 뷰 트랜지션을 추가하기 전에는, 서로 다른 Worker가 소유한 페이지 간 이동 시 다음 페이지 렌더링이 시작될 때까지 브라우저에서 몇 백 밀리초 정도 하얀 빈 화면(중간 로딩 상태)이 나타납니다. 페이지는 응집되어 보이지 않고, 단일 페이지 애플리케이션처럼 느껴지지도 않습니다.
각 사이트 사이에 서로 다른 내비게이션 요소가 나타나는 모습.
하얀 빈 페이지 대신 요소가 “남아 있도록” 만들고 싶다면, CSS View Transitions를 정의하면 됩니다. 아래 코드는 뷰 트랜지션 이벤트가 발생하려 할 때 현재 문서에서 nav DOM 요소를 화면에 유지하고, 기존 페이지와 목적지 페이지 사이에 모양 차이(delta)가 있다면 이를 ease-in-out 트랜지션으로 애니메이션 처리하라고 지시합니다.
순식간에, 서로 다른 두 Worker가 하나처럼 느껴집니다.
@supports (view-transition-name: none) {
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.3s;
animation-timing-function: ease-in-out;
}
nav { view-transition-name: navigation; }
}
서로 다른 세 사이트 사이에서도 하나의 내비게이션 요소처럼 보이는 모습.
두 페이지 사이 전환이 보기에 매끄럽게 만드는 것이라면, 우리는 느낌도 클라이언트 사이드 SPA처럼 즉각적이길 원합니다. 현재 Firefox와 Safari는 Speculation Rules를 지원하지 않지만, Chrome/Edge/Opera는 이 비교적 최신 API를 지원합니다. Speculation Rules API는 특히 문서 URL에 대해 향후 내비게이션 성능을 개선하도록 설계되어, 멀티 페이지 애플리케이션을 단일 페이지 애플리케이션처럼 느끼게 해줍니다.
코드로 풀어보면, 지원 브라우저에게 웹 애플리케이션에 연결된 다른 버티컬 슬라이스(보통 공통 내비게이션을 통해 링크됨)를 어떻게 프리페치(prefetch)할지 알려주는 특정 형식의 스크립트 규칙을 정의해야 합니다.
<script type="speculationrules">
{
"prefetch": [
{
"urls": ["https://product-a.com", "https://product-b.com"],
"requires": ["anonymous-client-ip-when-cross-origin"],
"referrer_policy": "no-referrer"
}
]
}
</script>
이렇게 하면 애플리케이션이 다른 마이크로프런트엔드를 프리페치하여 메모리 캐시에 보관하고, 해당 페이지로 이동할 때 거의 즉시 로드되는 것처럼 느껴집니다.
마케팅/문서/대시보드처럼 명확히 구분되는 버티컬 슬라이스에는 사용자가 약간의 로딩을 예상하기 때문에 꼭 필요하지 않을 수 있습니다. 하지만 대시보드 페이지 내부처럼, 하나의 눈에 보이는 경험 안에서 버티컬 슬라이스가 정의되는 경우에는 사용을 강력히 권장합니다.
View Transitions와 Speculation Rules를 조합하면, 완전히 다른 코드 리포지토리들을 단일 페이지 애플리케이션에서 제공되는 것처럼 느끼게 연결할 수 있습니다. 정말 놀랍죠.
이제 여러 애플리케이션을 호스팅하고, 요청이 들어올 때 이를 이어 붙이는 메커니즘이 필요합니다. 하나의 Cloudflare Worker를 “라우터(Router)”로 정의하면, 에지에서 네트워크 요청을 처리하는 단일 논리 지점이 생기고, URL 경로를 담당하는 버티컬 마이크로프런트엔드로 요청을 전달할 수 있습니다. 또한 하나의 도메인을 그 라우터 Worker에 매핑하면 나머지는 “그냥 동작”한다는 것도 장점입니다.
아직 Cloudflare Worker의 서비스 바인딩을 살펴보지 않았다면, 잠깐 시간을 내어 알아볼 가치가 있습니다.
서비스 바인딩은 공개적으로 접근 가능한 URL을 거치지 않고도 한 Worker가 다른 Worker를 호출할 수 있게 해줍니다. 서비스 바인딩을 통해 Worker A가 Worker B의 메서드를 호출하거나, Worker A의 요청을 Worker B로 포워딩할 수 있습니다. 더 구체적으로, 라우터 Worker는 정의된 각 버티컬 마이크로프런트엔드 Worker(예: 마케팅, 문서, 대시보드)로 호출을 전달할 수 있습니다(각각이 Cloudflare Worker라고 가정).
왜 이게 중요할까요? 이 메커니즘이 바로 이 버티컬 슬라이스들을 “이어 붙이는” 핵심이기 때문입니다. 다음 섹션에서 요청 라우팅이 트래픽을 어떻게 분할하는지 살펴보겠지만, 각 마이크로프런트엔드를 정의하려면 라우터 Worker의 wrangler 정의를 업데이트해서 어떤 프런트엔드를 호출할 수 있는지 알려야 합니다.
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "router",
"main": "./src/router.js",
"services": [
{
"binding": "HOME",
"service": "worker_marketing"
},
{
"binding": "DOCS",
"service": "worker_docs"
},
{
"binding": "DASH",
"service": "worker_dash"
},
]
}
위 예시는 라우터 Worker에 정의되며, 이를 통해 우리는 세 개의 추가 Worker(마케팅, 문서, 대시보드)로 요청을 보낼 권한이 있음을 알 수 있습니다. 권한 부여는 이처럼 간단하지만, 이제 요청 라우팅과 HTML 리라이팅(network response)이라는 더 복잡한 로직으로 넘어가 보겠습니다.
필요할 때 호출할 수 있는 다양한 Worker를 알고 있으니, 이제 언제 어디로 네트워크 요청을 보낼지 결정하는 로직이 필요합니다. 라우터 Worker는 커스텀 도메인에 할당되므로, 모든 들어오는 요청은 네트워크 에지에서 먼저 라우터 Worker에 도달합니다. 그 다음 어떤 Worker가 요청을 처리해야 하는지 결정하고, 결과 응답을 관리합니다.
첫 단계는 URL 경로를 관련 Worker에 매핑하는 것입니다. 특정 요청 URL이 들어오면 어디로 포워딩해야 하는지 알아야 합니다. 이를 위해 규칙을 정의합니다. 와일드카드 라우트, 동적 경로, 파라미터 제약도 지원하지만, 핵심을 더 명확히 보여주기 위해 여기서는 기본인 “리터럴 경로 프리픽스”에 집중하겠습니다.
이 예시에서는 세 개의 마이크로프런트엔드가 있습니다:
/ = 마케팅
/docs = 문서
/dash = 대시보드
위 경로 각각은 실제 Worker(앞 섹션의 wrangler 서비스 정의 참고)에 매핑되어야 합니다. 라우터 Worker에서는 어떤 경로가 어떤 서비스 바인딩에 매핑되는지 알기 위해 아래 데이터로 추가 변수를 정의합니다. 이제 요청이 들어올 때 사용자를 어디로 라우팅할지 알게 됩니다! ROUTES라는 이름의 wrangler 변수를 만들고 다음 내용을 넣습니다:
{
"routes":[
{"binding": "HOME", "path": "/"},
{"binding": "DOCS", "path": "/docs"},
{"binding": "DASH", "path": "/dash"}
]
}
사용자가 /docs/installation 경로를 방문한다고 상상해 봅시다. 내부적으로는 요청이 먼저 라우터 Worker에 도달하고, 라우터 Worker는 어떤 URL 경로가 어떤 Worker로 매핑되는지 이해하는 역할을 합니다. 라우터는 /docs 경로 프리픽스가 DOCS 서비스 바인딩에 매핑되어 있고, wrangler 파일을 통해 worker_docs 프로젝트를 가리킨다는 사실을 압니다. 라우터 Worker는 /docs가 버티컬 마이크로프런트엔드 라우트로 정의되어 있음을 알고, 경로에서 /docs 프리픽스를 제거한 뒤 요청을 worker_docs Worker로 전달하여 처리하게 하고, 마지막으로 받은 응답을 그대로 반환합니다.
그런데 왜 /docs 경로를 제거할까요? 이는 라우터 Worker를 통해 접근할 때 URL을 정리해서, 라우터 밖에서 호출된 것 처럼 Worker가 요청을 처리하게 하려는 구현 선택입니다. 다른 Cloudflare Worker처럼 worker_docs 서비스도 단독으로 접근 가능한 자체 URL을 가질 수 있습니다. 우리는 그 서비스 URL이 계속 독립적으로 동작하길 원했습니다. 새 라우터 Worker에 연결되면 프리픽스를 자동으로 제거해주므로, 서비스가 자기 URL로 접근되든 라우터 Worker를 통해 접근되든… 어느 쪽이든 상관없게 됩니다.
URL 경로(예: /docs 또는 /dash)로 프런트엔드 서비스를 나누면 요청 포워딩은 쉬워지지만, 응답에 포함된 HTML이 “경로 컴포넌트를 통해 리버스 프록시되고 있다”는 사실을 모르면 문제가 생깁니다.
예를 들어 문서 사이트의 응답에 이미지 태그 <img src="./logo.png" />가 있다고 합시다. 사용자가 https://website.com/docs/에서 이 페이지를 보고 있다면, logo.png 로딩은 실패할 수 있습니다. 왜냐하면 /docs 경로는 라우터 Worker가 인위적으로 정의한 것에 가깝기 때문입니다.
서비스가 라우터 Worker를 통해 접근될 때에만, 반환되는 브라우저 응답이 유효한 자산을 참조하도록 절대/상대 경로를 HTML 리라이트할 필요가 있습니다. 실제로는 요청이 라우터 Worker를 통과할 때 올바른 서비스 바인딩으로 요청을 전달하고 그 응답을 받습니다. 이를 클라이언트로 넘기기 전에 DOM을 리라이트할 기회가 있는데, 절대/상대 경로를 발견하면 프록시된 경로를 앞에 붙입니다. 기존에 <img src="./logo.png" />를 반환하던 HTML은, 클라이언트 브라우저에 반환하기 전에 <img src="./docs/logo.png" />로 수정됩니다.

CSS 뷰 트랜지션과 문서 프리로딩의 마법으로 잠시 돌아가 보겠습니다. 물론 각 프로젝트에 직접 코드를 넣어 동작하게 만들 수도 있지만, 이 라우터 Worker는 HTMLRewriter를 사용해 그 로직을 자동으로 처리해 줄 수도 있습니다.
라우터 Worker의 ROUTES 변수에서 루트 레벨에 smoothTransitions를 true로 설정하면, CSS 트랜지션 뷰 코드가 자동으로 추가됩니다. 또한 라우트 내부에서 preload 키를 true로 설정하면, 해당 라우트에 대한 speculation rules 스크립트 코드도 자동으로 추가됩니다.
아래는 두 기능을 함께 사용한 예시입니다:
{
"smoothTransitions":true,
"routes":[
{"binding": "APP1", "path": "/app1", "preload": true},
{"binding": "APP2", "path": "/app2", "preload": true}
]
}
오늘부터 버티컬 마이크로프런트엔드 템플릿으로 개발을 시작할 수 있습니다.
Cloudflare 대시보드 딥링크로 이동하거나, “Workers & Pages”로 가서 “Create application” 버튼을 클릭해 시작하세요. 그 다음 “Select a template”을 클릭하고 “Create microfrontend”를 선택하면 구성을 시작할 수 있습니다.

기존 Worker를 매핑하고 View Transitions를 활성화하는 방법은 문서를 확인해 보세요. 에지에서 동작하는 복잡한 멀티 팀 애플리케이션을 여러분이 무엇을 만들지 기대됩니다!
Cloudflare의 Connectivity Cloud는 기업 전체 네트워크를 보호하고, 고객이 인터넷 규모 애플리케이션을 효율적으로 구축하도록 돕고, 모든 웹사이트 또는 인터넷 애플리케이션을 가속하며, DDoS 공격을 방어하고, 해커를 차단하며, Zero Trust 여정을 지원합니다.
어떤 기기에서든 1.1.1.1을 방문해 인터넷을 더 빠르고 안전하게 만드는 무료 앱으로 시작해 보세요.
더 나은 인터넷을 만들기 위한 우리의 미션에 대해 더 알아보려면 여기서 시작하세요. 새로운 커리어 방향을 찾고 있다면 채용 공고를 확인해 보세요.
Cloudflare WorkersDeveloper PlatformDevelopersDashboardFront EndMicro-frontends