{"id":10691,"date":"2026-08-17T15:49:21","date_gmt":"2026-08-17T20:49:21","guid":{"rendered":"https:\/\/master.dev\/blog\/?p=10691"},"modified":"2026-08-17T15:49:22","modified_gmt":"2026-08-17T20:49:22","slug":"react-compiler-linting-just-got-a-rust-native-speedup-in-oxlint","status":"publish","type":"post","link":"https:\/\/master.dev\/blog\/react-compiler-linting-just-got-a-rust-native-speedup-in-oxlint\/","title":{"rendered":"React Compiler Linting Just Got a Rust-Native Speedup in Oxlint"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The React team made a big splash recently when it <a href=\"https:\/\/github.com\/react\/react\/pull\/36173\">announced the release of the Rust rewrite of React<\/a> Compiler and said that it would be the new canonical version of the compiler going forward.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I\u2019ve been on the React Compiler train to enable reliable performance on my AI website builder <a href=\"https:\/\/outlyne.com\">Outlyne<\/a> for almost a year now, and I haven\u2019t looked back. I no longer think about when I need to <code>useCallback<\/code> or <code>useMemo<\/code>. That, coupled with judicious use of <code>useEffectEvent<\/code> and adherence to <a href=\"https:\/\/react.dev\/learn\/you-might-not-need-an-effect\">\u201cYou Might Not Need An Effect\u201d<\/a> best practices, has largely freed me from the most common complaints leveled at React by its critics (and, maybe even more so, its proponents).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I\u2019ve also migrated fully onto <a href=\"https:\/\/vite.dev\/\">Vite<\/a> v8 (with Rolldown) and the accompanying oxc ecosystem. There were many replacements recently in the Vite ecosystem:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Rollup \u2192 Rolldown<\/li>\n\n\n\n<li>eslint \u2192 Oxlint<\/li>\n\n\n\n<li>prettier \u2192 oxfmt<\/li>\n\n\n\n<li>jest \u2192 vitest<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This means repo-wide code formatting is effectively instantaneous, tests run way faster, and linting is mostly very fast. But React Compiler has held that toolchain back from its full potential.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Most of my build time goes to Babel + React Compiler, and my lint task has been slowed way down by needing to lean on Oxlint&#8217;s support for JS plugins to add the React Compiler linter. That linter plugin needs to run Babel, then the React Compiler core to build up the AST and understanding of the code it requires to statically analyze it and report its results. In total, running the React Compiler lint plugin made my lint job take more than three times as long as linting without.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u201cNot worth it,\u201d you\u2019re probably thinking, \u201cit\u2019s just some lint rules.\u201d So glad you brought that up, because it gets at one last bit of essential context: <strong>I consider running the React Compiler linter, with all rules enabled, to be an indisputable prerequisite<\/strong> to using React Compiler, something I covered in a <a href=\"https:\/\/acusti.ca\/blog\/2025\/12\/16\/react-compiler-silent-failures-and-how-to-fix-them\/\">previous blog post<\/a>. Briefly, removing manual memoization to leave it in the hands of React Compiler is magic and simplifies and cleans up your codebase significantly, but it can also bite you hard if you have a situation where the component tree rapidly re-renders without the proper memoization being applied and you happen to introduce some code outside of React Compiler\u2019s supported subset of JavaScript. Doing so causes the compiler to bail out, meaning you lose the automatic memoization and could see significant UX degradation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It happened to us: a bailout shipped a janky, visually broken (though still functional) animated placeholder in our homepage\u2019s primary prompt input.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Oxlint gets native React Compiler support<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">While Vite doesn\u2019t have support for the Rust version of React Compiler, <code>oxlint<\/code> <a href=\"https:\/\/github.com\/oxc-project\/oxc\/releases\/tag\/apps_v1.70.0\">released v1.70.0<\/a> in June, which introduced built-in support for the <a href=\"https:\/\/github.com\/oxc-project\/oxc\/pull\/23202\">React Compiler linting plugin<\/a>:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Adds a nursery <code>react\/react-compiler<\/code> rule that runs the React Compiler (<code>oxc_react_compiler<\/code>) in lint-only mode and reports Rules of React violations: conditional hook calls, setState during render, ref access during render, mutation errors, etc.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">The oxc team built and released their own vendored React Compiler crate (<code>oxc_react_compiler<\/code>) to power the new built-in ruleset. No more need for the Babel pipeline or the Oxlint jsPlugin escape hatch. I tried switching over and found that all of the existing React Compiler lint rules were fully supported, which I verified by introducing violations and seeing that they were caught. There was a small bug related to type generics, but that was fixed by <a href=\"https:\/\/github.com\/oxc-project\/oxc\/pull\/24158\">oxc#24158<\/a>, which first shipped in Oxlint v1.73.0.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And we <strong>found that the speed gains were as promised<\/strong>. Switching to the native rule took our lint task from ~29.2s \u2192 ~9.1s, a 3.2\u00d7 speedup courtesy of the native Rust implementation. That number still includes <code>perfectionist<\/code>, a JS plugin with no native Oxlint equivalent that we keep running. When I tried dropping that too, the same lint task runs in ~2.6s, an 11\u00d7 speedup.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Option You Never Knew You Needed: <code>reportAllBailouts<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>react\/react-compiler<\/code> ships as a \u201cnursery rule\u201d, meaning it\u2019s considered unstable and needs to be explicitly enabled in your config, at which point you can also enable a super useful new config option unique to Oxlint called <code>reportAllBailouts<\/code>. In your <code>.oxlintrc.json<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"plugins\"<\/span>: &#91;<span class=\"hljs-string\">\"react\"<\/span>],\n  <span class=\"hljs-attr\">\"rules\"<\/span>: {\n    <span class=\"hljs-attr\">\"react\/react-compiler\"<\/span>: &#91;<span class=\"hljs-string\">\"error\"<\/span>, { <span class=\"hljs-attr\">\"reportAllBailouts\"<\/span>: <span class=\"hljs-literal\">true<\/span> }]\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Doing so makes the linter report an error on any instance where React Compiler has to bail out from compiling a component or hook. That lets you enforce, at the lint level, a code base where every hook and component is React Compiler compatible. You can achieve the same thing with the eslint React Compiler plugin, but it requires manually enumerating and enabling all of their lint rules in your config, including all of the bailout rules that aren\u2019t a part of the <code>recommended<\/code> preset, and it\u2019s tough to keep it comprehensive. My original list of rules gleaned from the React Compiler linter source was incomplete, and my expanded list had fallen out-of-date by the time I switched to Oxlint.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One tradeoff to the native Oxlint implementation: <code>react\/react-compiler<\/code> is a single aggregate rule, covering everything from rules-of-hooks violations to bailouts. Disabling it on a line to quiet an intentional lint violation silences every compiler diagnostic on that line, so we\u2019ve tried to minimize usage of that option. We also added guidance in our <code>AGENTS.md<\/code> warning about the risks of adding <code>\/\/ oxlint-disable-next-line react\/react-compiler<\/code> in a component or hook.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What About Vite?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">So that all covers the lint part of your pipeline, which I would consider fully solved and available on the Rust React Compiler toolchain. But that\u2019s not the case yet for your actual build, even if the rest of your build pipeline is Rust-based.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">oxc merged a native, build-time transform version in <a href=\"https:\/\/github.com\/oxc-project\/oxc\/pull\/22942\">June 2026<\/a>, but Rolldown\/Vite maintainers pulled that integration back out shortly after shipping it, because enabling it grew Rolldown\u2019s binary by around 17%. On August 4, 2026, oxc published <a href=\"https:\/\/www.npmjs.com\/package\/oxc-transform-react?activeTab=versions\">v0.0.1 of<\/a> <code>oxc-transform-react<\/code>. It\u2019s very early, but <a href=\"https:\/\/github.com\/oxc-project\/oxc\/tree\/main\/napi\/transform-react\">the package\u2019s README<\/a> describes it as \u201cNative Node.js bindings for Oxc\u2019s experimental Rust port of React Compiler\u201d.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The package is intended for low-level usage to transform source code, but I\u2019ve started testing it out to create a Vite plugin to replace the <code>@rolldown\/plugin-babel<\/code> part of the recommended Vite React Compiler setup, and it\u2019s promising. I\u2019ll publish a follow-up post about that once it\u2019s stable enough for me to switch over to using it in production.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Try It Out<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This only impacts linting, so you can adopt it without touching your build step, regardless of your stack (Next.js, Webpack, Vite, etc). It does require <code>oxlint<\/code>, but the <a href=\"https:\/\/oxc.rs\/docs\/guide\/usage\/linter\/migrate-from-eslint.html\">ESLint to Oxlint migration<\/a> is well-established and straightforward. If you\u2019re on ESLint v9\/v10 with flat config, there\u2019s a migration tool that will handle it programmatically:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npx @oxlint\/migrate &lt;optional-eslint-flat-config-path&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Otherwise, ask your favorite LLM to do it, using the <a href=\"https:\/\/skills.sh\/oxc-project\/oxc\/migrate-oxlint\">migrate-oxlint skill<\/a> for extra insurance. Or for the most incremental option, just add Oxlint beside ESLint. It\u2019s so fast that if you drop the existing ESLint React Compiler plugin and adopt the Oxlint version without any other changes, you will speed up your lint step despite adding a brand new tool.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To install Oxlint:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npm install --save-dev oxlint\n<span class=\"hljs-comment\"># or<\/span>\npnpm add -D oxlint\n<span class=\"hljs-comment\"># or<\/span>\nyarn add -D oxlint\n<span class=\"hljs-comment\"># or<\/span>\nbun add -D oxlint<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Create <code>.oxlintrc.json<\/code> (simplest version to get started with):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"plugins\"<\/span>: &#91;<span class=\"hljs-string\">\"react\"<\/span>],\n  <span class=\"hljs-attr\">\"rules\"<\/span>: { <span class=\"hljs-attr\">\"react\/react-compiler\"<\/span>: <span class=\"hljs-string\">\"error\"<\/span> }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Run <code>npx oxlint<\/code>, first without <code>reportAllBailouts<\/code>. The default mode only reports Rules of React violations (conditional hooks, <code>setState<\/code> during render, etc.), which are real bugs worth fixing. Once those are clean, update <code>.oxlintrc.json<\/code> to <code>\"rules\": { \"react\/react-compiler\": [\"error\", { \"reportAllBailouts\": true }] }<\/code> to see everywhere the compiler is silently skipping memoization and where you\u2019re missing out on performance in your app.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One of the most common incompatibilities I\u2019ve run into is:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">MyComponent<\/span>(<span class=\"hljs-params\">{ value }<\/span>) <\/span>{\n  value = value ?? someStateValue;\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The fix is to replace the reassignment with a rename, which is arguably cleaner anyway:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">MyComponent<\/span>(<span class=\"hljs-params\">{ value: valueFromProps }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> value = valueFromProps ?? someStateValue;\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That little issue will opt out your entire component from React Compiler, meaning you could regress performance in any part of your app simply by adding a nullish prop coercion to a hot path component. But the Oxlint <code>reportAllBailouts<\/code> option will trigger an error if you do so, protecting you from those accidental regressions going forward.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A bailout is what broke our homepage\u2019s animated placeholder, as I mentioned earlier in this post. You can <a href=\"https:\/\/acusti.ca\/blog\/2025\/12\/16\/react-compiler-silent-failures-and-how-to-fix-them\/\">read the full story here<\/a>. The takeaway: \u201cReact Compiler without linting all bailouts considered unsafe\u201d. Protect yourself with Oxlint.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The React team made a big splash recently when it announced the release of the Rust rewrite of React Compiler and said that it would be the new canonical version of the compiler going forward. I\u2019ve been on the React Compiler train to enable reliable performance on my AI website builder Outlyne for almost a [&hellip;]<\/p>\n","protected":false},"author":53,"featured_media":10696,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[530,62,528,529,215],"class_list":["post-10691","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-oxlint","tag-react","tag-react-compiler","tag-rust","tag-vite"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/08\/react-compiler-linting.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10691","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/users\/53"}],"replies":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/comments?post=10691"}],"version-history":[{"count":6,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10691\/revisions"}],"predecessor-version":[{"id":10710,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10691\/revisions\/10710"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media\/10696"}],"wp:attachment":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media?parent=10691"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/categories?post=10691"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/tags?post=10691"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}