← All posts
·11 min read

Claude Code with Rspack: Webpack-Compatible Builds at Rust Speed

Claude CodeRspackBuildWebpack

Why Rspack without CLAUDE.md produces webpack code by default

Rspack is a webpack-compatible bundler written in Rust. The config surface is intentionally near-identical to webpack, which means existing webpack knowledge transfers and webpack ecosystem packages mostly work. This compatibility is also a problem for Claude Code, because Claude generates webpack configs that work in Rspack but miss the performance benefits that Rspack ships natively: built-in SWC transformation, faster source map generation, parallel module processing, and the Rust-native loaders that replace common webpack plugins.

The most common Rspack mistakes Claude makes by default: configuring babel-loader for TypeScript transformation when Rspack ships SWC built in, installing mini-css-extract-plugin when CssExtractRspackPlugin is built in and faster, using webpack's HtmlWebpackPlugin when HtmlRspackPlugin is the optimised replacement, writing entry point definitions that match webpack's defaults without realising Rspack's defaults differ, and ignoring the SWC config that controls JSX and TypeScript transformation.

This guide covers the CLAUDE.md configuration that locks Claude Code into Rspack's idiomatic patterns: built-in transformers over external loaders, Rspack-native plugins over webpack equivalents, the dev server tuning that produces sub-second cold starts, and the migration mapping for projects coming from webpack. If you are building the broader React or Vue application around Rspack, Claude Code with React covers the framework patterns that Rspack will be bundling.

The Rspack CLAUDE.md template

The CLAUDE.md at your project root needs to declare which Rspack version you are using, the entry point structure, the loader and plugin choices that should default to Rspack-native versions, and the dev server configuration.

# Rspack build rules

## Stack
- @rspack/core ^1.x
- @rspack/cli ^1.x
- TypeScript 5.x strict (transformed by Rspack's built-in SWC, NOT babel-loader)
- React 19.x OR Vue 3.x OR Svelte 5.x (framework-dependent loader config)
- Node.js 20.x

## Config file
- rspack.config.ts at project root (TypeScript config, NOT .js or .mjs)
- Import { Configuration } from '@rspack/core' and type the export

## Built-in transformations (DO NOT install babel-loader)
- TypeScript: built-in via SWC ('builtin:swc-loader')
- JSX: built-in via SWC
- ES2024+ syntax: built-in via SWC
- NEVER configure babel-loader unless using a custom babel plugin Claude is aware of

## Built-in plugins (use Rspack-native versions)
- HtmlRspackPlugin (NOT HtmlWebpackPlugin)
- CssExtractRspackPlugin (NOT mini-css-extract-plugin)
- CopyRspackPlugin (NOT copy-webpack-plugin)
- ProgressPlugin (NOT webpack.ProgressPlugin)
- DefinePlugin from @rspack/core (NOT webpack.DefinePlugin)

## Dev server
- @rspack/dev-server with hot: true
- devServer.client.overlay = { errors: true, warnings: false }
- devServer.historyApiFallback = true for SPA routing
- devServer.headers for CORS in local dev only

## Hard rules
- NEVER install babel-loader, ts-loader, or terser-webpack-plugin
- NEVER use HtmlWebpackPlugin, use HtmlRspackPlugin (same API, 10x faster)
- NEVER use mini-css-extract-plugin, use CssExtractRspackPlugin (built-in)
- ALWAYS type rspack.config.ts with import type { Configuration } from '@rspack/core'
- NEVER copy a webpack.config.js verbatim without checking the Rspack-native equivalents

Three rules in this template prevent the majority of failures Claude generates without them.

The built-in SWC rule is the biggest performance win. Rspack ships SWC as a built-in loader ('builtin:swc-loader') that handles TypeScript, JSX, and modern JavaScript syntax without requiring a separate babel or ts-loader installation. Claude trained on webpack code instinctively installs babel-loader and ts-loader, which Rspack will use, but at a fraction of the speed of the built-in version. The CLAUDE.md rule "NEVER install babel-loader" prevents this entire class of regression.

The native plugins rule prevents a quieter performance loss. Plugins like HtmlWebpackPlugin and mini-css-extract-plugin work in Rspack via the compatibility layer, but they run as JavaScript code through Node.js. The Rspack-native versions (HtmlRspackPlugin, CssExtractRspackPlugin) run in Rust and are significantly faster. The API is intentionally identical, so the swap is one line of code.

The TypeScript config file rule is small but consistent. Writing rspack.config.ts instead of rspack.config.js enables type checking on the config itself, which catches typos, deprecated options, and structural mistakes before the first build. Claude often writes .js configs because that is what most webpack tutorials show.

Install and basic config

Install Rspack:

npm i -D @rspack/core @rspack/cli @rspack/dev-server

Create a TypeScript config:

// rspack.config.ts
import path from 'node:path';
import type { Configuration } from '@rspack/core';
import {
  HtmlRspackPlugin,
  CssExtractRspackPlugin,
  ProgressPlugin,
  DefinePlugin,
} from '@rspack/core';

const isProduction = process.env.NODE_ENV === 'production';

const config: Configuration = {
  mode: isProduction ? 'production' : 'development',
  entry: './src/index.tsx',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: isProduction
      ? 'assets/[name].[contenthash].js'
      : 'assets/[name].js',
    publicPath: '/',
    clean: true,
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx|js|jsx)$/,
        exclude: /node_modules/,
        loader: 'builtin:swc-loader',
        options: {
          jsc: {
            parser: {
              syntax: 'typescript',
              tsx: true,
            },
            transform: {
              react: {
                runtime: 'automatic',
                refresh: !isProduction,
              },
            },
            target: 'es2022',
          },
        },
      },
      {
        test: /\.css$/,
        use: [
          isProduction ? CssExtractRspackPlugin.loader : 'style-loader',
          'css-loader',
        ],
      },
      {
        test: /\.(png|jpg|jpeg|webp|svg|gif|ico)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024,
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlRspackPlugin({
      template: './src/index.html',
      inject: 'body',
    }),
    new CssExtractRspackPlugin({
      filename: 'assets/[name].[contenthash].css',
    }),
    new DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV ?? 'development'),
    }),
    new ProgressPlugin({}),
  ],
  devServer: {
    port: 3000,
    historyApiFallback: true,
    hot: true,
    client: {
      overlay: {
        errors: true,
        warnings: false,
      },
    },
  },
  performance: {
    hints: isProduction ? 'warning' : false,
    maxAssetSize: 500_000,
    maxEntrypointSize: 500_000,
  },
};

