Ray Book
프레임워크의 철학

빌드와 런타임, 언제 일을 하는가

컴파일 타임과 런타임 사이의 스펙트럼, 각 프레임워크가 어디에 비중을 두는지 비교합니다

frameworkbuildcompilerruntimebundle-size

핵심 질문

프레임워크는 언제 일을 하는가, 빌드할 때? 실행할 때?

npm run build를 누르면 어떤 일이 일어나는가? 번들에는 무엇이 포함되는가? 브라우저에서 실행되는 코드의 양은 얼마인가? 이 질문들에 대한 답이 프레임워크마다 극적으로 다릅니다.

스펙트럼: 순수 런타임 ↔ 컴파일러 중심

프레임워크의 빌드/런타임 전략은 하나의 스펙트럼 위에 놓입니다.

순수 런타임                                    컴파일러 중심
    ←─────────────────────────────────────────→
  React        Vue        Angular        Svelte
  (변환만)    (힌트 삽입)   (AOT 컴파일)   (전체 컴파일)

왼쪽으로 갈수록 빌드는 가볍고 런타임이 무겁습니다. 오른쪽으로 갈수록 빌드가 무겁고 런타임이 가볍습니다. 어느 쪽이 "정답"이라는 것은 없습니다, 각각 다른 트레이드오프를 선택한 것입니다.

React: 거의 모든 것이 런타임

React의 빌드 과정은 단순합니다. JSX를 JavaScript로 변환하는 것이 전부입니다.

// 빌드 전 (JSX)
<button onClick={handleClick}>{count}</button>

// 빌드 후 (JavaScript)
React.createElement('button', { onClick: handleClick }, count)

Babel이나 SWC가 하는 일은 구문 변환 뿐입니다. 최적화? 분석? 없습니다. React의 진짜 일은 모두 브라우저에서 일어납니다, VDOM 생성, diff, 재조정, 스케줄링.

이것이 React의 강점이자 약점입니다.

  • 강점 : 런타임에 모든 결정을 내리므로 극도로 동적입니다. 조건부 렌더링, 동적 컴포넌트, 런타임 코드 생성 등 무엇이든 가능합니다
  • 약점 : 런타임 라이브러리 (~42KB gzip) 가 반드시 번들에 포함됩니다. 앱이 "Hello World"여도 이 비용을 지불합니다

React Compiler (v1.0, 2025년 10월 안정화) 는 이 그림을 바꾸고 있습니다. useMemo, useCallback, React.memo를 자동으로 삽입하여 불필요한 re-render를 빌드 타임에 제거합니다. 하지만 VDOM 자체를 없애는 것은 아닙니다, React는 여전히 런타임 중심입니다.

1. 소스 코드1 / 3
JSXReact 컴포넌트 소스
Babel / SWC
Bundle
React
JSX로 작성된 React 컴포넌트입니다. JSX는 JavaScript가 아니므로 브라우저가 직접 실행할 수 없습니다.

Vue: 빌드와 런타임의 협력

Vue는 중간 지점을 택합니다. SFC 컴파일러가 빌드 시 최적화 힌트를 삽입하고, 런타임이 그 힌트를 활용합니다.

<template>
  <div>
    <h1>정적 제목</h1>          <!-- 컴파일러: "이건 절대 안 바뀜" -->
    <p>{{ dynamicText }}</p>   <!-- 컴파일러: "이것만 추적하면 됨" -->
  </div>
</template>

vue-compiler-sfc는 템플릿을 분석하여 정적 노드를 호이스팅하고, 동적 바인딩에 패치 플래그 를 삽입합니다. 런타임 VDOM diff는 이 플래그를 보고 "변경될 수 있는 부분"만 비교합니다.

결과적으로 Vue는 VDOM을 쓰되, React보다 훨씬 적은 범위만 diff합니다. 빌드 시 컴파일러가 "어디를 봐야 하는지" 미리 알려주기 때문입니다.

Vue Vapor 모드 (Vue 3.6 베타, 2025년 말) 는 한 걸음 더 나아갑니다, VDOM을 완전히 제거하고 컴파일 타임 DOM 조작 코드를 생성합니다. Svelte의 접근과 유사하지만, 기존 Vue 코드와 호환되는 opt-in 방식입니다.

1. 소스 코드1 / 3
.vue SFC단일 파일 컴포넌트
vue-compiler-sfc
Bundle
Vue
.vue 파일 (SFC) 에는 <script>, <template>, <style>이 하나의 파일에 담겨 있습니다.

Angular: 빌드 시 가능한 한 많이 처리

Angular의 빌드 과정은 가장 무겁습니다. AOT (Ahead-of-Time) 컴파일러가 빌드 시 수행하는 작업:

  1. 데코레이터 분석 : @Component, @Injectable 등의 메타데이터를 파싱
  2. 템플릿 컴파일 : HTML 템플릿을 Ivy 명령어 (DOM 조작 함수 호출) 로 변환
  3. 의존성 주입 분석 : 서비스 간 의존 관계를 정적으로 해석
  4. 타입 체크 : 템플릿 내 바인딩의 타입까지 검증
// 빌드 전
@Component({
  template: '<p>{{ message }}</p>'
})
class MyComponent {
  message = 'hello';
}

// 빌드 후 (Ivy 명령어, 단순화)
function MyComponent_Template(rf, ctx) {
  if (rf & 1) { // 생성
    ɵɵelementStart(0, 'p');
    ɵɵtext(1);
    ɵɵelementEnd();
  }
  if (rf & 2) { // 업데이트
    ɵɵadvance(1);
    ɵɵtextInterpolate(ctx.message);
  }
}

