React Compiler의 한계 — 뭘 최적화하고 뭘 못 하는가

6 min read
ReactReact Compiler성능메모이제이션프레임워크
React Compiler의 한계 — 뭘 최적화하고 뭘 못 하는가

React Compiler는 React를 대체하지 않는다

React Compiler가 나왔을 때 많은 사람이 기대한 건 React의 렌더링 모델 자체가 바뀌는 것이었다. useMemouseCallback을 수동으로 쓸 필요가 없어진다는 건 맞다. 그런데 "그게 Signal 기반 프레임워크처럼 세밀한 업데이트를 한다는 뜻인가"라고 묻는다면, 아니다.

컴파일러는 React의 기존 계약 안에서 동작한다. 규칙을 바꾸는 게 아니라, 그 규칙을 지키면서 불필요한 재실행을 줄인다.


실제로 하는 일

React Compiler는 세 가지를 한다.

1. 자동 의존성 추론

src/components/UserCard.tsx
// 컴파일 전: 수동 메모이제이션
function UserCard({ user, onClick }) {
  const fullName = useMemo(
    () => `${user.firstName} ${user.lastName}`,
    [user.firstName, user.lastName]
  )
  const handleClick = useCallback(() => onClick(user.id), [onClick, user.id])
  return <div onClick={handleClick}>{fullName}</div>
}
 
// 컴파일 후: 컴파일러가 처리
function UserCard({ user, onClick }) {
  const fullName = `${user.firstName} ${user.lastName}`
  const handleClick = () => onClick(user.id)
  return <div onClick={handleClick}>{fullName}</div>
}

코드에서 useMemouseCallback을 지워도 된다. 컴파일러가 어떤 값이 언제 바뀌는지 추론해서 캐싱을 삽입한다.

[💡 잠깐! 이 용어는?] 메모이제이션(Memoization): 동일한 입력에 대해 이전 결과를 재사용하는 최적화. React에서는 불필요한 컴포넌트 재렌더링과 함수 재생성을 막는 데 쓴다.

2. JSX 서브트리 재사용

동일한 props가 들어오면 이미 계산한 JSX 결과를 재사용한다. 이전에는 React.memo()로 컴포넌트를 직접 감싸야 했던 것을 컴파일러가 자동화한다.

3. 성능 바닥을 높인다

최악의 케이스(모든 게 매번 새로 계산되는 상황)를 없애준다. 처음부터 잘 짠 코드보다는 못하더라도, 최적화를 빠뜨린 코드의 성능 하한선이 올라간다.


못 하는 것들

기대실제
Signal 기반 세밀한 DOM 업데이트컴포넌트/서브트리 단위 업데이트 유지
Hook 실행 순서 변경Fiber/Hook 계약 그대로 유지
스케줄링 방식 변경Concurrent 스케줄링 모델 변경 없음
아키텍처 문제 해결구조적 최적화는 여전히 개발자 몫

컴파일러는 "재실행을 줄이는" 것이지 "다른 방식으로 실행"하는 게 아니다. React의 기본 동작인 "컴포넌트를 리렌더링하고 이전 결과와 비교"하는 흐름은 바뀌지 않는다.

[💡 잠깐! 이 용어는?] Fiber 스케줄러: React 16부터 도입된 렌더링 엔진. 작업을 잘게 쪼개 우선순위에 따라 처리하는 구조. Concurrent Mode의 기반이 되며, Compiler는 이 계약 밖에서 동작하지 않는다.


Compiler-First 프레임워크와 비교

Svelte, SolidJS 같은 컴파일러 우선 프레임워크는 런타임 자체가 다르다.

차원React + CompilerCompiler-First (Svelte/Solid)
업데이트 단위컴포넌트/서브트리소스 레벨 의존성 전파
메커니즘자동 메모이제이션빌드타임 의존성 그래프
마이그레이션 비용낮음-중간중간-높음
런타임 번들 크기일반적으로 큼작음

비유하면 React Compiler는 기존 고속도로에 자동 크루즈 컨트롤을 추가한 것이다. Svelte는 아예 다른 방식으로 설계된 도로다. 더 빠를 수 있지만 진입로가 다르다.

포인트: "두 접근법은 서로 다른 비용을 최적화한다. 모더니티 경쟁이 아니라 목적 함수 선택이다."


언제 React Compiler가 효과적인가

  • 기존 React 코드베이스를 그대로 유지하면서 성능을 높이고 싶을 때
  • useMemo/useCallback 남발로 코드가 복잡해진 팀
  • 컴포넌트 트리가 깊고 불필요한 리렌더링이 프로파일러에서 확인될 때

반대로 아키텍처 자체가 문제인 경우 — N+1 렌더링, 너무 큰 Context 범위, 깊은 prop drilling — 은 컴파일러로 해결되지 않는다. 구조는 여전히 개발자가 잡아야 한다.


마무리

React Compiler는 "React 프로그래밍 모델을 유지하면서 성능 바닥을 높이는 도구"다. 모든 프레임워크를 대체하는 만능 최적화가 아니다. 정확한 기대치를 갖고 쓰면 실무에서 유용하다 — 특히 수동 메모이제이션 코드를 정리하고 싶은 팀에게는 즉각적인 코드 정리 효과가 있다.


참고:

관심 있을 만한 포스트

Tsonic — TypeScript를 네이티브 바이너리로 컴파일하는 실험

TypeScript → C# → NativeAOT 파이프라인으로 네이티브 실행 파일을 만드는 Tsonic. 어떻게 동작하고, 어떤 한계가 있는지 살펴봤다.

TypeScriptNativeAOT

Bun이 빠른 건 맞다 — 그런데 당신의 이벤트 루프가 문제다

Bun으로 바꿔도 p99가 개선되지 않는 이유. 런타임 선택보다 먼저 봐야 할 진짜 병목 지점들.

BunNode.js

Coaction v1.0 — Web Worker로 멀티스레딩 상태 관리하기

JavaScript 단일 스레드 한계를 극복하는 상태 관리 라이브러리 Coaction의 동작 방식, Zustand와의 차이, Standard/Shared 두 가지 모드 사용법을 정리한다.

CoactionWeb Worker

LCP 28초짜리 React 앱을 1초로 깎아낸 기록 — 4단계 성능 수술 프레임워크

번들 분석부터 에셋 최적화까지, React 앱의 LCP를 단계적으로 개선하는 실전 프레임워크를 다룬다.

React성능 최적화

Next.js 블로그 만들기 — 카드 그리드와 포스트 상세 페이지

Velog 스타일 카드 UI와 MDX 렌더링 상세 페이지 구현. 반응형 그리드, SEO 메타데이터, 정적 사이트 생성까지.

Next.jsReact

AI 코딩의 맹점 — Artifacts 없이 에이전트는 기억을 잃는다

PRD, ADR, TDD가 AI 코딩 워크플로우에서 왜 선택이 아닌 필수인지, 실전 구조와 함께 살펴본다.

AI 코딩Artifacts

Next-Translate 3.0 — Turbopack과 App Router를 위한 i18n 재건

1년간 공백 후 돌아온 Next-Translate 3.0이 Turbopack 지원, 비동기 params, App Router 안정화를 한 번에 처리하는 방법.

Next.jsi18n

V8 WasmGC 투기적 최적화 — 가상 메서드를 인라인으로 만드는 법

V8이 WasmGC의 가상 메서드 디스패치에 투기적 인라이닝을 도입해 Dart와 Java 앱에서 최대 8% 성능을 끌어낸 방법.

V8WebAssembly