export default config;

A few details Claude tends to get wrong without explicit guidance.

The 'builtin:swc-loader' string is the magic identifier that tells Rspack to use the built-in SWC loader rather than looking for swc-loader in node_modules. Claude often writes 'swc-loader' (without the builtin: prefix) which then fails to resolve unless @swc/loader is installed.

The jsc.transform.react.refresh: !isProduction flag enables React Fast Refresh in dev mode. This is the equivalent of react-refresh-webpack-plugin in webpack, and it is built into the SWC loader. Claude often installs the webpack plugin and configures it separately, which works but is slower.

The type: 'asset' rule with dataUrlCondition.maxSize: 8 * 1024 is the modern asset handling pattern. Files smaller than 8KB are inlined as data URLs, larger files are emitted as separate assets. Claude trained on older webpack docs reaches for url-loader and file-loader, which Rspack supports but does not need.

Add a config structure section to CLAUDE.md:

## Config structure

- rspack.config.ts with typed Configuration export
- Use 'builtin:swc-loader' (with builtin: prefix)
- jsc.transform.react.refresh for React Fast Refresh in dev
- type: 'asset' for images (replaces url-loader + file-loader)
- mode: 'production' | 'development' driven by NODE_ENV
- output.clean: true (removes dist/ contents between builds)
- resolve.alias for path shortcuts (mirror tsconfig.json paths)

SWC configuration deep dive

The SWC loader exposes a JSC (JavaScript Compiler) config that controls how source files are transformed. Claude often uses the defaults, which are reasonable but not optimal for most projects.

{
  loader: 'builtin:swc-loader',
  options: {
    jsc: {
      parser: {
        syntax: 'typescript',
        tsx: true,
        decorators: false,           // Set true if using class decorators (NestJS, TypeORM)
        dynamicImport: true,
      },
      transform: {
        react: {
          runtime: 'automatic',      // Use the JSX runtime (no React import needed)
          development: !isProduction,
          refresh: !isProduction,    // React Fast Refresh in dev
          throwIfNamespace: true,
        },
        legacyDecorator: false,
        decoratorMetadata: false,
      },
      target: 'es2022',              // Match your tsconfig.json target
      loose: false,                  // Stricter spec compliance
      externalHelpers: false,        // Set true with @swc/helpers installed for smaller bundles
    },
    isModule: true,
  },
}

