[번역] 모든 프론트엔드 아키텍처 패턴 한 번에 이해하기
Soshy·

내가 준비했던 프론트엔드 면접에서는 결국 같은 질문이 조금씩 다른 모습으로 등장했다.
무엇을 만들었느냐가 아니다. 왜 그렇게 만들었느냐는 질문이다.
오랫동안 내 답은 “원래 팀에서 그렇게 쓰고 있었으니까요”를 크게 벗어나지 못했다.
누군가 조금 더 일찍 알려줬으면 좋았을 이야기가 있다.
프론트엔드 아키텍처 패턴은 결국 하나의 질문에 대한 여러 답이다.
브라우저와 서버, 그리고 빌드 시점에 각각 얼마나 많은 일을 맡길 것인가?
SSR, RSC, 마이크로 프론트엔드(micro-frontend), BFF 같은 것들은 전부 이 경계를 어디에 그을 것인지에 대한 서로 다른 답일 뿐이다.
시작하기 전에 솔직하게 밝혀둘 것이 있다.
나는 개발자 200명이 일하는 회사에서 직접 마이크로 프론트엔드 마이그레이션을 이끌어본 적은 없다.
이 글의 내용은 내가 프로덕션 애플리케이션을 개발하면서 얻은 경험과 공식 문서, 그리고 SoundCloud, Netflix, Spotify, Vercel, Cloudflare 같은 회사의 엔지니어링 글을 바탕으로 했다.
특정 회사의 경험을 이야기할 때는 그 사실을 명확히 밝히겠다.
내가 모든 것을 직접 해봤다고 가장하는 것보다 이쪽이 훨씬 유용하다고 생각한다.

정적 페이지에서 Thick Client까지
웹은 처음부터 복잡하지 않았다.
초기의 정적 HTML 페이지는 즉시 로드됐고, 고장 날 만한 것도 거의 없었다.
그러다 Django, Rails, ASP.NET 같은 서버 사이드 MVC 프레임워크가 등장하면서 요청이 들어올 때마다 서버에서 HTML을 생성하기 시작했다.
동적인 데이터를 보여줄 수 있게 됐다. 하지만 무언가를 클릭할 때마다 페이지 전체를 다시 로드해야 했다.
그다음 SPA 시대가 왔다. React, Vue, Angular가 등장했다.
갑자기 브라우저가 거의 모든 일을 맡게 됐다. 라우팅, 상태 관리, 검증, 때로는 인증까지 처리한다. 일단 로드되고 나면 즉각적으로 반응하는 듯한 UI를 만들 수 있다는 것이 장점이다.
문제는 바로 그 “일단 로드되고 나면” 이라는 부분이다.
거대한 JavaScript 번들, 느린 첫 화면 렌더링이 뒤따른다. SEO도 까다로워진다. 요즘 Google은 JavaScript를 실행하기는 하지만, 대규모 사이트에서는 렌더링 때문에 색인 생성이 늦어질 수 있다. 소셜 미디어 미리보기 봇이나 많은 AI 크롤러처럼 JavaScript를 아예 실행하지 않는 크롤러도 많다.
이들이 콘텐츠를 볼 수 없다면, 이들에게는 그 콘텐츠가 존재하지 않는 것이나 마찬가지다.
솔직히 말하면 이것이 프론트엔드 아키텍처의 전체 역사라고 해도 크게 틀리지 않는다.
우리는 계속해서 씬 클라이언트(thin client, 서버가 대부분의 작업을 담당하는 구조)와 씩 클라이언트(thick client, 브라우저가 대부분의 작업을 담당하는 구조) 사이의 경계를 움직여 왔다.
이제부터 살펴볼 모든 패턴도 결국 그 경계를 서로 다른 위치에 그은 것이다.

Backend-for-Frontend: 디바이스별로 하나의 API를 다시 구성하기
이 질문은 정말 자주 나오니 바로 답부터 해보자.
Backend-for-Frontend(BFF)는 프론트엔드 팀이 소유하는 작은 서버 계층이다. UI와 실제 백엔드 서비스 사이에 위치하며, 특정 UI가 필요로 하는 형태에 정확히 맞도록 데이터를 재구성하는 것이 유일한 역할이다.

이 패턴에는 명확한 탄생지가 있다. SoundCloud다.
2013년 무렵 SoundCloud 팀은 모놀리스를 마이크로서비스로 분리하고 있었다. 그런데 웹, iOS, Android 같은 모든 클라이언트가 어느 쪽에도 제대로 맞지 않는 하나의 공용 API를 두고 씨름하고 있었다.
그들이 선택한 해결책은 각 클라이언트에 전용 씬 백엔드를 하나씩 제공하는 것이었다.
Phil Calçado가 당시 SoundCloud의 사례를 기록했고, Sam Newman이 이를 대표적인 아키텍처 패턴으로 정리하면서 BFF라는 이름이 자리 잡았다.
Netflix도 같은 문제를 자신들만의 방식으로 해결했다. 스마트 TV와 스마트폰은 필요한 데이터 구조가 크게 다르기 때문에, 디바이스별로 최적화된 어댑터 계층을 두었다.
문제 상황은 쉽게 떠올릴 수 있다.
모바일 앱에는 상품명, 가격, 썸네일만 필요하다고 해보자. 웹 앱에는 여기에 리뷰, 재고, 관련 상품까지 필요하다.
하나의 공용 endpoint를 사용하면 모바일에서는 필요 이상의 데이터를 가져오는 오버페칭(over-fetching)이 발생하거나, 웹에서는 필요한 데이터를 충분히 가져오지 못하는 언더페칭(under-fetching)이 발생한다. 결국 온갖 쿼리 파라미터를 추가하며 문제를 쫓아다니게 된다.
BFF를 사용하면 각 클라이언트에 맞는 형태의 페이로드를 내려줄 수 있다.
// bff.js — Node 18+, fetch는 기본 제공
import express from 'express';
const app = express();
const API = 'https://api.example.com';
app.get('/products/:id', async (req, res) => {
const response = await fetch(`${API}/products/${req.params.id}`);
const data = await response.json();
res.json({
id: data.id,
name: data.title,
price: data.price,
thumbnail: data.images[0],
});
});
app.listen(4000, () => console.log('BFF listening on :4000'));
프론트엔드는 /products/123을 호출하고 자신이 요청한 형태의 데이터만 정확히 받는다. 불필요한 필드도 없고, 추가적인 네트워크 왕복도 없다.
하지만 비용도 분명히 존재한다.
BFF는 운영하고, 모니터링하고, 장애 없이 유지해야 하는 서비스가 하나 더 추가된다는 뜻이다.
BFF가 다운되면 실제 백엔드가 정상이어도 UI가 함께 다운된다.
“그냥 BFF 하나 추가하면 되죠”라는 조언에서는 이 부분이 너무 가볍게 다뤄지는 것 같다.
BFF는 공짜 인프라가 아니다. 프론트엔드 팀의 작업을 편하게 해주는 대신, 새로운 단일 장애점(single point of failure)을 하나 추가하는 것이다.
렌더링 전략: HTML은 실제로 어디에서 만들어지는가
최근 몇 년 사이 가장 크게 변한 영역이다. 동시에 실제 환경에서는 각 방식의 경계가 다이어그램에서 보이는 것보다 훨씬 흐릿한 영역이기도 하다.
정적 사이트 생성(Static Site Generation, SSG)은 빌드 시점에 모든 페이지를 생성한 뒤 CDN에서 정적 파일로 제공한다.
속도와 비용 측면에서는 이보다 뛰어난 방식을 찾기 어렵다. 하지만 다음 배포 전까지 콘텐츠가 그대로 고정된다는 단점이 있다.
증분 정적 재생성(Incremental Static Regeneration, ISR)은 SSG에 탈출구를 하나 만든 방식이다.
설정한 시간이 지나면 특정 페이지를 조용히 다시 생성할 수 있다. 콘텐츠가 바뀔 때마다 다시 배포하지 않으면서도 SSG의 속도 대부분을 유지할 수 있다.
서버 사이드 렌더링(Server-Side Rendering, SSR)은 요청이 들어올 때마다 HTML을 생성한다.
전통적인 방식에서는 완성된 페이지 전체를 먼저 전송한 뒤 하이드레이션(hydration)을 진행한다. 브라우저가 JavaScript 번들을 다운로드하고, 이미 존재하는 마크업에 동작을 연결한다.
React 18에서는 스트리밍 SSR(streaming SSR)인 renderToPipeableStream이 추가됐다. 전체 컴포넌트 트리가 완성되기를 기다리지 않고, 준비되는 부분부터 청크 단위로 전송한다.
새로운 애플리케이션에서는 스트리밍이 현대적인 기본 방식이 됐지만, 여전히 많은 프로덕션 애플리케이션이 기존의 한 번에 전체를 전송하는 방식으로 문제없이 동작하고 있다.
React Server Components(RSC)와 아일랜드 아키텍처(island architecture)는 여기서 한 단계 더 나아간다.
페이지에서 실제로 상호작용이 필요한 부분에만 JavaScript를 전송한다.
정적인 콘텐츠는 하이드레이션할 필요 없이 순수 HTML로 남는다.

