Fix: Rspack Not Working — Build Failing, Loaders Not Applying, or Dev Server Not Starting
Part of: JavaScript & TypeScript Errors
Quick Answer
How to fix Rspack issues — configuration migration from webpack, loader compatibility, CSS extraction, module federation, React Fast Refresh, and build performance tuning.
The Problem
Rspack fails to start with a config error:
npx rspack buildError: configuration.module.rules[0].use is not a valid valueOr a webpack loader you migrated doesn’t work:
Error: Can't resolve 'babel-loader'
Module not found: Error: Package path ./loader is not exported from packageOr CSS isn’t extracted into separate files:
All styles are inlined in JavaScript — no .css output fileOr React Fast Refresh doesn’t work — the page fully reloads on every change:
[HMR] Update applied. Full reload needed.Why This Happens
Rspack is a Rust-based bundler designed as a drop-in replacement for webpack. It supports most of webpack’s configuration format, but with important differences:
- Not all webpack loaders are compatible — Rspack uses its own Rust-based implementations for common transformations (TypeScript, JSX, CSS). JavaScript-based loaders like
babel-loaderandts-loaderwork but are slower. Some loaders that depend on webpack internals don’t work at all. - Built-in features replace plugins — Rspack has native CSS support, built-in SWC for TypeScript/JSX, and integrated asset handling. Using the webpack equivalents (
css-loader,style-loader,MiniCssExtractPlugin) still works but is unnecessary and slower. - Config format is almost identical but not 100% — most
webpack.config.jsoptions work unchanged. But some plugin APIs, hook signatures, and loader option formats differ. The error messages are usually clear about what’s wrong, but the fix isn’t always obvious. - React Fast Refresh needs the Rspack plugin, not the webpack one —
@pmmmwh/react-refresh-webpack-plugindoesn’t work with Rspack. Use@rspack/plugin-react-refreshinstead.
A second category of failure is path handling between operating systems. Rspack is Rust-native and treats paths as bytes by default, while many webpack configs and JavaScript-based loaders assume Node’s path semantics. Globs that work on macOS (src/**/*.tsx) can fail to match anything on Windows when mixed slashes are introduced by path.join returning \. Always normalise to forward slashes before handing strings to Rspack rule test/include arrays.
A third category is implicit assumptions about plugin hook timing. webpack 5 plugins authored before the Rspack-compatibility era may register synchronous hooks that Rspack runs in parallel. Failing plugins usually print a hook name in the error stack — that name is the first clue. Look up whether the plugin has shipped an Rspack-compatible release before patching it yourself.
Platform and Environment Differences
Webpack plugin compatibility is the biggest migration risk. Rspack ships compatibility shims for the most-used webpack plugins (Html, Copy, Define, Provide, BannerPlugin, Module Federation) but rewrites them in Rust, so plugin options that depend on the exact JS implementation behaviour can drift. ForkTsCheckerWebpackPlugin, webpack-bundle-analyzer, and any plugin that calls compilation.hooks.processAssets.tapPromise with internal webpack utilities require Rspack-specific replacements. Before migrating, run npx @rspack/cli against your existing config and read the warnings — they list incompatible plugins explicitly.
Vue 2 versus Vue 3 loaders differ entirely. Rspack supports vue-loader@^17 for Vue 3 out of the box, but Vue 2 with vue-loader@^15 does not work — the loader’s internal calls into webpack’s deprecated APIs throw. If you need Rspack speed on a Vue 2 codebase, the practical answer is to upgrade to Vue 2.7 (which exposes the Composition API and Vue 3-compatible build) or stay on webpack until you finish the migration. Do not mix Vue 2 SFCs and Rspack in production without a thorough manual test pass.
Module Federation in Rspack uses the v1.5 spec. Federated containers built with webpack 5 Module Federation v1 still consume from Rspack hosts, but consuming a webpack remote from Rspack only works when both sides ship the same shared object shape. Singleton mismatches (singleton: true on one side, false on the other) cause silent React duplicate-instance warnings at runtime. Pin React, React DOM, and @rspack/core versions in lockstep across the federation graph.
Linux versus Windows path handling trips up CI more than dev. Linux is case-sensitive, so import './Button' works in dev (macOS or Windows) and breaks in CI. Rspack does not auto-correct case the way some webpack loaders did. Enable resolve.caseSensitive: true (or simply rely on Linux’s native enforcement) early so the build fails in dev. On Windows, long file paths over 260 characters are still a problem unless the host has long path support enabled — symptoms are ENOENT errors during the chunk emit phase that disappear on macOS.
CI cache strategies matter because Rspack’s incremental build cache lives at node_modules/.cache/rspack/. By default the cache is keyed on config content, Node version, and CWD — moving the repo path between CI jobs invalidates it. Cache the directory in GitHub Actions with actions/cache@v4 keyed on lockfile + rspack.config.js + RSPACK_CACHE_VERSION. Avoid sharing the cache across PRs because watch-mode artefacts can poison it; isolate per-branch.
Fix 1: Basic Rspack Configuration
npm install -D @rspack/core @rspack/cli// rspack.config.js
const { defineConfig } = require('@rspack/cli');
const path = require('path');
module.exports = defineConfig({
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
// Use Rspack's built-in SWC loader — much faster than babel-loader
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
transform: {
react: {
runtime: 'automatic', // React 17+ JSX transform
development: process.env.NODE_ENV === 'development',
refresh: process.env.NODE_ENV === 'development',
},
},
},
},
},
{
test: /\.css$/,
// Built-in CSS support — no css-loader or style-loader needed
type: 'css',
},
{
test: /\.(png|jpg|gif|svg|woff2?)$/,
// Built-in asset handling
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024, // Inline if under 8kb
},
},
},
],
},
// Built-in plugins — no npm install needed
plugins: [],
devServer: {
port: 3000,
hot: true,
historyApiFallback: true,
},
});Fix 2: CSS Extraction and Processing
Rspack has native CSS support — no plugins needed for basic CSS:
// rspack.config.js
module.exports = {
module: {
rules: [
// Basic CSS — extracted to separate file automatically in production
{
test: /\.css$/,
type: 'css', // Built-in CSS handling
},
// CSS Modules
{
test: /\.module\.css$/,
type: 'css/module', // Built-in CSS Modules
},
// Sass/SCSS — still needs sass-loader
{
test: /\.scss$/,
use: [
{
loader: 'sass-loader',
options: {
// Use modern API
api: 'modern-compiler',
},
},
],
type: 'css', // Output goes through built-in CSS pipeline
},
// PostCSS + Tailwind CSS
{
test: /\.css$/,
use: [
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'tailwindcss',
'autoprefixer',
],
},
},
},
],
type: 'css',
},
],
},
// For production — CSS is extracted by default
// For development — CSS is injected via style tags for HMR
experiments: {
css: true, // Enable native CSS support (default in Rspack)
},
};If you prefer the webpack-style MiniCssExtractPlugin approach:
const rspack = require('@rspack/core');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
rspack.CssExtractRspackPlugin.loader,
'css-loader',
'postcss-loader',
],
},
],
},
plugins: [
new rspack.CssExtractRspackPlugin({
filename: 'css/[name].[contenthash].css',
}),
],
};Fix 3: React Fast Refresh
HMR for React requires the Rspack-specific refresh plugin:
npm install -D @rspack/plugin-react-refresh react-refresh// rspack.config.js
const ReactRefreshPlugin = require('@rspack/plugin-react-refresh');
const isDev = process.env.NODE_ENV === 'development';
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: { syntax: 'typescript', tsx: true },
transform: {
react: {
runtime: 'automatic',
development: isDev,
refresh: isDev, // Enable refresh transform in dev
},
},
},
},
},
],
},
plugins: [
isDev && new ReactRefreshPlugin(),
].filter(Boolean),
devServer: {
hot: true,
},
};Fast Refresh won’t work if:
- Component files export non-component values (e.g., constants alongside components)
- Components are wrapped in
React.memoat the export level in some edge cases - The file uses
module.hot.accept()manually — remove it and let the plugin handle it - Class components are used — Fast Refresh only works with function components and hooks
Fix 4: Migrate from webpack
Most webpack configs work with minimal changes:
// webpack.config.js → rspack.config.js — what to change
// BEFORE (webpack)
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
module: {
rules: [
{ test: /\.tsx?$/, use: 'ts-loader' },
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
],
},
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html' }),
new MiniCssExtractPlugin(),
new CopyWebpackPlugin({ patterns: [{ from: 'public' }] }),
],
};
// AFTER (rspack)
const rspack = require('@rspack/core');
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
// Replace ts-loader with built-in SWC
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: { syntax: 'typescript', tsx: true },
transform: { react: { runtime: 'automatic' } },
},
},
},
{
test: /\.css$/,
// Replace css-loader + MiniCssExtractPlugin with built-in
type: 'css',
},
],
},
plugins: [
// HtmlWebpackPlugin → built-in HtmlRspackPlugin
new rspack.HtmlRspackPlugin({ template: './public/index.html' }),
// CopyWebpackPlugin → built-in CopyRspackPlugin
new rspack.CopyRspackPlugin({
patterns: [{ from: 'public', globOptions: { ignore: ['**/index.html'] } }],
}),
],
};Loader compatibility quick reference:
| webpack Loader | Rspack Equivalent |
|---|---|
babel-loader | builtin:swc-loader (built-in) |
ts-loader | builtin:swc-loader (built-in) |
css-loader + style-loader | type: 'css' (built-in) |
file-loader / url-loader | type: 'asset' (built-in) |
sass-loader | sass-loader (still needed) |
postcss-loader | postcss-loader (still needed) |
svg-inline-loader | type: 'asset/source' |
Fix 5: Module Federation
Rspack supports Module Federation v1.5 natively:
// rspack.config.js — host application
const rspack = require('@rspack/core');
module.exports = {
plugins: [
new rspack.container.ModuleFederationPlugin({
name: 'host',
remotes: {
// Remote app available at runtime
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
// rspack.config.js — remote application
module.exports = {
plugins: [
new rspack.container.ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./Header': './src/components/Header',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};// In host app — import remote component
const RemoteButton = React.lazy(() => import('remoteApp/Button'));
function App() {
return (
<Suspense fallback="Loading...">
<RemoteButton />
</Suspense>
);
}Fix 6: Build Performance Tuning
Rspack is fast by default, but you can optimize further:
module.exports = {
// Source maps — choose based on need
devtool: process.env.NODE_ENV === 'production'
? 'source-map' // Full source maps for production debugging
: 'cheap-module-source-map', // Faster for dev
// Caching — persistent cache across builds
cache: true, // Simple boolean or configure as object
// Optimization
optimization: {
// Split vendor chunks
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all',
},
},
},
// Tree shaking — on by default in production
usedExports: true,
sideEffects: true,
// Minimize — uses SWC minifier (faster than terser)
minimize: process.env.NODE_ENV === 'production',
},
// Resolve — limit resolution scope
resolve: {
extensions: ['.ts', '.tsx', '.js'], // Don't add unnecessary extensions
modules: ['node_modules'], // Don't search upward
},
// Stats — reduce build output noise
stats: 'errors-warnings',
};Compare build times:
# Measure build performance
npx rspack build --profile
# Analyze bundle size
npx rspack build --analyze
# Opens a bundle analyzer in the browserStill Not Working?
builtin:swc-loader throws “unexpected token” on decorators — SWC needs decorator support enabled explicitly. Add jsc.parser.decorators: true and jsc.transform.legacyDecorator: true to your builtin:swc-loader options. Make sure experimentalDecorators is also set in your tsconfig.json.
Webpack plugin X doesn’t work with Rspack — not all webpack plugins are compatible. Plugins that use compiler.hooks extensively (e.g., webpack-bundle-analyzer, ForkTsCheckerWebpackPlugin) may need Rspack-specific alternatives. Check @rspack/ scoped packages first. For webpack-bundle-analyzer, use Rspack’s built-in --analyze flag instead.
Dev server starts but page is blank — check that devServer.historyApiFallback: true is set if you’re using client-side routing. Also verify the output.publicPath matches your dev server setup. The default / works for most cases, but if your app is served from a subdirectory, set it explicitly.
CSS @import in type: 'css' doesn’t resolve node_modules — by default, Rspack’s built-in CSS resolves @import relative to the file. For @import 'package-name/styles.css', prefix with ~: @import '~package-name/styles.css'. Or switch to postcss-loader with postcss-import which resolves node_modules automatically.
Build hangs at “make” phase on Windows but works on macOS/Linux — Rspack’s file watcher uses Node’s fs.watch underneath, and Windows antivirus (Defender, CrowdStrike, SentinelOne) frequently scans large node_modules trees during the rebuild trigger. Add the project root to your antivirus exclusion list, or move node_modules to a directory the scanner skips. If you cannot change AV policy, set watchOptions.poll: 1000 to fall back to polling — slower, but immune to scanner blocking.
Module Federation host loads but remote chunks return 404 in production — the publicPath in the remote build must be the public URL where its chunks are served, not the local dev URL. Set output.publicPath: 'https://cdn.example.com/remote/' for the remote and confirm the host’s remotes entry points at the matching URL. The default auto publicPath only works when host and remote share an origin.
Tree shaking leaves dead code in the bundle — Rspack’s tree shaking is opt-in via sideEffects declarations in the producing package’s package.json. If a dependency does not declare "sideEffects": false, Rspack keeps everything to be safe. Add an override in your own package.json via optimization.sideEffects: true and audit which deps actually need side effects (CSS, polyfills) versus those that only mark themselves to placate webpack 4.
builtin:swc-loader produces wrong source maps for class methods — older Rspack versions emit a source map that misattributes class method line numbers under aggressive minification. Upgrade to the latest minor and set jsc.minify.mangle.keepClassNames: true. If you still see drift, switch the loader options to sourceMaps: 'inline' during debugging to confirm the issue is in the emit step rather than your bundler config.
For related build tool issues, see Fix: esbuild Not Working, Fix: Webpack Bundle Size Too Large, Fix: webpack Alias Not Working, and Fix: Vite Failed to Resolve Import.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Clack Not Working — Prompts Not Displaying, Spinners Stuck, or Cancel Not Handled
How to fix @clack/prompts issues — interactive CLI prompts, spinners, multi-select, confirm dialogs, grouped tasks, cancellation handling, and building CLI tools with beautiful output.
Fix: Sharp Not Working — Installation Failing, Image Not Processing, or Build Errors on Deploy
How to fix Sharp image processing issues — native binary installation, resize and convert operations, Next.js image optimization, Docker setup, serverless deployment, and common platform errors.
Fix: Shiki Not Working — No Syntax Highlighting, Wrong Theme, or Build Errors
How to fix Shiki syntax highlighter issues — basic setup, theme configuration, custom languages, transformer plugins, Next.js and Astro integration, and bundle size optimization.
Fix: Biome Not Working — Rules Not Applied, ESLint Config Not Migrated, or VSCode Extension Ignored
How to fix Biome linter/formatter issues — biome.json configuration, migrating from ESLint and Prettier, VSCode extension setup, CI integration, and rule override syntax.