리액트로 개발하다 보면 자주 마주치는 순간이 있을 것이다. 바로 특정 컴포넌트의 상태만 변경되었는데, 다른 컴포넌트까지 리렌더링되는 순간이다. 처음에는 대수롭지 않게 넘어가지만, 컴포넌트가 많아지면 이 렌더링이 점점 신경 쓰이기 시작한다. 이 글에서는 테코톡 주제였던 React Compiler에 대해서 소개를 해볼 것이다.
React Compiler는 React 19와 같이 등장하였다. 그러다 보니 존재조차 모르던 사람들이 많았다. 나 또한 React Digest라는 리액트 관련 뉴스레터를 구독해왔는데 이메일로 날아온 관련 글을 보고 알게 되었다.
본론으로 들어가기에 앞서, 최적화를 왜 해야할까?
카드 정보를 입력하는 간단한 페이지이다. 카드 번호를 입력하고 있지만 유효기간과 CVC 컴포넌트까지 함께 리렌더링되는 것을 볼 수 있다. React에서는 상태가 변경되면 해당 컴포넌트가 다시 렌더링되고, 자식 컴포넌트들도 다시 렌더링된다.
결국 불필요한 리렌더링이 성능 문제로 이어지기 때문에 최적화를 해야한다.
React에서 최적화를 위해 사용하는 방식에는 대표적으로 세 가지가 있다.
바로 useMemo, useCallback, React.memo이다.
useMemo는 계산 결과를 기억하는 도구이다.
const memoizedValue = useMemo(() => {
return expensiveCalculation(a, b);
}, [a, b]);
이 코드에서 a 또는 b가 변경될 때에만 expensiveCalculation 이라는 무거운 계산 함수가 실행된다.
useCallback은 값이 아닌 함수 참조를 유지한다.
const handleClick = useCallback(() => {
console.log(count);
}, [count]);
// 자식 컴포넌트에 Props로 전달
<ChildComponent onClick={handleClick} />
함수는 리렌더링되면 새로 만들어지는데, 이때 참조값은 유지되지 않는다. 자식 컴포넌트 입장에서는 props로 받는 함수가 리렌더링마다 바뀐 것처럼 보인다.
따라서 useCallback을 사용해 함수 참조를 유지한다.
React.memo는 컴포넌트를 메모이제이션한다.
const MyComponent = React.memo((props) => {
// Props가 변경될 때만 렌더링
return <div>{props.data}</div>;
});
부모가 다시 렌더링되어도 props가 같다면, 이 컴포넌트는 다시 실행하지 않고 이전 결과를 재사용한다.
그냥 기존처럼 최적화를 하면 되지 않을까?라고 생각할 수 있는데, 기존 방식에는 문제점들이 존재한다.
"테스트 코드도 비용이다."라는 말을 들어본 적이 있을 것이다. 이와 같이 메모이제이션 자체에도 비용이 있다." 그래서 모든 곳에 적용하면 메모이제이션 여부를 판단하기 위해 매번 실행되는 비교 연산을 하게 되면서 오히려 성능이 떨어질 수 있다.
useMemo나 useCallback은 의존성 배열을 정확히 작성해야 한다.
const memoizedValue = useMemo(() => {
return calculate(data, filter);
}, [data]);
이처럼 filter를 의존성 배열에서 누락시킨 경우 filter의 값이 바뀌어도 이전 결과를 재사용한다. 그냥 filter를 의존성 배열에 넣으면 해결된다고 생각할 수 있는데, 이런 버그를 바로 알아차릴 수가 없어 원인 파악에 많은 시간이 소요된다.
React는 선언형 라이브러리이다. 선언형이라는 건 "이 상태에서는 이런 UI가 보여야 한다."라고 결과를 선언하는 방식이다. 개발자는 화면에 어떻게 보여줄지만 작성하면, DOM을 바꾸고 화면에 그리는 것은 React가 처리한다.
하지만, 최적화는 선언형이 아닌 명령형이다.
"이 값은 기억해줘", "이 함수는 새로 만들지 마"처럼 개발자가 명령하게 된다. 즉 UI는 선언적으로 작성하지만, 성능 최적화는 명령형으로 관리하는 모순이 생긴 것이다.
2025년 10월 React Compiler v1.0이 등장하였다. 2024년에 베타 버전으로 이미 출시가 되었지만 정식으로 등장한 것은 2025년 10월이다.
React Compiler를 사용하면 개발자가 매번 수동으로 memo를 붙일 필요가 없어진다. 컴파일러가 빌드 과정에서 코드를 읽고, 재사용 가능한 부분을 자동으로 최적화해준다.
앞에서 본 카드 정보 입력 페이지이다. 똑같은 코드에 React Compiler만 적용해 보았다.
카드 번호 컴포넌트만 리렌더링되는 것을 볼 수 있다.
React Compiler의 동작 흐름은 크게 3단계이다.
코드 분석, 자동 메모이제이션, 리렌더링 건너뛰기이다.