Astro의 아일랜드도 React 바깥에서 같은 방식을 사용한다.
Qwik은 재개 가능성(resumability)을 통해 더 나아간다. 애플리케이션 상태를 HTML 자체에 직렬화하여 하이드레이션을 거의 완전히 건너뛴다.
현재 Next.js 15+에서 RSC는 다음과 같은 모습이다. 이제 params가 Promise이므로 await해야 한다.
// app/products/[id]/page.tsx (Next.js 15+)
import { fetchProduct } from '@/lib/api';
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await fetchProduct(id); // 서버에서 실행됨
return (
<main>
<h1>{product.name}</h1>
<p>${product.price}</p>
<AddToCartButton productId={product.id} />
</main>
);
}
fetchProduct는 서버에서 실행되므로 상품명과 가격은 이미 렌더링된 상태로 전달된다.
클라이언트 JavaScript가 전달되는 부분은 AddToCartButton뿐이다.
번들은 작아진다. 상호작용이 필요한 곳만 인터랙티브하게 만들고, 나머지는 모두 정적으로 유지한다.

엣지 렌더링의 이야기, 그리고 왜 다시 돌아섰는가
이 주제는 별도의 섹션으로 다룰 만하다.
현대 프론트엔드에서 가장 흥미로운 “우리가 틀렸다” 사례 중 하나이면서, 면접에서도 좋은 답변 소재이기 때문이다.
2021년부터 2023년 사이의 주장은 완벽하게 들렸다.
Cloudflare Workers나 Vercel Edge Functions처럼 사용자와 가까운 데이터센터의 엣지(edge)에서 SSR을 실행한다.
거리가 줄어드니 페이지도 빨라진다.
Vercel은 이 방식을 강하게 밀었다.
그러다 Vercel은 공개적으로 방향을 바꿨다.
당시 Vercel의 VP of Product였던 Lee Robinson은 이를 두고 직접 이렇게 말했다.
“이건 나도 속았다.”
문제는 컴퓨팅 자원이 사용자와 가까이 있기만 하면 되는 것이 아니라는 점이다.
데이터베이스와도 가까워야 한다.
대부분의 데이터베이스는 특정 리전 하나에 존재한다.
그렇기 때문에 도쿄의 엣지 함수가 버지니아의 데이터베이스를 호출한다면, 그냥 버지니아에서 직접 렌더링하는 것보다 오히려 느려질 수 있었다.

