TechFeedTechFeed
Frontend

React Compiler 1.0 실전 마이그레이션 가이드 — useMemo·useCallback 없는 React 개발

React Compiler 1.0의 자동 메모이제이션 원리, Next.js 16·Vite 7·Expo SDK 54 설치 방법, 기존 코드베이스 3단계 마이그레이션 전략, 여전히 수동 메모이제이션이 필요한 케이스를 정리했다.

React Compiler 1.0이 2025년 10월 정식 출시됐다. 빌드 타임에 컴포넌트를 분석해 자동으로 메모이제이션을 삽입하는 이 도구는 useMemo·useCallback을 직접 작성하던 패턴을 근본적으로 바꾸고 있다. Next.js 16·Expo SDK 54·Vite 7에서 이미 공식 통합이 완료됐고, Meta 프로덕션에서 측정 가능한 성능 향상이 확인됐다. 이 글은 컴파일러 원리, 프레임워크별 설치, 기존 코드베이스 마이그레이션 전략, 그리고 여전히 수동 메모이제이션이 필요한 예외 케이스를 정리한다.

이 글이 필요한 사람: React 18 이상을 쓰고 있으며 useMemo·useCallback 남용 문제나 성능 최적화 비용을 줄이고 싶은 프론트엔드 개발자.

React Compiler 1.0이란 — 자동 메모이제이션의 원리

React Compiler 1.0 자동 메모이제이션 다이어그램
React Compiler는 빌드 타임에 컴포넌트를 분석하고 최적화를 자동 삽입한다

React Compiler는 빌드 타임(ahead-of-time) 컴파일러다. 소스 코드를 분석해 어떤 값이 렌더링 간에 변하지 않는지 추론하고, 필요한 위치에 메모이제이션을 자동 삽입한다. 개발자가 직접 useMemo·useCallback을 쓰지 않아도 컴파일러가 최적화된 코드를 생성한다.

핵심 원리는 의존성 추적(dependency tracking)이다. 컴파일러는 각 컴포넌트와 훅의 입력값(props, state, context)을 정적으로 분석하고, 출력값이 입력에만 의존하는지 판단한다. 순수 함수로 판단되면 자동으로 캐싱 로직을 삽입한다.

  • 분석 범위: 컴포넌트, 커스텀 훅, 일반 JavaScript 함수 내부 계산
  • 적용 기준: 렌더링 간 참조 동일성(referential equality) 유지가 필요한 값
  • 제외 케이스: 사이드이펙트가 있거나 외부 상태를 mutate하는 경우
  • 안전장치: 컴파일러가 불확실한 경우에는 최적화를 건너뜀 — 오히려 느려지는 경우는 발생하지 않는다

Meta는 React Compiler를 Instagram, WhatsApp Web 프로덕션에서 수개월 운영한 뒤 v1.0을 출시했다. 공식 블로그에 따르면 인터랙션 지연(interaction latency)이 측정 가능하게 감소했다.

useMemo·useCallback이 사라지는 이유 — Before / After 비교

기존에는 불필요한 리렌더링을 막기 위해 개발자가 직접 메모이제이션 훅을 삽입해야 했다. 이 과정은 의존성 배열 관리 실수, 과도한 최적화, 코드 가독성 저하 문제를 유발했다.

Before (React Compiler 없이)
// 기존 패턴 — 개발자가 직접 useMemo/useCallback 작성 import { useMemo, useCallback } from 'react' function ProductList({ products, onSelect }) { // 매번 새 배열 생성 방지 const sorted = useMemo(() => { return [...products].sort((a, b) => a.price - b.price) }, [products]) // 매번 새 함수 생성 방지 const handleSelect = useCallback((id) => { onSelect(id) }, [onSelect]) return sorted.map(p => ( <ProductCard key={p.id} product={p} onSelect={handleSelect} /> )) }
After (React Compiler 적용)
// React Compiler 이후 — 컴파일러가 자동으로 메모이제이션 삽입 // import useMemo, useCallback 불필요 function ProductList({ products, onSelect }) { // 컴파일러가 빌드 타임에 자동 캐싱 삽입 const sorted = [...products].sort((a, b) => a.price - b.price) const handleSelect = (id) => { onSelect(id) } return sorted.map(p => ( <ProductCard key={p.id} product={p} onSelect={handleSelect} /> )) } // 빌드 결과물은 최적화된 캐싱 로직 포함 — 런타임 동작은 동일

프레임워크별 설치 — Next.js 16 · Vite 7 · Expo SDK 54

2026년 기준 주요 프레임워크는 모두 React Compiler 공식 통합을 완료했다. 새 프로젝트라면 기본 활성화된 경우도 있다.

  • Next.js 16: next.config.js에 설정 한 줄로 활성화. 새 프로젝트는 기본값으로 켜져 있다
  • Expo SDK 54: 기본 활성화. 별도 설정 없이 바로 컴파일러 혜택을 받는다
  • Vite 7: babel-plugin-react-compiler 설치 후 vite.config.ts 수정
  • Webpack/CRA 기반: babel 플러그인 방식으로 수동 통합 필요
