Privacy Policy© 2026 DEV BAK - TECH BLOG. All rights reserved.
DEV BAK - TECH BLOG
frontend

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
    • From webpack.config.js to rsbuild.config.ts
    • What Changed in Rsbuild 2.0 — ESM-First and Dropping Webpack Support
  • Practical Application
    • Migrating a CRA Project
    • Porting a Complex Webpack Config
    • Bulk Migration of 30 Apps in a Monorepo
  • Pros and Cons
    • Advantages
    • Disadvantages and Caveats
    • The Most Common Mistakes in Practice
  • Closing Thoughts
  • References

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.rules structure 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.

typescript
// 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.

bash
# 1. Remove existing CRA dependency
pnpm remove react-scripts
 
# 2. Install Rsbuild
pnpm add -D @rsbuild/core @rsbuild/plugin-react
json
// package.json
{
  "scripts": {
    "start": "rsbuild dev",
    "build": "rsbuild build",
    "preview": "rsbuild preview"
  }
}
typescript
// 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.html contains CRA-specific variables like %PUBLIC_URL%, the build will break. Before switching to Rsbuild, either clean up those variables from index.html or replace them with htmlRspackPlugin's templateParameters. 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.

typescript
// 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%.

typescript
// 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;
}
typescript
// 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.alias and individual apps also try to extend source.alias, using a plain { ...base, ...overrides } spread will wipe out all shared aliases. Using the mergeRsbuildConfig utility 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

  1. Trying to port Webpack plugins without reviewing them first — Analysis tools like webpack-bundle-analyzer need to be replaced with rsbuild-bundle-analyzer or 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.

  2. Using performance.chunkSplit config from Rsbuild 1.x unchanged in 2.0 — The chunkSplit option 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.

  3. 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:

  1. 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.
  2. 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.
  3. It's worth comparing the output of rsbuild build and your existing webpack build with a bundle analyzer. If artifact sizes or chunk composition differ, you can usually align them by adjusting performance.chunkSplit settings.

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
#Rsbuild#Rspack#Webpack#빌드최적화#SWC#모노레포#TypeScript#LightningCSS#ESM#HMR
Share

Table of Contents

Table of ContentsCore ConceptsWhy Rsbuild Is Fast — Rust Bundler + Integrated ToolchainFrom webpack.config.js to rsbuild.config.tsWhat Changed in Rsbuild 2.0 — ESM-First and Dropping Webpack SupportPractical ApplicationMigrating a CRA ProjectPorting a Complex Webpack ConfigBulk Migration of 30 Apps in a MonorepoPros and ConsAdvantagesDisadvantages and CaveatsThe Most Common Mistakes in PracticeClosing ThoughtsReferences

Recommended Posts

Fixing Micro Frontend Style Conflicts with CSS @layer | Cascade Layers Isolation Strategy
frontend

Fixing Micro Frontend Style Conflicts with CSS @layer | Cascade Layers Isolation Strategy

When I first worked on a Micro Frontend (MFE) project, I remember being quite surprised to see a button style deployed by Team A showing up altered on Team B's ...

June 23, 202622 min read
When Linting Gets 62x Faster, Your Development Habits Change — Migrating from ESLint to Oxlint in a Vite Project
frontend

When Linting Gets 62x Faster, Your Development Habits Change — Migrating from ESLint to Oxlint in a Vite Project

Have you ever felt that your linter is slow? I did too. I used to think waiting nearly 30 seconds every time I ran on a mid-sized React project was just normal...

June 23, 202619 min read
Tailwind v4 @container — Responsive Components That Hold Up in Sidebars and Grids Alike
frontend

Tailwind v4 @container — Responsive Components That Hold Up in Sidebars and Grids Alike

At some point in responsive UI work, you hit a wall. A card component you carefully crafted works perfectly in the main feed, but the moment you drop the same c...

June 23, 202618 min read
View Transitions API — Production Page Transition Animations Without Libraries, After Achieving Baseline 2025
frontend

View Transitions API — Production Page Transition Animations Without Libraries, After Achieving Baseline 2025

Honestly, my first reaction when I saw this API was "this actually works?" The idea of implementing app-like page transitions with just a few lines of CSS and a...

June 7, 202620 min read
HTMX 4.0 Server Rendering Patterns: Architecture Choices for Building Interactive Web Apps Without Client State
frontend

HTMX 4.0 Server Rendering Patterns: Architecture Choices for Building Interactive Web Apps Without Client State

When I first learned React, I honestly buried this question in the back of my mind: "Does clicking a single button really require this much code?" State managem...

June 7, 202622 min read
How to Reduce TTFB from 350ms to 60ms with Next.js RSC + Streaming
frontend

How to Reduce TTFB from 350ms to 60ms with Next.js RSC + Streaming

While reviewing the Core Web Vitals of a production service, I once discovered pages where TTFB was well over 400ms. Each DB query was individually fast, so I w...

June 7, 202619 min read