The target: 'es2022' setting should match the target in your tsconfig.json. Mismatches cause subtle bugs where TypeScript expects modern syntax to be preserved but SWC down-levels it (or vice versa). Claude often leaves these out of sync because the two configs are in separate files.

For projects using decorators (NestJS, TypeORM, class-validator), the SWC config needs to enable decorator support explicitly:

{
  jsc: {
    parser: { syntax: 'typescript', decorators: true },
    transform: {
      legacyDecorator: true,
      decoratorMetadata: true,
    },
  },
}

Add a SWC section to CLAUDE.md:

## SWC configuration

- target: 'es2022' (match tsconfig.json target exactly)
- transform.react.runtime: 'automatic' (new JSX transform, no React import)
- transform.react.refresh: !isProduction (Fast Refresh in dev)
- For decorators: parser.decorators = true, transform.legacyDecorator = true
- externalHelpers: true requires @swc/helpers installed, reduces bundle size 5-15%
- NEVER set loose: true unless you understand the spec deviations

Module Federation in Rspack

Rspack supports Module Federation for sharing code between independently deployed builds. The plugin API mirrors webpack's ModuleFederationPlugin with one important difference: Rspack ships its own implementation that does not require the webpack plugin.

import { ModuleFederationPlugin } from '@rspack/core';

const config: Configuration = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        admin: 'admin@http://localhost:3001/remoteEntry.js',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^19.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^19.0.0' },
      },
    }),
  ],
};

The singleton: true flag for shared dependencies prevents multiple copies of React (or any singleton library) from being loaded across federated modules. Without it, you can end up with two copies of React that produce confusing hook errors. Claude often omits the singleton flag because it is not required for simpler examples.

Get Claudify. The Rspack-aware CLAUDE.md template ships with Module Federation patterns and built-in plugin defaults.

Add a Module Federation section to CLAUDE.md only if your project uses it:

## Module Federation

- Use ModuleFederationPlugin from @rspack/core (NOT webpack equivalent)
- ALWAYS set singleton: true for React, ReactDOM, and other framework singletons
- Match requiredVersion to your package.json version range
- remoteEntry.js URLs use [name]@[url] format
- For dynamic remote loading: use the runtime API, not config-time strings

Dev server optimisation

Rspack's biggest advantage over webpack is cold start time. A medium-sized React project that takes 8 to 12 seconds to start with webpack typically starts in 1 to 2 seconds with Rspack. This is largely automatic, but a few config choices preserve or destroy the gain.

devServer: {
  port: 3000,
  host: '0.0.0.0',                    // Required for Docker, network access from phone
  historyApiFallback: true,
  hot: true,                          // Hot Module Replacement enabled
  liveReload: false,                  // Disable, hot: true is sufficient
  compress: true,                     // gzip responses
  client: {
    overlay: {
      errors: true,
      warnings: false,                // Warnings only in terminal, not over UI
      runtimeErrors: true,
    },
    progress: true,
  },
  static: {
    directory: path.resolve(__dirname, 'public'),
    publicPath: '/',
  },
  proxy: [
    {
      context: ['/api'],
      target: 'http://localhost:8080',
      changeOrigin: true,
    },
  ],
},

The liveReload: false flag is worth a note. With HMR enabled, the live reload fallback is redundant and can cause double refreshes when HMR fails to apply a change. Claude often leaves both enabled because they are independent options.

The compress: true option enables gzip on dev server responses, which makes large bundles (over 1MB) noticeably faster to load. Webpack omits this by default; Rspack's dev server respects it.

For projects with a backend API, the proxy config routes API requests through the dev server, avoiding CORS configuration in local development:

## Dev server

- host: '0.0.0.0' for Docker / network testing
- hot: true (HMR), liveReload: false (avoid double refresh)
- compress: true for gzip
- client.overlay: errors only, hide warnings (terminal is the right place for warnings)
- proxy: route /api to backend dev server, avoids CORS config
- historyApiFallback: true for SPA routing
- static.directory: 'public' for files served as-is

Migration mapping from webpack

For projects coming from webpack, the table below maps common webpack plugins to their Rspack-native equivalents. Use the Rspack version wherever it exists.

webpack Rspack-native equivalent
HtmlWebpackPlugin HtmlRspackPlugin (built-in)
mini-css-extract-plugin CssExtractRspackPlugin (built-in)
copy-webpack-plugin CopyRspackPlugin (built-in)
webpack.DefinePlugin DefinePlugin from @rspack/core
webpack.ProgressPlugin ProgressPlugin from @rspack/core
webpack-bundle-analyzer Same package, fully compatible
terser-webpack-plugin Built-in via SWC minify, no plugin needed
babel-loader 'builtin:swc-loader'
ts-loader 'builtin:swc-loader'
react-refresh-webpack-plugin Built-in via SWC transform.react.refresh
webpack-merge Same package, fully compatible
dotenv-webpack DefinePlugin with process.env values