Ivy 명령어는 VDOM 없이 DOM을 직접 조작합니다. 생성 (rf & 1) 과 업데이트 (rf & 2) 가 분리되어, 업데이트 시에는 변경된 바인딩만 실행됩니다.

Angular 21부터 zone.js가 기본에서 제거되고 Signals 기반 변경 감지가 표준이 되면서, 런타임 오버헤드가 크게 줄었습니다. 하지만 프레임워크 코어의 크기 자체는 여전히 큽니다.

1. 소스 코드1 / 3
.ts + TemplateTS 클래스 + HTML 템플릿
AOT Compiler (ngc)
Bundle
Angular
TypeScript 클래스와 HTML 템플릿으로 구성됩니다. @Component 데코레이터가 메타데이터를 제공합니다.

Svelte: "런타임이 거의 없다"

Svelte는 스펙트럼의 가장 오른쪽에 있습니다. .svelte 파일은 프레임워크가 아니라 컴파일러의 입력 입니다.

<!-- 빌드 전 -->
<script>
  let count = $state(0);
</script>
<button onclick={() => count++}>{count}</button>

컴파일러가 이것을 순수 JavaScript로 변환합니다.

// 빌드 후 (단순화)
let count = source(0);     // 시그널 객체

// DOM 생성
const button = document.createElement('button');

// 시그널 → DOM 직접 연결
render_effect(() => {
  button.textContent = get(count);
});

button.addEventListener('click', () => set(count, get(count) + 1));

VDOM도 없고, diff도 없습니다. 시그널이 변경되면 그 시그널에 연결된 DOM 노드만 직접 업데이트됩니다.

이 접근의 핵심 트레이드오프: 앱이 작을수록 Svelte가 압도적으로 유리하고, 앱이 커지면 차이가 줄어듭니다. 왜냐하면 Svelte는 컴포넌트마다 DOM 조작 코드를 생성하므로, 컴포넌트가 많아질수록 코드량이 선형으로 증가합니다. 반면 React/Vue의 런타임은 고정 비용이므로 컴포넌트가 아무리 많아도 런타임 크기는 변하지 않습니다.

1. 소스 코드1 / 3
.svelte컴파일러 입력 파일
Svelte Compiler
Bundle
Svelte
.svelte 파일에 마크업, 로직, 스타일이 담겨 있습니다. React나 Vue와 달리, 이 파일은 프레임워크가 아니라 컴파일러의 입력입니다.

번들 사이즈 비교

빈 앱 (Hello World)

빈 앱에서의 차이는 극적입니다.

Svelte:   ~3KB    ████
Vue:      ~33KB   ████████████████████████████████
React:    ~44KB   ██████████████████████████████████████████
Angular:  ~90KB+  ████████████████████████████████████████████████████████████████████████████████████████

Svelte가 30배 가까이 작습니다. 런타임이 거의 없기 때문입니다. Angular는 Zone.js를 제거한 v21 기준이며, 프레임워크 코어 자체가 크지만 트리 쉐이킹으로 실제 사용하는 코드만 번들에 포함됩니다.

실제 앱 (대형 프로젝트)

앱이 커지면 이야기가 달라집니다. 앱 코드가 전체 번들의 대부분을 차지하게 되면서, 런타임 크기의 영향은 상대적으로 줄어듭니다. 500KB 앱 번들에서 42KB의 React 런타임은 전체의 8%에 불과합니다.

또한 Svelte는 컴포넌트마다 코드를 생성하므로, 대형 앱에서는 React/Vue 대비 앱 코드 자체가 커질 수 있습니다. 결과적으로 대형 앱에서의 번들 사이즈 차이는 수렴 합니다.

DX (개발자 경험) 비교

HMR (Hot Module Replacement)

  • Svelte : 가장 빠름, 컴파일이 단일 파일 단위로 독립적
  • React : 빠름, Fast Refresh가 컴포넌트 단위로 동작
  • Vue : 빠름, SFC 각 블록이 독립적으로 HMR 가능
  • Angular : 느림 (개선 중), AOT 재컴파일이 필요하지만, Angular 21에서 크게 개선

빌드 타임 에러

Angular이 가장 많은 에러를 빌드 시 잡아냅니다. 템플릿 타입 체크까지 수행하므로, 런타임에 발견되는 버그가 적습니다. React는 반대로 대부분의 에러가 런타임에 발생합니다, JSX는 JavaScript이므로 별도의 템플릿 분석이 불가능합니다.

트레이드오프 비교

ReactVueAngularSvelte
빌드 역할변환 (JSX → JS)최적화 (템플릿 힌트)무거운 컴파일 (AOT)전체 컴파일
런타임 크기~44KB~33KB~90KB+~2KB
빈 앱 번들~44KB~33KB~90KB+~3KB
대형 앱 번들차이 줄어듦차이 줄어듦차이 줄어듦차이 줄어듦
HMR빠름빠름느림 (개선 중)매우 빠름
빌드 타임 에러적음보통많음 (템플릿 타입 체크)보통

React는 빌드를 단순하게 유지하고 런타임에 유연성을 확보합니다. Svelte는 빌드에 모든 부담을 지우고 런타임을 극소화합니다. Vue와 Angular는 그 사이 어딘가에서 각자의 균형을 찾았습니다.

어떤 전략이 "더 좋다"는 것은 없습니다. 앱의 규모, 팀의 구조, 성능 요구사항에 따라 최적의 지점이 달라집니다. 작은 위젯이라면 Svelte의 미니멀 번들이 압도적이고, 대형 엔터프라이즈 앱이라면 Angular의 빌드 타임 검증이 가치를 발휘합니다.


마지막 글에서는 프레임워크를 넘어서 , Signals 수렴, Server Components, 컴파일러 혁명 등 프레임워크들이 향하고 있는 공통의 미래를 정리합니다.