Vercel은 자체 제품인 v0에서도 이를 테스트했고, 일반적인 Node.js 렌더링이 엣지 렌더링보다 더 빨랐다는 결과를 얻었다.
2025년에는 Edge Functions가 공식적으로 deprecated됐고, Vercel의 권장 사항도 바뀌었다.
Node.js 런타임을 사용하고 컴퓨팅 자원을 데이터와 같은 리전에 배치하라는 것이다.
그렇다고 엣지에 대한 아이디어가 완전히 사라진 것은 아니다. 대신 좀 더 제한적인 형태로 살아남았다.
페이지의 정적 셸(shell)은 엣지에서 즉시 제공하고, 동적인 부분은 데이터와 가까운 곳에 있는 컴퓨팅 자원에서 스트리밍한다.
Vercel의 Partial Prerendering이 대략 이런 방식이다.
Cloudflare Workers 역시 여전히 실제로 사용되는 엣지 SSR 플랫폼이다. 데이터 자체도 전 세계에 분산돼 있다면 특히 잘 동작한다.
하지만 2026년 기준으로 솔직하게 요약한다면 이렇다.
사용자와의 거리보다 데이터와의 거리가 더 중요했다.
면접에서 엣지 렌더링에 대한 질문을 받는다면, 관련 용어를 아는 것보다 업계가 왜 방향을 바꿨는지 이해하는 편이 훨씬 가치 있다.
모듈형 프론트엔드 모놀리스
SPA의 규모와 팀 규모가 어느 수준을 넘어서면 모든 코드가 평평하게 섞여 있는 저장소는 위험해진다.
모두가 같은 공용 컴포넌트를 수정하고, 무엇을 누가 소유하는지도 불분명해진다.
모듈형 모놀리스(modular monolith)는 코드베이스를 크게 두 영역으로 나눈다.
하나는 디자인 시스템, 공용 훅, 로깅처럼 플랫폼 팀이 관리하는 플랫폼 계층이다.
다른 하나는 user/나 payments/ 같은 기능 폴더를 각각의 기능 팀이 관리하는 도메인 계층이다.
클린 아키텍처(clean architecture)나 헥사고날 아키텍처(hexagonal architecture)와 비슷한 방향성이지만, 모든 형식을 그대로 따르지는 않는다.
프론트엔드에서는 완전한 클린 아키텍처가 대부분 지나치게 복잡하다.
버튼 하나와 fetch 호출 하나 사이에 세 단계의 추상화 계층을 둘 필요까지는 없다.
마이크로 프론트엔드: 독립성을 얻는 대신 치르는 대가
마이크로 프론트엔드는 각각의 도메인을 독립적으로 배포할 수 있는 작은 애플리케이션으로 취급한다.
보통 Webpack Module Federation 같은 기술을 이용해 셸(shell)이 런타임에 각 애플리케이션을 불러온다.
장점은 실질적인 독립성이다.
각 팀이 자신의 일정에 맞춰 배포할 수 있다.
정말 필요하다면 서로 다른 프레임워크를 섞어서 사용할 수도 있다.
Zalando, IKEA, DAZN 같은 회사들은 이 방식을 대규모로 운영한 경험을 공개한 바 있다. 다만 이런 사례는 항상 팀 규모가 크고, 공용 도구에 상당한 투자가 이뤄진 환경이었다.
이 패턴 자체에 대한 자세한 설명이 필요하다면 micro-frontends.org가 대표적인 참고 자료다.
하지만 실제 장애 회고에서 반복해서 나타나는 문제는 프레임워크를 섞는 것 자체가 아니다.
공유 의존성 버전이 서로 달라지는 문제(shared dependency drift) 다.

한 원격 애플리케이션은 공용 라이브러리를 업데이트했는데 다른 애플리케이션은 업데이트하지 않았다고 해보자.
그러면 한 페이지에서 서로 다른 두 버전의 React가 조용히 실행되면서 같은 DOM을 두고 충돌할 수 있다.
모두가 막연하게 이야기하는 “복잡성”보다 실제로 팀을 더 괴롭히는 것은 이런 구체적인 문제다.
반대쪽에도 경계할 만한 사례가 있다.
Spotify는 몇 년 전 데스크톱 앱에서 iframe 기반 마이크로 프론트엔드 구조를 실험했다.
하지만 이후 다시 하나의 통합된 아키텍처로 합쳤다. 각 시스템 사이의 경계에서 발생하는 비용이 독립성을 통해 얻는 이점보다 커졌던 것이 그 이유 중 하나였다.
규모가 엄청나게 큰 조직이라고 해서 마이크로 프론트엔드가 자동으로 정답이 되는 것은 아니다.
그래서 아키텍처 다이어그램에는 잘 나오지 않는 질문을 하나 던져야 한다.
실제로 개발자가 몇 명인가?
개발자가 약 15명 미만이라면: 모듈형 모놀리스가 거의 항상 유리하다. 별도의 배포 파이프라인을 여러 개 운영할 만큼 사람이 많지 않다.
개발자가 15~50명 정도이고 명확한 도메인 경계가 몇 개 있다면: BFF와 잘 구성된 모듈형 모놀리스만으로 대부분 해결할 수 있다. 이 단계에서도 마이크로 프론트엔드는 아직 이른 경우가 많다.
개발자가 50명 이상이고 여러 팀이 배포 과정에서 실제로 서로를 막고 있다면: 그때부터 마이크로 프론트엔드의 비용이 정당화되기 시작한다. 애플리케이션이 커졌기 때문이 아니다. 조직이 커졌기 때문이다.

