We Migrated from Webpack to Rsbuild and Production Builds Got 74% Faster — The Migration Reality and Rspack Pitfalls
Honestly, my first reaction was "another new build tool?" — same as when Vite came out, same as when Turbopack was announced. But after seeing the case from Alan's fintech team, I couldn't just scroll past it. Their most complex project went from a 2-minute 10-second production build down to 34 seconds, and that came without massively overhauling their config.
This post is an honest account from a frontend developer who has been using Webpack for over a year, covering where you actually get stuck and where you save time when switching to Rsbuild. It covers why Rsbuild is fast, the pitfalls you can easily miss during migration, and what changed in 2.0. It should be especially useful if you're not confident enough to fully restructure your code like you would for Vite, but you still don't want to give up on build speed.
So — can you really finish it in a day?
Table of Contents
Core Concepts
Why Rsbuild Is Fast — Rust Bundler + Integrated Toolchain
Rsbuild is a build tool created by the ByteDance web infrastructure team, running on top of Rspack — a Webpack-compatible bundler implemented in Rust. The speed comes from a combination of three tools: Rspack (a Rust implementation compatible with Webpack) handles bundling, SWC (a Babel replacement) handles transpilation, and Lightning CSS handles CSS minification. All three are written in Rust, and Rsbuild ties them together with zero config.
Rspack is a bundler that maintains the same plugin API and
module.rulesstructure as Webpack while reimplementing the internals in Rust. That's why most existing Webpack loaders and plugins can be used as-is.
One thing worth clarifying: Lightning CSS is often described as a "PostCSS replacement," but that's not quite accurate. Rsbuild's default CSS transformation still goes through PostCSS. Lightning CSS is primarily used at the CSS minification stage. To fully replace PostCSS with Lightning CSS, you need to explicitly configure tools.lightningcssLoader, and some PostCSS plugins still can't be replaced. If you misunderstand this as "PostCSS is gone by default" and delete your config, you'll be in trouble.
The most practically important difference from Vite is this: Vite uses an ESM-based no-bundle approach in the dev server and Rollup in production. This dev/prod mismatch leads to the classic "worked locally, broke in the build" scenario. Rsbuild uses Rspack for both development and production. I actually abandoned Vite once over this exact issue — having consistent behavior across environments is far more reassuring in production work than you might expect.
From webpack.config.js to rsbuild.config.ts
The starting point for migration is replacing the config file. The parts of your existing Webpack config that are compatible with Rspack can be passed through directly via tools.rspack.
// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
export default defineConfig({
plugins: [pluginReact()],
tools: {
rspack: {
// Existing webpack.config.js module.rules, resolve, etc. can be ported here
module: {
rules: [
{
test: /\.svg$/,
use: ['@svgr/webpack'],
},
],
},
},
},
output: {
distPath: {
root: 'dist',
},
},
});Compared to webpack.config.js, the structure feels familiar. The concepts of entry, output, resolve, and module.rules are all intact, and since it's a TypeScript-first config file, type hints work well too.
What Changed in Rsbuild 2.0 — ESM-First and Dropping Webpack Support
Rsbuild 2.0, released in the first half of 2025, made the ecosystem's direction clear.
| Item | 1.x | 2.0 |
|---|---|---|
| Base bundler | Rspack 1.x | Rspack 2.0 |
| Package format | CJS + ESM | Pure ESM (~500KB reduction in install size, improved tree-shaking) |
| Minimum Node.js | 18+ | 20.19+ or 22.12+ |
| Webpack bundler support | Supported | Fully dropped |
| React Server Components | Not supported | Experimental support (rsbuild-plugin-rsc) |
"Pure ESM" matters for more than just size. ESM is statically analyzable, which lets build tools eliminate unused code more accurately (tree-shaking), and it also reduces the interop issues commonly seen with CJS.
It's worth noting the paradox of Webpack bundler support being fully dropped in 2.0. The strategy of "use Rsbuild while keeping Webpack compatibility" was viable in 1.x, but from 2.0 onward you need to fully commit to the Rspack approach. That transition is precisely the core challenge of "migrating from the Webpack legacy."
Practical Application
Migrating a CRA Project
Yellow.ai migrated a CRA-based project to Rsbuild and reduced build times by over 50%. If you're on CRA, the migration is the simplest it can be. If you haven't ejected, you can replace everything with a single rsbuild.config.ts.
# 1. Remove existing CRA dependency
pnpm remove react-scripts
# 2. Install Rsbuild
pnpm add -D @rsbuild/core @rsbuild/plugin-react// package.json
{
"scripts": {
"start": "rsbuild dev",
"build": "rsbuild build",
"preview": "rsbuild preview"
}
}// rsbuild.config.ts — minimal config to replace CRA
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
export default defineConfig({
plugins: [pluginReact()],
html: {
// CRA's %PUBLIC_URL% variable is not handled automatically by Rsbuild.
// You need to either remove it from index.html, or inject it directly
// via htmlRspackPlugin's templateParameters.
template: './public/index.html',
},
source: {
entry: {
index: './src/index.tsx',
},
},
});Warning: If your CRA
public/index.htmlcontains CRA-specific variables like%PUBLIC_URL%, the build will break. Before switching to Rsbuild, either clean up those variables fromindex.htmlor replace them withhtmlRspackPlugin'stemplateParameters. This is the most common sticking point in CRA migrations.
Porting a Complex Webpack Config
Alan's fintech team migrated a project with a tangle of SVG handling, custom loaders, and environment variable injection. The results were impressive.
| Metric | Before (Webpack) | After (Rsbuild) | Improvement |
|---|---|---|---|
| Initial dev build | 16.17s | 5.99s | -63% |
| HMR response time | 2.90s | 700ms | -76% |
| Production build | 2m 10s | 34s | -74% |
The most tangible improvement was HMR. 2.9 seconds down to 700ms — save the file, glance at the screen, and the change is already there. In a development cycle where you save hundreds of times a day, this difference feels much bigger than the numbers suggest.
The porting strategy is to use tools.rspack.
// rsbuild.config.ts — example of porting a complex config
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginSvgr } from '@rsbuild/plugin-svgr';
export default defineConfig({
plugins: [
pluginReact(),
pluginSvgr(), // replaces @svgr/webpack usage from webpack
],
source: {
define: {
// replaces webpack's DefinePlugin
'process.env.API_URL': JSON.stringify(process.env.API_URL),
},
alias: {
// replaces webpack's resolve.alias
'@components': './src/components',
'@utils': './src/utils',
},
},
tools: {
rspack: (config) => {
// Custom loaders without built-in Rspack support go here
// Make sure to verify loader compatibility with Rspack beforehand
config.module?.rules?.push({
test: /\.worker\.ts$/,
use: [{ loader: 'worker-loader' }],
});
return config;
},
},
performance: {
chunkSplit: {
strategy: 'split-by-experience',
},
},
});Looking at a mapping of Webpack config options, you'll see the migration isn't as unfamiliar as it seems.
| Config Item | Webpack | Rsbuild |
|---|---|---|
| Environment variable injection | DefinePlugin |
source.define |
| Path aliases | resolve.alias |
source.alias |
| SVG React components | @svgr/webpack loader |
pluginSvgr() |
| Chunk splitting | optimization.splitChunks |
performance.chunkSplit |
| Custom loaders | module.rules |
tools.rspack callback |
Bulk Migration of 30 Apps in a Monorepo
When migrating multiple apps at once, the most effective approach is to extract shared config into a package. There's a documented case of bulk-migrating 30+ apps this way, cutting local builds by 40% and CI builds by 30%.
// packages/build-config/src/index.ts — shared config package
import { defineConfig, mergeRsbuildConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import type { RsbuildConfig } from '@rsbuild/core';
export function createBaseConfig(overrides?: RsbuildConfig): RsbuildConfig {
const base = defineConfig({
plugins: [pluginReact()],
source: {
alias: {
'@shared': '../../packages/shared/src',
},
},
});
// Use mergeRsbuildConfig instead of spread:
// with nested keys like source.alias, a plain spread will overwrite them entirely.
return overrides ? mergeRsbuildConfig(base, overrides) : base;
}// apps/dashboard/rsbuild.config.ts — extending in each app
import { createBaseConfig } from '@company/build-config';
export default createBaseConfig({
server: { port: 3001 },
source: {
entry: { index: './src/main.tsx' },
},
});Deep merge warning: If your shared config has
source.aliasand individual apps also try to extendsource.alias, using a plain{ ...base, ...overrides }spread will wipe out all shared aliases. Using themergeRsbuildConfigutility safely merges nested objects.
With this structure, updating the shared config package once propagates to everything, making maintenance easy.
Pros and Cons
Advantages
| Item | Details |
|---|---|
| Build speed | 60–76% reduction in build time vs. Webpack for large projects (varies by project size and config) |
| Migration cost | Far fewer code changes than switching to Vite; most existing loaders and plugins can be reused |
| dev/prod consistency | Both development and production use Rspack → no behavioral differences between environments |
| Zero-config DX | TypeScript and HMR built-in; official plugins for React, Vue, Svelte, and Solid |
| Growing ecosystem | Integrated ecosystem with upstream tools like Modern.js, Rspress, Rslib, and Storybook Rsbuild |
Disadvantages and Caveats
| Item | Details | Mitigation |
|---|---|---|
| Not 100% Webpack-compatible | Specialized in-house custom plugins may not work | Review plugin compatibility list before migrating |
| Memory usage | Reports of 3–4GB additional memory usage during dev in large monorepos | Check CI memory limits; verify dev machine headroom |
| No WASM support | Browser-side WebAssembly builds not supported | Verify separately before migrating if WASM is needed |
| Node.js 18 not supported | Rsbuild 2.0 requires Node 20.19+ | Upgrade Node version in CI/CD pipeline first |
| Production artifact differences | Output may differ subtly from Webpack | Compare with bundle analyzer before deploying |
The memory issue doesn't hit home from numbers alone, but it can create genuinely painful situations in practice. There are reports of CI pipelines dying with OOM errors when migrating monorepos in the 7,000–8,000 file range to Rsbuild. The dev machine held up, but the CI container hit its memory limit. Make sure to check your CI memory limit before migrating, and if needed, respond by raising the limit or reducing build parallelism.
The Most Common Mistakes in Practice
-
Trying to port Webpack plugins without reviewing them first — Analysis tools like
webpack-bundle-analyzerneed to be replaced withrsbuild-bundle-analyzeror Rsbuild's built-in analysis features. Bringing over plugins incompatible with Rspack will block the migration entirely. It's recommended to check your plugin list first. -
Using
performance.chunkSplitconfig from Rsbuild 1.x unchanged in 2.0 — ThechunkSplitoption structure changed in Rsbuild 2.0. Bringing over 1.x config as-is may cause warnings or errors; it's worth consulting the v1 → v2 upgrade guide alongside the migration. -
Installing 2.0 without checking the Node.js version — Rsbuild 2.0 does not support Node.js 18. Even if your application code runs fine on Node 18, failing to upgrade the Node version in your CI/CD pipeline will cause failures at the deployment stage.
Closing Thoughts
At the start I asked "can you really finish it in a day?" — the answer is "it depends." For CRA-based projects or standard Webpack configs, yes, you can genuinely wrap it up in a day. If you have three or fewer custom Webpack plugins, most cases won't take more than two days. If you have many plugins or special build requirements like Workers or WASM, you'll need more time upfront for compatibility review.
Rsbuild is currently the most realistic migration path for gaining Rust bundler speed without throwing away your Webpack config investment. Migrating to Vite is particularly tricky for projects with a lot of CJS-based legacy, but Rsbuild lets you leverage your existing Webpack knowledge while pulling build speed upward.
Three steps you can start right now:
- Check your current project's Webpack plugin list and verify Rspack compatibility. The official Rsbuild migration guide has a summary of compatibility status. The fewer custom plugins you have, the lower the migration cost.
- It's recommended to try it on a side project or a single app in your monorepo first. You can start with a single line:
pnpm add -D @rsbuild/core @rsbuild/plugin-react. - It's worth comparing the output of
rsbuild buildand your existingwebpack buildwith a bundle analyzer. If artifact sizes or chunk composition differ, you can usually align them by adjustingperformance.chunkSplitsettings.
References
- Rsbuild — Migrating from Webpack | Official Docs
- Announcing Rsbuild 2.0 | Official Blog
- Announcing Rsbuild 1.0 | Official Blog
- Boosting build speed by over 50% by migrating to Rsbuild | Yellow.ai Tech Blog
- A bundler story: migrating from Webpack to Rspack | Alan Blog
- How we migrated 30+ apps from Webpack to RSBuild | sordyl.dev
- From Webpack to Rsbuild: A Migration Story | Medium
- My journey from Webpack to Vite and finally Rsbuild | GinkoNote
- Lessons learned switching to Rspack | Brian Birtles' Blog
- Build tools performance benchmarks | GitHub
- Rsbuild v1 → v2 Upgrade Guide | Official Docs