Swapping Babel for SWC in a Vite React Project: What Actually Changes
Vite's SWC-based React plugin replaces Babel with a Rust compiler for faster JSX transforms. Here's the migration, how to measure the gain on your own codebase, and the Babel-plugin trade-offs that decide whether it's worth it.
06 May 2026, 01:10 UTC

If your Vite dev server feels snappy but your React builds crawl, the bottleneck is often Babel. Vite's default React plugin (@vitejs/plugin-react) uses Babel to transform JSX and power Fast Refresh, and on a large codebase that transpile step adds up. The @vitejs/plugin-react-swc plugin swaps Babel for SWC, a Rust-based compiler, and on typical React projects it cuts transpilation time substantially. Here's what the switch involves, what you give up, and how to verify it actually helped.
Why Babel becomes the slow part
Vite's dev server is fast because it serves native ES modules and lets the browser do the work — but React files still need JSX transformation before the browser can run them. Babel is a JavaScript program parsing and rewriting JavaScript; SWC does the same job in compiled Rust with parallelization. The rest of Vite (esbuild for dependency pre-bundling, Rollup for production builds) stays identical either way, so the only thing changing is the per-file JSX/TSX transform and the Fast Refresh runtime injection.
Making the switch
The change is deliberately small. From your project root, with a normal user account (no elevated permissions needed):
npm uninstall @vitejs/plugin-react
npm install -D @vitejs/plugin-react-swcThen update vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
export default defineConfig({
plugins: [react()],
})That's the entire migration for a standard project. Fast Refresh keeps working — the plugin wires it up through SWC automatically, so hot-module replacement behaves the same as before. Run npm run dev and edit a component; you should see updates land in the browser without a full reload, exactly as with the Babel plugin.
A worked example: measuring whether it helped
Don't take the speedup on faith — measure it on your own codebase, because the gain depends on how many JSX/TSX files you have and how heavy your Babel config was. A simple approach:
- Before switching, run
npm run buildthree times and note the wall-clock time (on Unix,time npm run build; on Windows PowerShell,Measure-Command { npm run build }). - Apply the plugin change, then repeat the three builds.
- Compare medians, and also compare the output:
dist/bundle sizes should be in the same ballpark. SWC and Babel minify slightly differently, so small size differences are normal; a large regression is not.
For dev mode, restart the dev server and time how long it takes from npm run dev to the "ready" line, then reload a route that imports many components. Cold-start improvements are usually modest (dependency pre-bundling dominates); the bigger win shows up in rebuilds during editing and in CI build times on large projects.
The trade-off: your Babel plugins don't come along
This is the part that decides whether the switch is right for you. SWC is not Babel — it doesn't run Babel plugins. If your babel.config.js includes transforms you depend on, they silently stop applying. Common cases:
- CSS-in-JS tooling. The
babel-plugin-styled-componentstransform (which adds readable class names and enables better SSR behavior) has an SWC equivalent, but it's a separate community plugin you must configure explicitly via the plugin'spluginsoption. Emotion users should check whether they rely on its Babel plugin for labels and source maps. - Experimental syntax. Anything behind a Babel plugin for a stage-2/stage-3 proposal may not have an SWC equivalent yet.
- Custom codemods or macros.
babel-plugin-macrosand similar have no direct SWC counterpart.
After switching, grep your repo for a Babel config (babel.config.js, .babelrc, or a babel key in package.json). If it's empty or only contains presets, you're safe to delete it. If it lists plugins, audit each one before committing to SWC.
A second limitation: SWC's story for legacy-browser polyfills is less mature. If you support older browsers and relied on Babel's preset-env with core-js injection, keep an eye on build output warnings and consider handling polyfills separately (for example with @vitejs/plugin-legacy, which runs its own pipeline). Test in the oldest browser you support before shipping.
How to check the result, and when to stay on Babel
Three quick verification steps after migrating:
- Run your test suite. JSX transform differences occasionally surface in snapshot output or in how error boundaries display component names.
- Do a production build and smoke-test the app, especially any feature that depended on a Babel plugin (styled-components SSR, i18n extraction, etc.).
- Confirm Fast Refresh still preserves component state: edit a component with local state and check the state survives the update.
If any of those fail and the cause is a missing Babel plugin with no SWC equivalent, reverting is trivial — reinstall @vitejs/plugin-react and restore the old config line. Nothing about the migration touches your source code.
The practical rule of thumb: if your Babel config is just presets (the common case for apps scaffolded in the last few years), switching to @vitejs/plugin-react-swc is a low-risk, measurable build-time win. If your config is a curated list of plugins, treat SWC as an opt-in experiment per project, not a default — and measure before and after so the decision is based on your numbers, not someone else's benchmark.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.