물론 이 기준이 모든 조직에 완벽하게 들어맞는다고 생각하지는 않는다.
다만 여러 팀이 실제 의사결정 과정을 회고할 때 반복해서 등장하는 패턴이 이렇다는 것이다.
면접에서 아키텍처를 이야기하는 방법
시니어 포지션에서는 패턴 목록을 읊는 것이 아니라 선택을 기대한다.
좋은 답변은 다음과 같은 구조를 가진다.
현재 어떤 방식을 사용하는가 — “마케팅 페이지는 SSG를 사용하고, 대시보드는 SSR을 사용하며, 모바일 앱 앞에는 BFF를 두고 있습니다.”
왜 그렇게 했는가 — “콘텐츠 변경이 거의 없는 영역에서는 SSG를 통해 1초 미만의 빠른 첫 화면 렌더링을 얻을 수 있습니다. SSR을 사용하면 잘못된 사용자 정보가 잠깐 보였다가 바뀌는 현상 없이 처음부터 사용자 이름을 표시할 수 있습니다.”
어떤 트레이드오프를 받아들였는가 — “BFF를 추가하면 네트워크 홉이 하나 늘어납니다. 대신 프론트엔드 팀이 자신에게 필요한 데이터 구조를 직접 소유할 수 있습니다. 그만한 가치가 있다고 판단했습니다.”
무엇을 선택하지 않았으며, 그 이유는 무엇인가 — “마이크로 프론트엔드도 검토했습니다. 하지만 현재 팀 규모에서는 협업과 조율에 드는 오버헤드를 감수할 만큼 이점이 크지 않았습니다.”
마지막 항목이 가장 중요하다.
무엇을 선택하지 않았는지 설명할 수 있느냐가 단순히 아키텍처 다이어그램을 외운 것과 실제로 의사결정을 내릴 줄 아는 것의 차이다.
앞에서 살펴본 Vercel의 엣지 사례는 바로 활용할 수 있는 좋은 예다.
엣지 렌더링을 적극적으로 판매하던 플랫폼조차 데이터가 다른 결론을 보여주자 자신의 판단을 바꿨다.
실제 아키텍처 사고란 이런 것이다.
아직 궁금할 만한 몇 가지 질문(FAQ)
1. SSR과 SSG의 차이는 무엇인가?
SSR은 요청이 들어올 때마다 HTML을 생성하기 때문에 실시간 데이터나 개인화된 데이터를 보여줄 수 있다.
SSG는 빌드 시점에 HTML을 한 번 생성한 뒤 정적 파일로 제공한다. 더 빠르고 저렴하지만 콘텐츠의 최신 상태는 마지막 배포 시점에 머문다.
ISR은 특정 페이지를 일정 시간이 지날 때마다 다시 생성해 둘 사이의 절충점을 제공한다.
2. 프론트엔드가 하나뿐이어도 BFF가 필요한가?
아마 필요하지 않을 가능성이 높다.
BFF는 모바일, 웹, 파트너 API처럼 여러 클라이언트가 같은 백엔드에서 서로 크게 다른 형태의 데이터를 필요로 할 때 가치가 생긴다.
프론트엔드가 하나뿐이라면 대부분 별다른 이유 없이 네트워크 홉 하나만 추가하는 셈이다.
3. 엣지 렌더링은 끝난 기술인가?
아니다. 다만 기본적인 접근 방식이 바뀌었다.
Vercel은 독립적인 Edge Functions를 deprecated하고, 현재는 데이터베이스와 가까운 곳에 Node.js 컴퓨팅 자원을 배치하는 방식을 권장한다.
정적 셸을 제공하거나 데이터 자체가 실제로 전 세계에 분산된 애플리케이션에서는 여전히 엣지가 유리하다. 이런 환경에서는 Cloudflare Workers가 강점을 보인다.
현재의 경험칙은 다음과 같다.
컴퓨팅 자원을 사용자 가까이가 아니라 데이터 가까이에 둬라.
4. 팀은 언제 실제로 마이크로 프론트엔드로 전환해야 하는가?
팀 간 배포 조율 자체가 병목이 되기 시작했을 때다. 그 이전에는 아니다.
애플리케이션이 복잡하더라도 하나의 팀이 전체를 담당하고 있다면 모듈형 모놀리스로 훨씬 적은 오버헤드만 감수하면서 같은 문제를 해결할 수 있다.
이 모든 패턴은 결국 같은 질문에 답한다.
브라우저가 얼마나 많은 일을 해야 하고, 서버는 얼마나 해야 하며, 빌드 시점에는 얼마나 처리해야 하는가?
그리고 올바른 답은 면접에서 어떤 패턴이 가장 멋있게 들리는가보다 팀의 규모, 데이터가 위치한 곳, 그리고 배포 과정에서 겪는 문제에 훨씬 더 크게 좌우된다.
결론
면접에서 정말로 묻고 있었던 것은 무엇을 만들었느냐가 아니었다.
왜 그렇게 만들었는지 알고 있느냐를 묻고 있었던 것이다.
이제는 적어도 “원래 팀에서 그렇게 쓰고 있었으니까요”가 아닌 답을 할 수 있다.