Split View: React Compiler가 Rust로 포팅됐다 — 머지된 것, 아직 아닌 것, 그리고 그 "10배"라는 숫자
React Compiler가 Rust로 포팅됐다 — 머지된 것, 아직 아닌 것, 그리고 그 "10배"라는 숫자
- 들어가며 — 컴파일러는 이미 1.0인데, 왜 다시 포팅인가
- 무엇이 머지됐나 — 사실관계부터
- '3배'와 '10배' — 저자 본인이 붙인 경고문
- "아키텍처는 사람이, 코드는 대부분 AI가"
- 진짜 볼 것은 속도가 아니라 검증 방식
- 아키텍처 — 같은 알고리즘, 다른 메모리 모델
- 머지 이후 한 달 — 계획이 바뀐 곳, 여전히 새는 곳
- 지금 쓸 수 있나 — npm이 답한다
- 컴파일러가 여전히 무는 곳 — 메모이제이션 안전성
- Meta의 12%와 2.5배는 무엇을 잰 숫자인가
- 마치며 — 지금 당신이 할 일
- 참고 자료
들어가며 — 컴파일러는 이미 1.0인데, 왜 다시 포팅인가
React Compiler는 이미 끝난 이야기처럼 보였습니다. React Compiler v1.0이 2025년 10월 7일에 나왔고, useMemo와 useCallback을 손으로 붙이던 시대를 정리하겠다는 약속이 안정 버전 딱지를 달았으니까요.
그런데 2026년 6월 9일, PR #36173 — [compiler] Port React Compiler to Rust가 facebook/react main에 머지됐습니다. 컴파일러 전체를 TypeScript에서 Rust로 다시 쓴 것입니다. 461개 파일, 123,289줄 추가. 저자는 React 팀의 Joseph Savona(@josephsavona)입니다.
그리고 며칠 지나지 않아 "React Compiler가 Rust로 10배 빨라졌다" 는 문장이 생태계를 돌기 시작했습니다. 이 글은 그 문장을 1차 출처로 되짚는 작업입니다. 결론부터 말하면, 머지된 것은 사실이고, 숫자는 사실이라고 부르기 어렵습니다 — 저자 본인이 그렇게 써 뒀기 때문입니다.
무엇이 머지됐나 — 사실관계부터
PR 본문 첫 문장이 전부 말해 줍니다.
This is an experimental, work-in-progress port of React Compiler to Rust. (이것은 React Compiler를 Rust로 옮긴 실험적이고 작업 중인 포팅입니다.)
이어지는 항목들도 톤이 같습니다.
- 작업 중. "we are sharing early, prior to testing internally at Meta, to get feedback from partners in parallel with continued development" — Meta 내부 테스트에 들어가기도 전에, 파트너 피드백을 받으려고 일찍 공개했다는 뜻입니다.
- 빌드 없음. "No builds available yet, you'll have to do some hacking if you want to try this." 써 보려면 직접 손을 봐야 합니다.
- 알려진 구멍은 없지만 숨은 버그는 있을 수 있음. "All fixtures pass, no known gaps but there may be lurking bugs."
타임라인은 2026년 3월 30일 오픈, 6월 9일 머지입니다. 두 달 반 걸렸습니다.
그리고 짚어 둘 게 하나 더 있습니다 — 이게 첫 시도가 아닙니다. 저장소 이력을 보면 2025년 1월 24일에 [compiler][be] Remove unused experimental Rust port라는 커밋이 있습니다. Rust 포팅은 한 번 시도됐다가 쓰이지 않은 채로 지워진 적이 있습니다. 이번 건 두 번째 판입니다.
'3배'와 '10배' — 저자 본인이 붙인 경고문
이제 문제의 숫자입니다. PR 본문에서 성능을 다룬 항목 전체를 그대로 옮기면 이렇습니다.
Early performance numbers are derived from AI and i haven't spent much time validating the benchmark setup, beyond the fact that the optimization opportunities it discovered made complete sense and the fixes were right. With that caveat, it does appear that the Rust version is quite fast already: 3x faster when operating as a Babel plugin. The serialization cost is quite high, but the actual transformation logic is ~10x faster, so it's net faster. Native integrations (oxc, swc) should be even faster.
번역하면 — "초기 성능 수치는 AI가 낸 것이고, 벤치마크 설정을 검증하는 데 시간을 별로 쓰지 않았다. 다만 AI가 찾아낸 최적화 지점들이 완전히 말이 됐고 수정도 맞았다는 정도까지는 확인했다. 그 단서를 달고 말하자면, Rust 버전은 이미 꽤 빨라 보인다 — Babel 플러그인으로 동작할 때 3배 빠르다. 직렬화 비용이 꽤 크지만 실제 변환 로직은 약 10배 빠르므로, 순증으로는 더 빠르다. 네이티브 통합(oxc, swc)은 더 빨라야 한다."
여기서 정확히 무슨 일이 벌어지고 있는지 뜯어봅시다.
첫째, 숫자의 출처가 AI입니다. 저자가 벤치마크를 짜고 돌려서 얻은 게 아니라 AI가 낸 수치이고, 저자는 "벤치마크 설정을 검증하는 데 시간을 별로 쓰지 않았다"고 명시했습니다. 그가 검증했다고 말한 것은 성능 수치가 아니라 AI가 찾아낸 최적화 지점이 타당했다는 점입니다. 이건 전혀 다른 주장입니다.
둘째, 3배와 10배는 같은 것을 잰 숫자가 아닙니다. 3배는 Babel 플러그인으로 동작하는 엔드투엔드 수치이고, 10배는 직렬화 비용을 뺀 변환 로직만의 수치입니다. "10배 빨라졌다"고 옮기는 순간 직렬화 비용이 사라지는데, 저자는 그 비용이 "꽤 크다(quite high)"고 같은 문장에서 말하고 있습니다. 실제로 당신 빌드에 닿는 숫자는 10배 쪽이 아니라 3배 쪽입니다.
셋째, oxc/swc가 더 빠르다는 건 측정이 아니라 예상입니다. 원문은 "should be even faster" — 조동사가 붙어 있습니다. 이걸 "더 빠르다"로 옮기면 안 됩니다.
넷째, 그 3배마저 이미 낡았습니다. 이건 좀 아이러니한데, 뒤에서 보겠습니다.
정리하면 이 숫자들은 실험적 WIP 브랜치에서, AI가 산출했고, 저자가 검증하지 않았다고 밝힌 수치입니다. 인용할 수 없다는 뜻이 아니라, 인용할 때 그 조건을 같이 들고 다녀야 한다는 뜻입니다. 이 생태계는 조건 없는 숫자를 민담으로 만드는 데 특기가 있습니다.
"아키텍처는 사람이, 코드는 대부분 AI가"
PR에서 숫자만큼 회자된 항목이 이겁니다.
The architecture was heavily guided by humans (me, @josephsavona) but majority coded by AI.
"대부분 AI가 코딩했다"만 떼면 오해가 생깁니다. 바로 다음 문장이 범위를 좁혀 줍니다.
I was very hands-on in setting the architecture, the testing and verification strategy, incremental migration approach, etc. I also kept a close eye on the code and spent a decent amount of time going back and forth to get code quality to a decent level.
즉 사람이 잡은 것은 아키텍처, 테스트·검증 전략, 점진적 마이그레이션 방식이고, 코드 품질도 "여러 번 왔다 갔다 하며" 끌어올렸다고 합니다. AI가 채운 것은 그 틀 안의 구현입니다.
그리고 이 작업이 AI에게 유리한 조건이었다는 점도 봐야 합니다. 이건 백지에서 컴파일러를 설계한 게 아니라, 정답지가 이미 있는 포팅입니다. TypeScript 구현이 레퍼런스로 존재하고, 패스 단위로 1:1 대응이 되고(저자 표현으로 "It's very much a pass-by-pass port"), 출력이 맞는지 기계적으로 대조할 수 있습니다. 명세가 실행 가능한 형태로 이미 존재하는 문제였습니다. 여기서 나온 결과를 "AI가 컴파일러를 짤 수 있다"로 일반화하는 건 과합니다.
진짜 볼 것은 속도가 아니라 검증 방식
개인적으로 이 PR에서 제일 배울 게 많은 부분은 10배가 아니라 여깁니다. 정답지가 있는 포팅을 AI에게 시킬 때 어떻게 신뢰를 만드는가.
1단계 — 출력 대조.
all 1725 fixtures pass in snap when comparing the temporary rust version of the plugin with the main version. this compares generated code output as well as errors.
1,725개 픽스처에 대해 Rust 버전과 기존 버전의 생성 코드와 에러를 대조합니다. 여기까지는 흔한 골든 테스트입니다.
2단계 — 패스 단위 내부 상태 대조. 이게 핵심입니다.
all fixtures also pass a full comparison of the per-pass compiler intermediate representation — the intermediate state (including log events and errors) are ~identical after every single pass (modulo some normalization of ids)
최종 출력만 맞추면 "우연히 같은 답"이 통과합니다. 그래서 모든 패스가 끝날 때마다 중간 표현(IR) 전체를 로그 이벤트와 에러까지 포함해 대조합니다. 저자가 이 목적으로 만든 스크립트가 compiler/script/test-rust-port.sh이고, PR 설명에 따르면 이건 임의의 JS 디렉터리를 향해 겨눌 수 있어서 Meta 내부 테스트에 쓸 계획이라고 합니다.
교훈은 이렇게 요약됩니다 — AI에게 대규모 포팅을 시킬 때 만들어야 하는 건 코드가 아니라 대조 장치입니다. "출력이 같다"가 아니라 "모든 중간 단계가 같다"를 기계가 확인할 수 있어야, 12만 줄을 사람이 다 읽지 않고도 신뢰할 수 있습니다. 저자가 "hands-on"이었다고 말한 대상에 "testing and verification strategy"가 들어 있는 게 우연이 아닙니다.
다만 여기에도 정직한 구멍이 있습니다.
The OXC and SWC example integrations seem to be working well, though i haven't manually verified this to the same extent as i have the Babel integration.
Babel 통합만큼 수동 검증하지는 않았다고 합니다.
아키텍처 — 같은 알고리즘, 다른 메모리 모델
내부 구조는 의외로 보수적입니다. PR 설명 그대로, Rust 버전은 TypeScript 버전과 같은 아키텍처입니다.
AST -> HIR (High-level Intermediate Representation)
- CFG (control-flow graph)
- SSA (single-static assignment)
|
+-> 동일한 패스들, 동일한 알고리즘
|
v
출력 AST
저장소의 compiler/crates를 열어 보면 이 구조가 그대로 크레이트로 갈라져 있습니다 — react_compiler_hir, react_compiler_ssa, react_compiler_lowering, react_compiler_optimization, react_compiler_reactive_scopes, react_compiler_typeinference, react_compiler_validation 등입니다.
달라진 건 알고리즘이 아니라 데이터 표현입니다.
The main differences are in the data representation - using arena-like structures (and indices into these arenas) to work within Rust's borrowing system.
TypeScript에서 객체 참조로 얽어 두던 그래프를 Rust에서는 그대로 옮길 수 없습니다. 빌림 검사기(borrow checker)가 순환 참조와 가변 별칭을 허용하지 않으니까요. 그래서 아레나에 노드를 몰아 넣고 인덱스로 참조합니다. CFG와 SSA처럼 노드가 서로를 가리키는 구조를 Rust로 옮길 때 사실상 표준이 된 패턴입니다.
공개 API도 특이합니다.
The public API is basically "Rust Babel AST" + Scope Info in, Rust Babel AST out.
Babel AST의 Rust 표현을 공용 인터페이스로 삼고, 각 통합(Babel, OXC, SWC)이 자기 표현으로 변환해 들어오고 나갑니다. 이유는 현실적입니다 — "the conversion from any AST into our HIR is complex, and we can only maintain one version"(어떤 AST든 HIR로 바꾸는 건 복잡하고, 우리는 한 벌만 유지할 수 있다). 게다가 지금은 스코프 정보를 통합 쪽에서 직렬화해 넘겨줘야 합니다. TypeScript 버전이 Babel의 스코프 분석에 얹혀 있었기 때문입니다. 자체 스코프 해석을 구현할 계획이라고 밝혀 뒀습니다.
여기서 앞의 "직렬화 비용이 꽤 크다"가 설명됩니다. Babel AST를 Rust로 넘기고 되받는 왕복, 거기에 스코프 그래프까지 직렬화하는 비용이 변환 로직의 10배 이득을 3배로 깎아먹고 있었던 것입니다.
머지 이후 한 달 — 계획이 바뀐 곳, 여전히 새는 곳
여기서부터가 PR 본문만 읽으면 놓치는 부분입니다. 커밋 이력이 훨씬 정직합니다.
직렬화 비용을 바로 때리기 시작했습니다. 머지 이틀 뒤인 6월 11일에 [rust-compiler] Return the compiled AST by value instead of JSON(#36729)과 [rust-compiler] Carry uninspected AST subtrees as raw JSON text(#36730)가 들어갔습니다. 컴파일된 AST를 JSON이 아니라 값으로 반환하고, 들여다볼 필요 없는 AST 서브트리는 원시 JSON 텍스트로 그냥 들고 다니는 겁니다. 앞서 "3배가 이미 낡았다"고 한 게 이 얘기입니다 — 그 숫자를 만든 코드는 머지 직후부터 바뀌기 시작했습니다. 지금 3배가 몇 배인지는 아무도 공개하지 않았습니다.
OXC/SWC 계획은 뒤집혔습니다. PR 본문은 "우리 저장소에 통합 담당자별로 크레이트 하나씩(react_compiler_swc, react_compiler_oxc), 로직 대부분이 거기 산다"는 그림을 그렸습니다. 그런데 6월 11일, [compiler] Remove OXC and SWC plugins in favor of them being handled by those projects(#36743)가 머지됩니다. 사유는 한 줄입니다 — "I believe the consensus is that the OXC and SWC plugins should live in those projects, and instead consume the React compiler as a crate"(OXC/SWC 플러그인은 그쪽 프로젝트에 살고, React 컴파일러를 크레이트로 소비하는 게 합의인 것 같다). 실제로 지금 compiler/crates에는 oxc/swc 크레이트가 없습니다. 머지 이틀 만에 통합 전략이 바뀐 것이고, PR 본문만 읽으면 틀린 그림을 갖게 됩니다.
Rust로 옮기면서 새로 생긴 문제도 있습니다. 6월 16일 [rust-compiler] Represent string values as JsString (WTF-16 aware)(#36731). JavaScript 문자열은 WTF-16이고 Rust의 String은 UTF-8입니다. 서로게이트 짝이 맞지 않는 문자열을 Rust 문자열로 그냥 받으면 원본을 손실 없이 보존할 수 없습니다. TypeScript에서는 존재하지 않던 종류의 버그가 언어를 바꾸면서 따라온 것입니다. 6월 22일에는 Use rustc-hash FxHasher for all maps and sets(#36811) 같은 전형적인 Rust 성능 튜닝도 들어갔습니다.
"모든 픽스처 통과"에는 각주가 필요합니다. 머지 커밋과 함께 들어온 compiler/crates/TODO.md에는 e2e 패리티 스냅샷 표가 있습니다.
Variant | Score | Failures
Babel | 1792 / 1802 | 10
SWC | 1786 / 1802 | 16
OXC | 1704 / 1795 | 91
이건 PR 본문의 "1725개 픽스처 전부 통과"와 모순이 아닙니다 — 다른 스위트입니다. 앞의 것은 snap 스위트(Rust 플러그인 대 기존 플러그인 출력 대조)이고, 이 표는 3개 통합 변형을 TypeScript 구현과 붙인 e2e 패리티 스위트입니다. 요점은 통합별로 보면 아직 다 통과하지 못한다는 것이고, 문서 스스로 "the OXC row predates the fixtures and has not been re-measured"(OXC 행은 픽스처보다 앞서 측정됐고 다시 재지 않았다)고 적어 뒀습니다. 참고로 cargo test --workspace는 84개 통과 0개 실패입니다. 이 파일은 6월 9일 머지 이후 손대지 않았으므로, 지금 수치는 다를 수 있습니다.
그리고 여전히 구멍을 줍고 있습니다. 7월 8일 [compiler] Bail out with Todo on using and await using declarations(#36946) — using / await using 선언을 만나면 포기하도록 했습니다. 7월 7일 [compiler] Port JSX tag classification fix to Rust (not-lowercase is a component)(#36951)처럼 TypeScript 쪽 수정을 Rust로 옮기는 커밋도 계속 들어옵니다. 여기에 이 포팅의 진짜 비용이 보입니다 — 당분간 두 벌을 동시에 유지해야 합니다.
지금 쓸 수 있나 — npm이 답한다
이 질문은 검색이 아니라 명령어로 답하는 게 빠릅니다.
npm view babel-plugin-react-compiler dist-tags
# latest: 1.0.0
# experimental: 0.0.0-experimental-a1856f3-20260507
2026년 7월 16일 기준 latest는 여전히 1.0.0(2025년 10월)이고, 마지막 퍼블리시는 2026년 5월 8일 실험 빌드입니다. 즉 6월 9일에 머지된 Rust 포팅이 담긴 npm 빌드는 아직 존재하지 않습니다. PR의 "No builds available yet"이 지금도 유효합니다.
교차 확인도 됩니다. OXC의 React Compiler 문서는 이 기능을 "experimental and under active development"로 표시하고 "Options and behaviour may change"라고 경고하면서, React에 머지됐지만 퍼블리시되지 않은(unpublished) Rust 포팅을 oxc-project/forked-react-compiler에 릴리스 가능한 크레이트로 벤더링해서 쓴다고 밝힙니다. 상류가 퍼블리시하지 않으니 포크해서 쓰고 있는 것입니다.
그리고 react.dev 블로그에는 이 포팅에 대한 글이 없습니다. 현재 가장 최근 글은 2026년 2월 24일 The React Foundation입니다. 공식 발표 없이 조용히 머지된 변경입니다.
이 대목에서 민담이 어떻게 만들어지는지 실시간으로 볼 수 있습니다. 검색하면 "Next.js 16.4가 Rust 컴파일러를 탑재해 빌드가 40% 이상 빨라졌다" 같은 문장이 나옵니다. 확인해 보면 이렇습니다.
npm view next dist-tags
# latest: 16.2.10
# preview: 16.3.0-preview.6
Next.js 16.4는 존재하지 않습니다. 40%라는 숫자도 출처가 없습니다. 없는 버전의 없는 벤치마크가 이미 인용 가능한 문장의 꼴을 하고 돌아다니는 중입니다. 프레임워크 성능 숫자를 볼 때 npm view 한 줄이 검색 결과 열 개보다 낫습니다.
컴파일러가 여전히 무는 곳 — 메모이제이션 안전성
Rust냐 TypeScript냐는 컴파일러가 어디서 물리는지를 바꾸지 않습니다. 같은 알고리즘의 이식이니까요. 그래서 실무자에게 더 중요한 건 이쪽입니다.
React Compiler는 자동 메모이제이션이 안전하지 않은 API를 알고 있고, 그런 모듈은 최적화에서 뺍니다. 그 목록이 DefaultModuleTypeProvider에 있는데, 2026년 7월 현재 딱 세 개입니다.
react-hook-form—useForm()이 돌려주는 것 중watch()만@tanstack/react-table@tanstack/react-virtual
이유는 소스 주석이 잘 설명합니다. 문제는 내부 가변성(interior mutability)입니다. 새 값을 만들어 상태로 넣는 대신 값을 제자리에서 변형하는 API는, 함수 객체가 그대로여도 호출 결과가 달라집니다. React는 "안이 바뀌면 바깥 객체도 바뀐다"를 전제로 메모이제이션을 하므로 이 전제가 깨집니다. 주석은 이 상황을 이렇게 정리합니다 — Rules of React를 정밀하게 문서화하기 전에 만들어진 라이브러리들이 메모이제이션이 안전하지 않은 API를 실수로 출시한 것이고, 개발자가 그걸 수동으로 메모하면 앱이 깨지거나, 컴파일러를 써 보고 "컴파일러가 내 코드를 망가뜨렸다"고 생각하게 된다는 것입니다.
이게 추상적인 걱정이 아니라는 걸 보여주는 사건이 열흘 전에 있었습니다. 7월 6일 [compiler]: add useWindowVirtualizer to known incompatible libraries입니다. 2025년 9월에 @tanstack/react-virtual의 useVirtualizer는 목록에 올렸는데 useWindowVirtualizer를 빠뜨렸던 겁니다. 둘은 같은 내부 구현을 감싼 얇은 래퍼라 같은 문제를 갖습니다. 결과가 구체적입니다 — 제보자 앱에서 getVirtualItems()가 첫 렌더 값(측정 전의 빈 배열)에 얼어붙어, 가상 목록이 영구히 비어 있는 상태가 됐습니다. 조용히 잘못된 메모이제이션으로 컴파일된 것입니다.
여기서 두 가지를 읽어야 합니다.
첫째, 이 안전망은 하드코딩된 목록입니다. 알려진 모듈만 막습니다. 목록에 없는데 같은 패턴을 쓰는 라이브러리(또는 당신의 사내 훅)는 그대로 통과합니다. OXC 문서도 같은 얘기를 합니다 — "Code that breaks the Rules of React is also skipped rather than optimized — for example interior mutability, or libraries built on observable mutation such as MobX's observer()." MobX의 observer()가 예시로 붙어 있습니다.
둘째, 이제 목록을 두 번 고쳐야 합니다. #36912는 수정을 DefaultModuleTypeProvider.ts와 Rust 크레이트의 default_module_type_provider.rs 양쪽에 넣었습니다. 앞서 말한 이중 유지 비용이 이렇게 생깁니다.
부수적으로 OXC 문서에는 통합 순서 함정도 적혀 있습니다 — "The React Compiler requires the original source: it must see JSX before any other transform." emotion의 CSS prop이나 Babel의 constant-elements처럼 JSX를 먼저 건드리는 플러그인이 있으면 깨집니다.
Meta의 12%와 2.5배는 무엇을 잰 숫자인가
Rust 얘기와 자주 뒤섞이는 숫자가 있어서 분리해 둡니다. React Compiler v1.0 발표의 문장입니다.
We've seen initial loads and cross-page navigations improve by up to 12%, while certain interactions are more than 2.5× faster.
조건을 정확히 붙이면 이렇습니다.
- 누가: React 팀(Meta)이 자사 앱에서 보고한 수치입니다. 독립 벤치마크가 아닙니다.
- 무엇을: 초기 로드와 페이지 간 이동이 최대(up to) 12% 개선, 특정(certain) 인터랙션이 2.5배 이상. "up to"와 "certain"은 장식이 아니라 범위 한정자입니다. 평균이 아닙니다.
- 언제: 2025년 10월, TypeScript 버전 1.0 기준입니다. Rust 포팅과 아무 관계가 없습니다.
- 메모리 사용량은 중립이었다고 덧붙입니다.
그리고 발표문 스스로 "Although your mileage may vary, we recommend experimenting with the compiler in your app to see similar performance gains"라고 씁니다. 여기서 12%는 런타임 성능이고 3배/10배는 빌드 타임 컴파일 속도라는 점도 헷갈리기 쉽습니다. 축이 다른 숫자입니다. 섞어서 "컴파일러가 10배 빨라져서 앱이 12% 빨라진다" 같은 문장을 만들면 두 번 틀립니다.
마치며 — 지금 당신이 할 일
정리하면 이렇습니다.
사실인 것: React Compiler의 Rust 포팅이 2026년 6월 9일 머지됐습니다. 아키텍처는 TypeScript 버전과 동일(HIR + CFG + SSA, 패스 단위 이식)하고, 차이는 주로 아레나·인덱스 기반 데이터 표현입니다. 아키텍처와 검증 전략은 사람이 잡고 구현은 대부분 AI가 썼으며, 패스 단위 IR 대조라는 꽤 견고한 검증 장치를 갖췄습니다.
사실이라고 부르기 어려운 것: "10배". AI가 산출했고 저자가 벤치마크 설정을 검증하지 않았다고 밝힌 수치이며, 그나마 엔드투엔드는 3배이고, 그 3배를 만든 코드는 머지 직후부터 직렬화 경로가 바뀌면서 이미 낡았습니다. "Next.js 16.4의 40%"류는 아예 없는 버전 얘기입니다.
당신에게 바뀌는 것: 오늘 기준으로 없습니다. npm의 latest는 1.0.0이고 Rust 빌드는 퍼블리시되지 않았습니다. 지금 React Compiler를 쓰고 있다면 그대로 TypeScript 1.0을 쓰고 있는 것이고, 그게 정상입니다. 준비랄 게 있다면 v1.0 발표가 권한 대로 정확한 버전에 고정(^1.0.0이 아니라 1.0.0)하고 e2e 테스트를 갖추는 것 정도입니다. 메모이제이션이 적용되는 방식이 버전마다 달라질 수 있다는 게 그 권고의 이유인데, 구현체가 통째로 바뀌는 전환기에는 더 그렇습니다.
언제 다시 볼 것인가: npm view babel-plugin-react-compiler dist-tags가 답합니다. Rust 빌드가 퍼블리시되고, react.dev에 공식 글이 올라오고, crates/TODO.md의 패리티 표가 정리되는 시점입니다. 그 전까지는 벤치마크 트윗이 아니라 저장소가 상태를 말합니다.
마지막으로, 이 사건에서 제일 오래 남을 교훈은 성능이 아니라 방법론 쪽 같습니다. 12만 줄짜리 컴파일러를 AI로 이식하면서 신뢰를 만든 건 코드 리뷰가 아니라 모든 패스의 중간 표현을 기계적으로 대조하는 장치였습니다. 정답지가 있는 문제였기에 가능했다는 한계도 같이 기억할 만합니다. 그리고 그 PR을 쓴 사람조차 자기 숫자에 "검증 안 했다"고 각주를 달아 뒀는데 생태계는 각주를 떼고 인용했다는 것 — 이쪽이 더 오래 반복될 교훈일지도 모르겠습니다.
참고 자료
- PR #36173 —
[compiler] Port React Compiler to Rust(josephsavona, 2026-03-30 오픈 / 2026-06-09 머지) - PR #36743 — OXC·SWC 플러그인 제거, 해당 프로젝트가 크레이트로 소비하도록 변경
- PR #36912 —
useWindowVirtualizer를 알려진 비호환 라이브러리에 추가 - PR #34493 —
@tanstack/react-virtual을 알려진 비호환 라이브러리에 추가 (2025-09) compiler/crates— Rust 크레이트 구조와TODO.md패리티 스냅샷DefaultModuleTypeProvider.ts— 내부 가변성과 비호환 라이브러리 목록- React Compiler v1.0 (react.dev, 2025-10-07) — Quest Store 12% / 2.5배, 버전 고정 권고
- React 블로그 — 최신 글은 2026-02-24 The React Foundation (Rust 포팅 공식 발표 없음)
- OXC — React Compiler 통합 문서 (실험적, 미퍼블리시 포팅을 벤더링)
- oxc-project/forked-react-compiler — OXC가 벤더링한 크레이트
- Next.js 16 아키텍처 — Turbopack과 Cache Components (관련 글)
React Compiler Got Ported to Rust — What Merged, What Did Not, and That "10x" Number
- Introduction — The Compiler Was Already 1.0, So Why Port It Again
- What Actually Merged — The Facts First
- '3x' and '10x' — the Warning Label the Author Attached Himself
- "Humans Guided the Architecture, AI Wrote Most of the Code"
- The Real Thing to Look At Is Not Speed, It Is the Verification Method
- Architecture — Same Algorithm, Different Memory Model
- The Month After Merge — Where the Plan Changed, Where It Still Leaks
- Can You Use It Today — npm Has the Answer
- Where the Compiler Still Bites — Memoization Safety
- What Meta's 12% and 2.5x Actually Measured
- Closing — What You Should Do Right Now
- References
Introduction — The Compiler Was Already 1.0, So Why Port It Again
React Compiler looked like a story that had already wrapped up. React Compiler v1.0 shipped on October 7, 2025, and the promise to close out the era of hand-wiring useMemo and useCallback had earned itself a stable-release label.
Then, on June 9, 2026, PR #36173 — [compiler] Port React Compiler to Rust merged into facebook/react main. The entire compiler had been rewritten from TypeScript into Rust: 461 files changed, 123,289 lines added. The author is Joseph Savona (@josephsavona) of the React team.
Within days, the sentence "React Compiler is 10x faster in Rust" started making the rounds. This post traces that sentence back to primary sources. The short version: the merge is a fact; the number is hard to call a fact — because the author himself wrote as much.
What Actually Merged — The Facts First
The first sentence of the PR body says it all.
This is an experimental, work-in-progress port of React Compiler to Rust.
The items that follow keep the same tone.
- Work in progress. "we are sharing early, prior to testing internally at Meta, to get feedback from partners in parallel with continued development" — meaning this went public before internal testing at Meta even started, specifically to get partner feedback.
- No builds. "No builds available yet, you'll have to do some hacking if you want to try this." If you want to try it, you are on your own.
- No known gaps, but lurking bugs are possible. "All fixtures pass, no known gaps but there may be lurking bugs."
The timeline: opened March 30, 2026, merged June 9. Two and a half months.
One more thing worth flagging: this is not the first attempt. The repository history shows a commit from January 24, 2025 titled [compiler][be] Remove unused experimental Rust port. A Rust port was tried once before, went unused, and was deleted. This is round two.
'3x' and '10x' — the Warning Label the Author Attached Himself
Now for the number in question. Here is the performance item from the PR body, in full.
Early performance numbers are derived from AI and i haven't spent much time validating the benchmark setup, beyond the fact that the optimization opportunities it discovered made complete sense and the fixes were right. With that caveat, it does appear that the Rust version is quite fast already: 3x faster when operating as a Babel plugin. The serialization cost is quite high, but the actual transformation logic is ~10x faster, so it's net faster. Native integrations (oxc, swc) should be even faster.
Let us pick apart exactly what is happening here.
First, the number comes from AI. The author did not write and run a benchmark himself; the figure came from AI, and he states plainly that he "haven't spent much time validating the benchmark setup." What he says he did verify is not the performance number but that the optimization opportunities the AI found made sense. That is a completely different claim.
Second, 3x and 10x are not measuring the same thing. 3x is the end-to-end number when running as a Babel plugin; 10x is transformation logic only, with the serialization cost stripped out. The moment you round it up to "10x faster," the serialization cost quietly disappears — and the author says, in the very same sentence, that this cost is "quite high." The number that actually reaches your build is the 3x one, not the 10x one.
Third, "oxc/swc are faster" is a prediction, not a measurement. The original says "should be even faster" — note the modal verb. It should not be rendered as a flat "are faster."
Fourth, even that 3x is already stale. There is some irony here, which we will get to below.
To sum up, these are numbers that were produced by AI on an experimental WIP branch, and that the author himself says he did not validate. That does not mean you cannot cite them — it means you have to carry the conditions along whenever you do. This ecosystem has a real talent for turning unconditioned numbers into folklore.
"Humans Guided the Architecture, AI Wrote Most of the Code"
This is the other line from the PR that got quoted almost as much as the number.
The architecture was heavily guided by humans (me, @josephsavona) but majority coded by AI.
Lift out just "majority coded by AI" and it is easy to misread. The very next sentence narrows the scope.
I was very hands-on in setting the architecture, the testing and verification strategy, incremental migration approach, etc. I also kept a close eye on the code and spent a decent amount of time going back and forth to get code quality to a decent level.
In other words, what the human set was the architecture, the testing and verification strategy, and the incremental migration approach, and code quality was also pulled up by "going back and forth" repeatedly. What AI filled in was the implementation inside that frame.
It is also worth noting that the conditions here favored AI. This was not designing a compiler from a blank page — it is a port with the answer key already in hand. The TypeScript implementation exists as a reference, there is a 1:1 correspondence pass by pass (in the author's words, "It's very much a pass-by-pass port"), and the output can be checked mechanically for correctness. This was a problem where the spec already existed in an executable form. Generalizing the result here into "AI can build a compiler" overreaches.
The Real Thing to Look At Is Not Speed, It Is the Verification Method
Personally, the most instructive part of this PR is not the 10x — it is this: how do you build trust when you hand AI a port that already has an answer key?
Step 1 — output comparison.
all 1725 fixtures pass in snap when comparing the temporary rust version of the plugin with the main version. this compares generated code output as well as errors.
Across 1,725 fixtures, the Rust version is compared against the existing version on generated code output and errors. So far, this is a familiar golden-file test.
Step 2 — per-pass internal-state comparison. This is the crux of it.
all fixtures also pass a full comparison of the per-pass compiler intermediate representation — the intermediate state (including log events and errors) are ~identical after every single pass (modulo some normalization of ids)
Matching only the final output lets "the same answer by accident" pass. So instead, the entire intermediate representation (IR), including log events and errors, is compared after every single pass. The script the author built for this is compiler/script/test-rust-port.sh, and per the PR description, it can be pointed at any arbitrary JS directory, which is planned for use in Meta's internal testing.
The lesson boils down to this: when you hand AI a large-scale port, what you need to build is not the code but the comparison rig. A machine has to be able to confirm not "the output matches" but "every intermediate step matches" before you can trust 120,000 lines without a human reading all of it. It is not a coincidence that "testing and verification strategy" is on the list of things the author says he was "hands-on" about.
There is an honest gap here too, though.
The OXC and SWC example integrations seem to be working well, though i haven't manually verified this to the same extent as i have the Babel integration.
He says he has not manually verified these to the same degree as the Babel integration.
Architecture — Same Algorithm, Different Memory Model
The internal structure is surprisingly conservative. Per the PR description itself, the Rust version has the same architecture as the TypeScript version.
AST -> HIR (High-level Intermediate Representation)
- CFG (control-flow graph)
- SSA (single-static assignment)
|
+-> same passes, same algorithms
|
v
output AST
Open compiler/crates in the repository and this structure is split straight into crates — react_compiler_hir, react_compiler_ssa, react_compiler_lowering, react_compiler_optimization, react_compiler_reactive_scopes, react_compiler_typeinference, react_compiler_validation, and so on.
What changed is not the algorithm but the data representation.
The main differences are in the data representation - using arena-like structures (and indices into these arenas) to work within Rust's borrowing system.
A graph that TypeScript tied together with object references cannot be carried over to Rust as-is — the borrow checker does not allow cyclic references and mutable aliasing. So nodes get packed into an arena and referenced by index instead. This has become close to the standard pattern for porting a structure where nodes point at each other, like CFG and SSA, into Rust.
The public API is unusual too.
The public API is basically "Rust Babel AST" + Scope Info in, Rust Babel AST out.
The Rust representation of the Babel AST is used as the shared interface, and each integration (Babel, OXC, SWC) converts to and from its own representation at the boundary. The reason is practical — "the conversion from any AST into our HIR is complex, and we can only maintain one version." On top of that, right now the integration side has to serialize and hand over scope information itself, because the TypeScript version rode on top of Babel's scope analysis. Implementing its own scope resolution is stated as a planned item.
This explains the earlier "the serialization cost is quite high." The round trip of handing the Babel AST to Rust and back, plus serializing the scope graph on top of it, is what was eating the transformation logic's 10x gain down to 3x.
The Month After Merge — Where the Plan Changed, Where It Still Leaks
From here on is what you miss if you only read the PR body. The commit history is far more honest.
Work on the serialization cost started immediately. Two days after merge, on June 11, [rust-compiler] Return the compiled AST by value instead of JSON (#36729) and [rust-compiler] Carry uninspected AST subtrees as raw JSON text (#36730) landed. The compiled AST now comes back as a value instead of JSON, and AST subtrees that do not need inspecting are just carried around as raw JSON text. This is what "the 3x is already stale" meant earlier — the code that produced that number started changing right after merge. Nobody has published what the multiplier is now.
The OXC/SWC plan got reversed. The PR body sketched a picture where "our repo has one crate per integration owner (react_compiler_swc, react_compiler_oxc), and most of the logic lives there." But on June 11, [compiler] Remove OXC and SWC plugins in favor of them being handled by those projects (#36743) merged. The reason given is one line — "I believe the consensus is that the OXC and SWC plugins should live in those projects, and instead consume the React compiler as a crate." And indeed, compiler/crates has no oxc/swc crate today. The integration strategy changed two days after merge, and reading only the PR body leaves you with the wrong picture.
New problems showed up specifically because of the move to Rust. On June 16, [rust-compiler] Represent string values as JsString (WTF-16 aware) (#36731). JavaScript strings are WTF-16, and Rust's String is UTF-8. If you just take a string with unpaired surrogates and drop it into a Rust string, you cannot preserve the original losslessly. A category of bug that did not exist in TypeScript came along for the ride with the language change. On June 22, a typical Rust performance tune-up landed too — Use rustc-hash FxHasher for all maps and sets (#36811).
"All fixtures pass" needs a footnote. compiler/crates/TODO.md, which landed with the merge commit, has an e2e-parity snapshot table.
Variant | Score | Failures
Babel | 1792 / 1802 | 10
SWC | 1786 / 1802 | 16
OXC | 1704 / 1795 | 91
This does not contradict the PR body's "all 1,725 fixtures pass" — it is a different suite. The earlier one is the snap suite (Rust plugin output compared against the existing plugin's output); this table is the e2e-parity suite that pits the three integration variants against the TypeScript implementation. The point is that, broken down by integration, it does not all pass yet, and the document itself notes "the OXC row predates the fixtures and has not been re-measured." For reference, cargo test --workspace passes 84 and fails 0. This file has not been touched since the June 9 merge, so the current numbers may differ.
And gaps are still being picked up. On July 8, [compiler] Bail out with Todo on using and await using declarations (#36946) — it now bails out when it hits a using / await using declaration. Commits that port fixes from the TypeScript side over to Rust keep coming in too, like [compiler] Port JSX tag classification fix to Rust (not-lowercase is a component) (#36951) on July 7. This is where the real cost of this port shows up — for the time being, two implementations have to be maintained in parallel.
Can You Use It Today — npm Has the Answer
This question is answered faster with a command than with a search.
npm view babel-plugin-react-compiler dist-tags
# latest: 1.0.0
# experimental: 0.0.0-experimental-a1856f3-20260507
As of July 16, 2026, latest is still 1.0.0 (from October 2025), and the last publish was an experimental build from May 8, 2026. In other words, there is no npm build yet that contains the Rust port merged on June 9. The PR's "No builds available yet" still holds today.
There is a cross-check too. OXC's React Compiler documentation marks this feature "experimental and under active development," warns that "Options and behaviour may change," and states that it vendors the Rust port — merged into React but still unpublished — as a releasable crate at oxc-project/forked-react-compiler. Since upstream has not published it, they are running off a fork.
And there is no post about this port on the react.dev blog. The most recent post right now is The React Foundation from February 24, 2026. This is a change that merged quietly, with no official announcement.
Right here you can watch folklore get manufactured in real time. Search around and you will find sentences like "Next.js 16.4 shipped with the Rust compiler and builds got more than 40% faster." Checking it looks like this.
npm view next dist-tags
# latest: 16.2.10
# preview: 16.3.0-preview.6
Next.js 16.4 does not exist. The 40% number has no source either. A nonexistent benchmark for a nonexistent version is already circulating in the shape of a quotable sentence. When you are looking at framework performance numbers, one line of npm view beats ten search results.
Where the Compiler Still Bites — Memoization Safety
Rust versus TypeScript does not change where the compiler bites you. It is a port of the same algorithm. So this is the part that matters more for practitioners.
React Compiler knows about APIs where automatic memoization is not safe, and it excludes those modules from optimization. That list lives in DefaultModuleTypeProvider, and as of July 2026 it has exactly three entries.
react-hook-form— onlywatch()among whatuseForm()returns@tanstack/react-table@tanstack/react-virtual
The source comment explains the reason well. The problem is interior mutability. An API that mutates a value in place instead of creating a new value and putting it into state can produce a different call result even while the function object stays the same. React memoizes on the assumption that "if the inside changes, the outside object changes too," and this assumption breaks. The comment sums up the situation this way: libraries built before the Rules of React were precisely documented shipped memoization-unsafe APIs by accident, and if a developer manually memoizes them, the app breaks — or they try the compiler and conclude "the compiler broke my code."
An incident ten days ago shows this is not an abstract worry. On July 6, [compiler]: add useWindowVirtualizer to known incompatible libraries. Back in September 2025, @tanstack/react-virtual's useVirtualizer had been added to the list, but useWindowVirtualizer was missed. The two are thin wrappers around the same internal implementation, so they share the same problem. The outcome was concrete — in the reporter's app, getVirtualItems() froze at its first-render value (an empty array, before any measurement), leaving the virtualized list permanently empty. It had been silently compiled with incorrect memoization.
There are two things to take from this.
First, this safety net is a hardcoded list. It only blocks known modules. A library (or your own in-house hook) that uses the same pattern but is not on the list sails straight through. The OXC documentation says the same thing — "Code that breaks the Rules of React is also skipped rather than optimized — for example interior mutability, or libraries built on observable mutation such as MobX's observer()." MobX's observer() is given as an example.
Second, the list now has to be fixed twice. #36912 put the fix in both DefaultModuleTypeProvider.ts and the Rust crate's default_module_type_provider.rs. This is exactly what the dual-maintenance cost mentioned earlier looks like in practice.
As an aside, the OXC documentation also notes an integration-ordering trap — "The React Compiler requires the original source: it must see JSX before any other transform." If some other plugin touches JSX first — emotion's CSS prop, or Babel's constant-elements, for instance — it breaks.
What Meta's 12% and 2.5x Actually Measured
There is another number that often gets mixed up with the Rust story, so let us separate it out. This is a line from the React Compiler v1.0 announcement.
We've seen initial loads and cross-page navigations improve by up to 12%, while certain interactions are more than 2.5× faster.
Attach the conditions precisely and it reads like this.
- Who: This is a figure reported by the React team (Meta) from their own app. It is not an independent benchmark.
- What: initial loads and cross-page navigations improved by up to 12%, and certain interactions by more than 2.5x. "up to" and "certain" are not decoration — they are scope qualifiers. This is not an average.
- When: October 2025, on TypeScript version 1.0. It has nothing to do with the Rust port.
- It also adds that memory usage was neutral.
And the announcement itself says, "Although your mileage may vary, we recommend experimenting with the compiler in your app to see similar performance gains." It is also easy to blur the fact that 12% is runtime performance while 3x/10x is build-time compilation speed — these numbers sit on different axes. Mix them together into a sentence like "the compiler got 10x faster so the app got 12% faster" and you are wrong twice over.
Closing — What You Should Do Right Now
To sum up.
What is true: The Rust port of React Compiler merged on June 9, 2026. The architecture is identical to the TypeScript version (HIR + CFG + SSA, ported pass by pass); the difference is mainly the arena-and-index-based data representation. Humans set the architecture and verification strategy while AI wrote most of the implementation, and it comes with a fairly solid verification mechanism — per-pass IR comparison.
What is hard to call a fact: "10x." This is a number that AI produced and that the author states he did not validate the benchmark setup for; the end-to-end figure is really 3x, and even the code behind that 3x is already stale, since the serialization path started changing right after merge. Claims like "Next.js 16.4's 40%" are about a version that does not even exist.
What changes for you: nothing, as of today. npm's latest is 1.0.0 and the Rust build is not published. If you are using React Compiler right now, you are using TypeScript 1.0, and that is exactly as it should be. If there is any preparation worth doing, it is what the v1.0 announcement already recommends — pin to an exact version (1.0.0, not ^1.0.0) and have e2e tests in place. The reasoning behind that recommendation is that how memoization gets applied can vary between versions, and that is even more true during a transition where the implementation is being swapped out wholesale.
When to check back: npm view babel-plugin-react-compiler dist-tags will tell you. The moment to watch for is when the Rust build gets published, an official post goes up on react.dev, and the parity table in crates/TODO.md gets cleaned up. Until then, the repository — not a benchmark tweet — is what tells you the actual state.
Last, the lesson from this whole episode that seems likely to outlast the performance numbers is a methodological one, not a speed one. What built trust while porting a 120,000-line compiler with AI was not code review — it was a rig that mechanically compares the intermediate representation at every pass. It is worth remembering, alongside that, the limitation that this was only possible because the problem came with an answer key. And the fact that even the person who wrote the PR footnoted his own numbers with "I have not validated this," while the ecosystem quoted the number and dropped the footnote — that may be the lesson that keeps repeating longest.
References
- PR #36173 —
[compiler] Port React Compiler to Rust(josephsavona, opened 2026-03-30 / merged 2026-06-09) - PR #36743 — removes the OXC/SWC plugins so those projects consume it as a crate instead
- PR #36912 — adds
useWindowVirtualizerto the known-incompatible-libraries list - PR #34493 — adds
@tanstack/react-virtualto the known-incompatible-libraries list (2025-09) compiler/crates— the Rust crate layout and theTODO.mdparity snapshotDefaultModuleTypeProvider.ts— interior mutability and the incompatible-library list- React Compiler v1.0 (react.dev, 2025-10-07) — Quest Store 12% / 2.5x, version-pinning recommendation
- React blog — most recent post is 2026-02-24, The React Foundation (no official announcement of the Rust port)
- OXC — React Compiler integration docs (experimental, vendors the unpublished port)
- oxc-project/forked-react-compiler — the crate OXC vendors
- Next.js 16 Architecture — A Deep Dive into Turbopack and Cache Components (related post)