컴파일러는 빌드 과정에서 코드를 읽는다. 이때 코드를 실제로 실행해보는 것이 아니라, 코드를 읽고 구조와 의존 관계를 파악한다.
코드를 파싱한 다음, 데이터의 흐름을 추적하고 최적화를 판단한다.

왼쪽처럼 직접 useMemo, useCallback을 붙이지 않고 오른쪽처럼 평범한 코드를 작성할 수 있다. 하지만 이 값이 어떤 props/state/변수에 의존하고, 그 의존성이 바뀌지 않았을 때 재사용해도 안전한가를 compiler가 판단해 입력값이 바뀌지 않은 계산을 재사용할 수 있다.
다시 한 번 말하지만 부모가 다시 렌더링된다고 해서 항상 자식까지 다시 렌더링될 필요는 없다. React Compiler는 부모가 리렌더링되어도 변경되지 않은 값과 함수를 재사용하여 자식의 불필요한 실행을 차단한다.
그렇다면 React Compiler가 모든 코드를 자동으로 최적화해줄까?
React Compiler가 모든 코드를 무조건 최적화해주는 것은 아니다.
코드를 안전하게 최적화하려면, 코드가 React의 규칙을 잘 지키고 있어야 한다.
쉽게 말하면, Compiler가 이해할 수 있는 코드여야 최적화가 안전하게 적용된다.

Hook은 조건문 안에서 호출하면 안된다. Hook은 렌더링마다 같은 순서로 호출되어야 React가 상태를 올바르게 연결할 수 있다.

props는 직접 변경하면 안된다. React Compiler는 데이터가 불변한다는 전제하에 최적화를 수행한다. props나 state를 직접 변경하면, 컴파일러가 변화를 감지하지 못하거나 예측 불가능한 버그가 발생할 수 있다.
따라서 변경이 필요하다면 파생값을 만들어서 전달해야 한다.

React 컴포넌트는 렌더링 중에 사이드 이펙트를 만들지 않는 것이 중요하다. 렌더링 중에 외부 변수를 변경하거나, API를 요청하는 코드는 컴파일러가 최적화를 포기한다.
function RandomComponent(){
const id = Math.random();
const now = Date.now();
return <div>{id} - {now}</div>;
}
Math.random()이나 Date.now()처럼 실행할 때마다 달라지는 값을 주의해야 한다. 이렇게 실행 시점마다 결과가 달라지는 함수는 메모이제이션 대상에서 제외된다. 안전이 보장되지 않으면 컴파일러는 최적화를 포기하고 기존 방식으로 동작한다.
즉 React Compiler는 수동 메모이제이션을 모두 없애는 도구라기보다, 개발자가 반복적으로 하던 최적화 판단을 상당 부분 줄여주는 도구로 이해하면 된다.
기존 프로젝트에 React Compiler를 적용할 수 있을까?
가능하지만 점진적으로 적용하는 방식이 안전하다. 점진적으로 적용하는 대표적인 2가지 방법을 소개해보겠다.
자세한 설치 및 적용 방법은 공식문서를 참고하길 바란다.

이처럼 babel.config 파일에 설정을 하면 특정 디렉토리만 적용하도록 할 수 있다.

컴파일러 옵션이 컴포넌트 단위일 때, 컴포넌트 선언 상단에 "use memo";를 작성하면 최적화를 원하는 대상에만 명시적으로 선언할 수 있다. 이 방식은 전역 설정의 위험 부담 없이, 성능 병목이 발생하는 특정 컴포넌트부터 안전하게 적용할 수 있다는 장점이 있다.
대규모 코드베이스에서는 리스크를 최소화하며 한 페이지씩 최적화를 확장해나가는 방식을 권장하고 있다.
컴파일러라고 해서 기본적으로 내장되어 있는 줄 아는 크루들이 실제로 많았다. React 19를 사용한다고 해서 컴파일러가 자동으로 켜지는 것이 아니라 패키지 설치와 빌드 도구 연결은 필수이다.
정리해보자면 예전에는 어디에 useMemo를 넣을지 이 함수는 useCallback으로 감싸야 할지를 고민했다면, 앞으로는 컴파일러가 이해할 수 있을 만큼 순수하고 예측 가능한 React 코드를 작성했는지가 더 중요해질 수 있다.
결국 React Compiler는 개발자가 반복적인 최적화 판단에서 벗어나 더 React답게 코드를 작성하도록 도와주는 도구이다.
Meta의 실제 프로덕션 환경을 통해 충분히 테스트를 거친 후 정식으로 출시되었기 때문에 써보는 것을 공식문서에서도 권장한다. 이 글을 읽고 한 번은 사용해봤으면 한다.
리액트로 프로젝트를 만들 때 늘 최적화가 어려웠는데 덕분에 좋은 도구 알고갑니다 !! ( *• ̀ω•́ )b