Next.js 16 — next.config.js
// next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { experimental: { reactCompiler: true // Next.js 16에서는 stable로 이동 } } module.exports = nextConfig
Vite 7 — 설치 & vite.config.ts
# 패키지 설치 npm install --save-dev babel-plugin-react-compiler # vite.config.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [ react({ babel: { plugins: ['babel-plugin-react-compiler'] } }) ] })

기존 코드베이스 마이그레이션 전략 — 3단계 접근법

React Compiler 마이그레이션 단계별 전략
기존 코드베이스에 점진적으로 React Compiler를 도입하는 3단계 접근법

수천 개 컴포넌트가 있는 코드베이스라면 한꺼번에 useMemo/useCallback을 제거하려다 리그레션이 생길 수 있다. React 팀이 권장하는 3단계 전략을 따르면 안전하게 마이그레이션할 수 있다.

  1. 1단계 — 컴파일러 활성화 (기존 훅 유지): 컴파일러를 켜되 기존 useMemo/useCallback은 그대로 둔다. 컴파일러는 기존 수동 최적화를 존중하면서 추가 최적화만 삽입한다. 이 상태에서 전체 테스트를 실행해 리그레션 여부를 확인한다.
  2. 2단계 — eslint-plugin-react-compiler 진단 적용: 린터 플러그인을 통해 컴파일러가 최적화를 건너뛴 컴포넌트와 그 이유를 파악한다. 컴파일러가 처리할 수 없는 패턴(props mutation, use after free 등)을 수정한다.
  3. 3단계 — 불필요한 훅 제거 (codemod 활용): 검증이 완료된 후 npx react-codemod remove-usememo 형태의 codemod로 자동 제거한다. 컴파일러가 이미 커버하는 경우에만 제거되므로 안전하다.

팀 내 코드 리뷰 기준도 업데이트해야 한다. 신규 코드에서는 useMemo/useCallback을 기본적으로 사용하지 않는 방향으로 가이드라인을 바꾸되, 아래에서 설명하는 예외 케이스는 여전히 허용한다.

수동 메모이제이션이 여전히 필요한 케이스

React Compiler가 만능은 아니다. 다음 케이스에서는 여전히 useMemo·useCallback을 명시적으로 사용해야 한다.

  • Interior mutability가 있는 라이브러리: react-hook-formwatch(), Zustand 셀렉터처럼 라이브러리 내부에서 객체를 변이(mutate)하는 경우
  • 비React 시스템과의 동기화: DOM 이벤트 리스너, WebSocket 핸들러, 서드파티 차트 라이브러리처럼 React 밖의 시스템에 콜백을 전달할 때
  • 정밀한 성능 제어가 필요한 경우: 게임 루프, 대용량 데이터 가공처럼 컴파일러 휴리스틱보다 더 세밀한 캐싱 제어가 필요한 경우
  • 컴파일러가 분석 불가한 동적 패턴: eval, 동적 import(), 외부 스크립트로 삽입된 값을 사용하는 컴포넌트
⚠️ react-hook-form 주의: watch()가 반환하는 객체는 매 렌더마다 새 참조를 반환한다. 컴파일러가 안전하게 분석할 수 없어 최적화를 건너뛴다. 이 경우 useCallback으로 핸들러를 감싸는 기존 패턴을 유지해야 한다.

도입 후 자주 발생하는 문제 — 원인과 해결

React Compiler를 활성화한 후 기존 테스트가 실패하거나 콘솔에 경고가 표시되는 경우가 있다. 주요 케이스별 원인과 해결 방법이다.

  • Props mutation 에러: props.items.push(...)처럼 props를 직접 변이하는 코드가 있는 경우. 컴파일러가 이를 감지하고 eslint-plugin-react-compiler로 경고를 출력한다. 해결: 불변성 패턴으로 리팩터링 필요
  • ref 접근 타이밍 오류: 렌더 단계에서 ref.current를 읽는 패턴. 컴파일러는 이를 사이드이펙트로 간주하고 최적화를 건너뛴다. 해결: ref 접근을 useEffect 내부로 이동
  • 컴파일러가 특정 파일을 건너뜀: // @skip-react-compiler 주석 또는 컴파일러가 처리 불가한 패턴 존재. 건너뛴 파일 목록은 빌드 로그에서 확인 가능
  • 성능이 오히려 나빠진 경우: 드물지만 컴파일러 오버헤드가 이득보다 큰 작은 컴포넌트에서 발생. eslint-plugin-react-compiler 진단으로 특정 컴포넌트에 'use no memo' 지시어를 붙여 제외
eslint-plugin-react-compiler 설치 확인: npm install eslint-plugin-react-compiler --save-dev.eslintrc.jsplugins: ["react-compiler"]rules: { "react-compiler/react-compiler": "error" }를 추가한다. 컴파일러가 처리할 수 없는 패턴을 빌드 전에 미리 감지한다.
ReactReact CompileruseMemouseCallback자동 메모이제이션Next.jsViteExpo프론트엔드 최적화

관련 도구

관련 포스트

Vercel AI SDK 6 완전 가이드 — 에이전트 1급 추상화, MCP 풀 지원, DevTools2026-04-17Next.js App Router 마이그레이션 완벽 가이드 — Pages Router에서 전환하기2026-03-17Svelte 5 vs React 19 — 2026년 프론트엔드 프레임워크 비교2026-03-13Remix vs Next.js — 풀스택 프레임워크 비교 20262026-03-14