Add a migration section to CLAUDE.md only for projects migrating from webpack:

## webpack to Rspack migration

- Replace babel-loader and ts-loader with 'builtin:swc-loader'
- Replace HtmlWebpackPlugin with HtmlRspackPlugin (same API)
- Replace mini-css-extract-plugin with CssExtractRspackPlugin (same API)
- Replace copy-webpack-plugin with CopyRspackPlugin (same API)
- DefinePlugin: import from @rspack/core, not webpack
- Drop terser-webpack-plugin entirely (SWC minifier is built-in)
- Drop react-refresh-webpack-plugin (built into SWC loader)
- Check plugin compatibility before keeping: most webpack plugins work but may be slow
- Webpack-merge and bundle-analyzer work unchanged

Common Claude Code mistakes with Rspack

Six patterns Claude generates incorrectly without CLAUDE.md constraints, with the correct replacement for each.

1. babel-loader for TypeScript

Claude generates: a babel-loader rule with @babel/preset-typescript and @babel/preset-react.

Correct pattern: 'builtin:swc-loader' with the appropriate JSC config. Faster, no extra dependencies.

2. HtmlWebpackPlugin

Claude generates: new HtmlWebpackPlugin({ template: './src/index.html' }).

Correct pattern: new HtmlRspackPlugin({ template: './src/index.html' }). Identical API.

3. Missing builtin: prefix on swc-loader

Claude generates: loader: 'swc-loader' (no prefix), causing module resolution failure.

Correct pattern: loader: 'builtin:swc-loader'.

4. Separate terser-webpack-plugin

Claude generates: new TerserPlugin() in optimisation.minimizer.

Correct pattern: drop the plugin entirely, SWC minifier runs automatically in production mode.

5. JavaScript config file

Claude generates: rspack.config.js with module.exports.

Correct pattern: rspack.config.ts with typed default export. Catches config typos at compile time.

6. Mismatched SWC target

Claude generates: target: 'es2015' in SWC config while tsconfig.json has target: 'es2022'.

Correct pattern: keep SWC target and tsconfig.json target identical to avoid silent down-levelling.

Add a common mistakes section to CLAUDE.md with these six pairs. Claude reproduces patterns from CLAUDE.md examples more reliably than from abstract rules.

Permission hooks for build scripts

A Rspack project accumulates scripts: production builds, bundle analysis, deploy scripts, asset uploads. Some are safe to run repeatedly, some interact with production.

In .claude/settings.local.json:

{
  "permissions": {
    "allow": [
      "Bash(npm run dev*)",
      "Bash(npm run build*)",
      "Bash(npm run analyze*)",
      "Bash(npm run lint*)"
    ],
    "deny": [
      "Bash(npm run deploy*)",
      "Bash(npm run release*)",
      "Bash(aws s3 sync*)",
      "Bash(gcloud storage cp*)"
    ]
  }
}

Builds and analysis are safe to run. Deploys to production buckets, releases, and direct cloud uploads should require confirmation. For broader CI patterns, Claude Code with Vercel covers the deploy hooks that should fire after a successful Rspack build.

Building bundles that are actually fast

The Rspack CLAUDE.md in this guide produces builds where the built-in SWC loader handles transformation, Rspack-native plugins replace their webpack equivalents, the config file is typed TypeScript, dev server cold starts complete in under 2 seconds, and the migration mapping from webpack is documented for any plugin Claude reaches for.

The underlying principle is the same as any tooling integration with Claude Code. Rspack without a CLAUDE.md produces working builds that miss the Rust-speed performance Rspack ships, because Claude reaches for webpack patterns that compile but run slower than the native alternatives. The CLAUDE.md template removes each failure mode by making the Rspack-native pattern the only pattern Claude generates.

For the next layer of the stack, Rspack works alongside Claude Code with TypeScript for type-safe config files, and Claude Code with Tailwind for CSS pipeline integration that takes full advantage of Rspack's CSS handling.

Get Claudify. Drop an Rspack-aware CLAUDE.md into your project and ship builds at Rust speed.

More like this

Ready to upgrade your Claude Code setup?

Get Claudify
Featured on Dofollow.Tools AI Toolz Dir Claudify - Featured on Startup Fame