Skip to content

Fix: Rspack Not Working — Build Failing, Loaders Not Applying, or Dev Server Not Starting

FixDevs · (Updated: )

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 build
Error: configuration.module.rules[0].use is not a valid value

Or 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 package

Or CSS isn’t extracted into separate files:

All styles are inlined in JavaScript — no .css output file

Or 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-loader and ts-loader work 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.js options 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-plugin doesn’t work with Rspack. Use @rspack/plugin-react-refresh instead.

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.memo at 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 LoaderRspack Equivalent
babel-loaderbuiltin:swc-loader (built-in)
ts-loaderbuiltin:swc-loader (built-in)
css-loader + style-loadertype: 'css' (built-in)
file-loader / url-loadertype: 'asset' (built-in)
sass-loadersass-loader (still needed)
postcss-loaderpostcss-loader (still needed)
svg-inline-loadertype: '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 browser

Still 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles