Webpack을 Rsbuild로 옮겼더니 프로덕션 빌드가 74% 빨라졌다 — 마이그레이션 현실과 Rspack 함정
솔직히 저도 처음엔 "또 새로운 빌드 도구야?"라는 반응이었습니다. Vite가 나왔을 때도 그랬고, Turbopack 발표 때도 그랬죠. 그런데 Alan 핀테크 팀의 사례를 보고 나서는 그냥 지나칠 수가 없었습니다. 가장 복잡한 프로젝트에서 프로덕션 빌드 시간이 2분 10초에서 34초로 줄었다는 것, 그게 설정을 크게 뒤집지 않고 나온 결과라는 것이요.
이 글은 Webpack을 1년 이상 써온 프론트엔드 개발자가 Rsbuild로 전환하면서 실제로 어디서 막히고, 어디서 시간을 아끼는지를 솔직하게 정리한 글입니다. Rsbuild가 빠른 원리, 마이그레이션에서 놓치기 쉬운 함정, 그리고 2.0 변경 사항까지 다룹니다. Vite처럼 코드를 전면 재구성할 자신은 없지만 빌드 속도는 포기하기 싫은 분께 특히 도움이 될 겁니다.
그래서, 진짜로 하루 만에 끝낼 수 있을까요?
목차
핵심 개념
Rsbuild가 빠른 이유 — Rust 번들러 + 통합 툴체인
Rsbuild는 ByteDance 웹 인프라 팀이 만든 빌드 도구로, Rust로 구현된 Webpack 호환 번들러인 Rspack 위에서 동작합니다. 속도 비결은 세 가지 도구의 조합입니다. 번들링은 Rspack(Webpack 호환 Rust 구현체), 트랜스파일은 SWC(Babel 대체), CSS 최소화는 Lightning CSS가 각각 담당합니다. 세 도구 모두 Rust로 작성됐고, Rsbuild가 이를 Zero-config로 묶어줍니다.
Rspack은 Webpack과 동일한 플러그인 API와
module.rules구조를 유지하면서 내부를 Rust로 재구현한 번들러입니다. 기존 Webpack 로더·플러그인 대부분을 그대로 사용할 수 있는 이유입니다.
한 가지 짚고 넘어갈 게 있습니다. Lightning CSS는 "PostCSS 대체"로 소개되는 경우가 많은데, 정확히는 다릅니다. Rsbuild의 기본 CSS 변환은 여전히 PostCSS를 거칩니다. Lightning CSS는 주로 CSS 최소화(minification) 단계에서 사용됩니다. PostCSS를 완전히 Lightning CSS로 대체하려면 tools.lightningcssLoader를 명시적으로 설정해야 하고, 일부 PostCSS 플러그인은 아직 대체 불가능합니다. "기본으로 PostCSS가 사라진다"고 오해하고 설정을 지워버리면 낭패를 봅니다.
Vite와 비교했을 때 실무적으로 가장 중요한 차이는 이겁니다. Vite는 개발 서버에서 ESM 기반 No-bundle 방식을 쓰고 프로덕션에서는 Rollup을 씁니다. 이 dev/prod 불일치 때문에 "로컬에선 됐는데 빌드하니 깨진다"는 상황이 생기죠. Rsbuild는 개발과 프로덕션 모두 Rspack을 씁니다. 저도 이 부분에서 Vite를 한 번 포기한 적이 있었는데, 환경 간 동작 일관성이 보장된다는 점이 실무에서는 생각보다 훨씬 큰 안도감을 줍니다.
webpack.config.js에서 rsbuild.config.ts로
마이그레이션의 시작점은 설정 파일 교체입니다. 기존 Webpack 설정 중 Rspack과 호환되는 부분은 tools.rspack으로 그대로 넘길 수 있습니다.
// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
export default defineConfig({
plugins: [pluginReact()],
tools: {
rspack: {
// 기존 webpack.config.js의 module.rules, resolve 등 이관 가능
module: {
rules: [
{
test: /\.svg$/,
use: ['@svgr/webpack'],
},
],
},
},
},
output: {
distPath: {
root: 'dist',
},
},
});webpack.config.js와 비교하면 구조가 익숙합니다. entry, output, resolve, module.rules 개념이 그대로 살아있고, TypeScript First 설정 파일이라 타입 힌트도 잘 됩니다.
Rsbuild 2.0의 변화 — ESM-First와 Webpack 지원 종료
2025년 상반기에 출시된 Rsbuild 2.0은 생태계 방향을 명확히 했습니다.
| 항목 | 1.x | 2.0 |
|---|---|---|
| 기반 번들러 | Rspack 1.x | Rspack 2.0 |
| 패키지 형식 | CJS + ESM | 순수 ESM (설치 용량 ~500KB 감소, tree-shaking 효율 개선) |
| Node.js 최소 요구 | 18+ | 20.19+ 또는 22.12+ |
| Webpack 번들러 지원 | 지원 | 완전 종료 |
| React Server Components | 미지원 | 실험적 지원 (rsbuild-plugin-rsc) |
"순수 ESM"이 중요한 이유는 단순히 용량 때문만이 아닙니다. ESM은 정적 분석이 가능해서 빌드 툴이 사용하지 않는 코드를 더 정확하게 제거할 수 있고(tree-shaking), CJS에서 흔히 발생하는 interop 이슈도 줄어듭니다.
2.0에서 Webpack 번들러 지원이 완전히 종료됐다는 점이 역설적으로 중요합니다. "Rsbuild를 쓰면서 Webpack 호환을 유지한다"는 전략은 1.x에서는 가능했지만, 2.0부터는 Rspack 방식으로 완전히 전환해야 합니다. 이 전환 작업이 바로 "Webpack 레거시 전환"의 핵심 과제가 됩니다.
실전 적용
CRA 프로젝트 마이그레이션
Yellow.ai가 CRA 기반 프로젝트를 Rsbuild로 전환해 빌드 시간을 50% 이상 단축한 사례가 있습니다. CRA를 쓰고 있다면 마이그레이션이 가장 단순합니다. eject하지 않았다면 rsbuild.config.ts 하나로 대체할 수 있습니다.
# 1. 기존 CRA 의존성 제거
pnpm remove react-scripts
# 2. Rsbuild 설치
pnpm add -D @rsbuild/core @rsbuild/plugin-react// package.json
{
"scripts": {
"start": "rsbuild dev",
"build": "rsbuild build",
"preview": "rsbuild preview"
}
}// rsbuild.config.ts — CRA 대체 최소 설정
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
export default defineConfig({
plugins: [pluginReact()],
html: {
// CRA의 %PUBLIC_URL% 변수는 Rsbuild에서 자동 처리되지 않습니다.
// index.html에서 해당 변수를 제거하거나, htmlRspackPlugin의
// templateParameters로 직접 주입하는 처리가 필요합니다.
template: './public/index.html',
},
source: {
entry: {
index: './src/index.tsx',
},
},
});주의: CRA의
public/index.html에%PUBLIC_URL%같은 CRA 전용 변수가 있다면 빌드가 깨집니다. Rsbuild로 전환하기 전에index.html에서 해당 변수를 정리하거나,htmlRspackPlugin의templateParameters로 대체 처리가 필요합니다. 이 부분이 CRA 이관에서 가장 흔하게 막히는 지점입니다.
복잡한 Webpack 설정 이관
Alan 핀테크 팀은 SVG 처리, 커스텀 로더, 환경변수 주입 등 다양한 설정이 얽힌 프로젝트를 이관했습니다. 결과가 인상적이었습니다.
| 지표 | 전 (Webpack) | 후 (Rsbuild) | 개선율 |
|---|---|---|---|
| 초기 dev 빌드 | 16.17초 | 5.99초 | -63% |
| HMR 응답 시간 | 2.90초 | 700ms | -76% |
| 프로덕션 빌드 | 2분 10초 | 34초 | -74% |
가장 체감이 확실한 건 HMR이었습니다. 2.9초에서 700ms — 저장하고 화면으로 시선을 옮기면 이미 반영돼 있습니다. 하루 종일 수백 번 저장하는 개발 사이클에서 이 차이는 생각보다 훨씬 크게 느껴집니다.
설정 이관 전략은 tools.rspack을 활용하는 것입니다.
// rsbuild.config.ts — 복잡한 설정 이관 예시
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginSvgr } from '@rsbuild/plugin-svgr';
export default defineConfig({
plugins: [
pluginReact(),
pluginSvgr(), // webpack에서 @svgr/webpack을 쓰던 부분 대체
],
source: {
define: {
// webpack의 DefinePlugin 역할
'process.env.API_URL': JSON.stringify(process.env.API_URL),
},
alias: {
// webpack의 resolve.alias 역할
'@components': './src/components',
'@utils': './src/utils',
},
},
tools: {
rspack: (config) => {
// Rspack에 아직 내장 지원이 없는 커스텀 로더는 여기서 추가
// 단, 로더가 Rspack과 호환되는지 반드시 사전에 확인이 필요합니다
config.module?.rules?.push({
test: /\.worker\.ts$/,
use: [{ loader: 'worker-loader' }],
});
return config;
},
},
performance: {
chunkSplit: {
strategy: 'split-by-experience',
},
},
});Webpack 설정 항목별 대응표를 보면 이관이 그렇게 낯설지 않다는 걸 알 수 있습니다.
| 설정 항목 | Webpack | Rsbuild |
|---|---|---|
| 환경변수 주입 | DefinePlugin |
source.define |
| 경로 별칭 | resolve.alias |
source.alias |
| SVG React 컴포넌트 | @svgr/webpack 로더 |
pluginSvgr() |
| 청크 분리 | optimization.splitChunks |
performance.chunkSplit |
| 커스텀 로더 | module.rules |
tools.rspack 콜백 |
모노레포 30개 앱 일괄 전환
여러 앱을 한 번에 전환할 때는 공통 설정을 패키지로 분리하는 방식이 효과적입니다. 30개 이상의 앱을 이 방식으로 일괄 전환하여 로컬 빌드 40%, CI 빌드 30%를 단축한 사례가 있습니다.
// packages/build-config/src/index.ts — 공통 설정 패키지
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',
},
},
});
// 스프레드 대신 mergeRsbuildConfig 사용:
// source.alias처럼 중첩 키가 있을 때 단순 스프레드로는 덮어써집니다.
return overrides ? mergeRsbuildConfig(base, overrides) : base;
}// apps/dashboard/rsbuild.config.ts — 각 앱에서 확장
import { createBaseConfig } from '@company/build-config';
export default createBaseConfig({
server: { port: 3001 },
source: {
entry: { index: './src/main.tsx' },
},
});딥머지 주의: 공통 설정에
source.alias가 있고 각 앱에서도source.alias를 확장하려 할 때,{ ...base, ...overrides }방식의 단순 스프레드를 쓰면 공통 alias가 전부 사라집니다.mergeRsbuildConfig유틸리티를 사용하면 중첩 객체를 안전하게 병합할 수 있습니다.
공통 설정 패키지 하나만 업데이트하면 전체에 반영되는 구조라 유지보수도 편합니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 빌드 속도 | 대규모 프로젝트 기준 Webpack 대비 60~76% 빌드 시간 단축 (프로젝트 규모·설정에 따라 편차 있음) |
| 마이그레이션 비용 | Vite 전환보다 훨씬 작은 코드 변경, 기존 로더·플러그인 대부분 재사용 가능 |
| dev/prod 일관성 | 개발·프로덕션 모두 Rspack 사용 → 환경 간 동작 차이 없음 |
| Zero-config DX | TypeScript·HMR 내장, React/Vue/Svelte/Solid 플러그인 공식 지원 |
| 생태계 성장 | Modern.js·Rspress·Rslib·Storybook Rsbuild 등 상위 도구와 통합 생태계 형성 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 100% Webpack 호환 불가 | 특수한 사내 커스텀 플러그인이 동작하지 않을 수 있음 | 마이그레이션 전 플러그인 호환성 목록 사전 검토 |
| 메모리 사용량 | 대규모 모노레포에서 개발 중 3~4GB 추가 메모리 사용 보고 | CI 메모리 한도 점검, 개발 머신 여유 확인 |
| WASM 미지원 | 브라우저 사이드 WebAssembly 빌드 미지원 | WASM이 필요하면 전환 전 별도 확인 필요 |
| Node.js 18 미지원 | Rsbuild 2.0부터 Node 20.19+ 필요 | CI/CD 파이프라인 Node 버전 업그레이드 선행 |
| 프로덕션 아티팩트 차이 | Webpack 출력과 미세하게 달라질 수 있음 | 번들 분석기로 사전 비교 후 적용 |
메모리 문제는 숫자만 봐선 와닿지 않는데, 실제로는 꽤 뼈아픈 상황을 만들 수 있습니다. 7,000~8,000개 파일 규모의 모노레포에서 Rsbuild로 전환했을 때 CI 파이프라인이 OOM으로 죽는 경우가 보고됐습니다. 개발 머신은 버텼는데 CI 컨테이너가 메모리 한도에 걸린 거죠. 전환 전에 CI 메모리 한도를 반드시 확인하고, 필요하다면 한도를 올리거나 빌드 병렬도를 줄이는 방향으로 대응할 수 있습니다.
실무에서 가장 흔한 실수
-
Webpack 플러그인을 검토 없이 이관하려는 시도 —
webpack-bundle-analyzer같은 분석 도구는rsbuild-bundle-analyzer나 Rsbuild 내장 분석 기능으로 교체해야 합니다. Rspack과 호환되지 않는 플러그인을 그대로 들고 오면 이관 자체가 막힙니다. 플러그인 목록부터 먼저 확인하는 것을 권장합니다. -
performance.chunkSplit설정을 Rsbuild 2.0에서 그대로 쓰는 경우 — Rsbuild 2.0에서chunkSplit옵션 구조가 변경됐습니다. 1.x 설정을 그대로 가져오면 경고나 오류가 발생할 수 있어, v1 → v2 업그레이드 가이드를 함께 참조하는 것이 좋습니다. -
Node.js 버전 확인 없이 2.0 설치 — Rsbuild 2.0은 Node.js 18을 지원하지 않습니다. 애플리케이션 코드는 Node 18에서 잘 돌아가더라도, CI/CD 파이프라인의 Node 버전까지 함께 업그레이드하지 않으면 배포 단계에서 실패할 수 있습니다.
마치며
도입부에서 "하루 만에 끝낼 수 있냐"고 물었는데, 답은 "경우에 따라 다르다"입니다. CRA 기반 프로젝트나 표준적인 Webpack 설정이라면 정말로 하루 안에 끝납니다. 커스텀 Webpack 플러그인이 3개 이하라면 대부분 이틀을 넘기지 않습니다. 플러그인이 많거나 Worker·WASM처럼 특수한 빌드 요구사항이 있다면 사전 호환성 검토에 더 시간이 필요합니다.
Rsbuild는 Webpack 설정 자산을 버리지 않으면서 Rust 번들러의 속도를 얻을 수 있는, 현재로선 가장 현실적인 전환 경로입니다. Vite 전환은 CJS 기반 레거시가 많은 프로젝트에서 특히 까다롭지만, Rsbuild는 기존 Webpack 지식을 최대한 활용하면서 속도를 끌어올릴 수 있습니다.
지금 바로 시작해볼 수 있는 3단계:
- 현재 프로젝트의 Webpack 플러그인 목록을 확인하고 Rspack 호환 여부를 점검해볼 수 있습니다. Rsbuild 공식 마이그레이션 가이드에 호환 현황이 정리돼 있습니다. 커스텀 플러그인이 없거나 적을수록 전환 비용이 낮습니다.
- 사이드 프로젝트나 모노레포의 앱 하나에 먼저 적용해보는 것을 권장합니다.
pnpm add -D @rsbuild/core @rsbuild/plugin-react한 줄로 시작할 수 있습니다. rsbuild build와 기존webpack build의 결과물을 번들 분석기로 비교해보시면 좋습니다. 아티팩트 크기나 청크 구성이 달라졌다면performance.chunkSplit설정을 조정하는 것으로 대부분 맞출 수 있습니다.
참고 자료
- Rsbuild — Migrating from Webpack | 공식 문서
- Announcing Rsbuild 2.0 | 공식 블로그
- Announcing Rsbuild 1.0 | 공식 블로그
- 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 업그레이드 가이드 | 공식 문서