Getting Started
    Core Concepts
    Building Features
    API Reference
    Configuration
    Deployment
    Upgrading
    Migrating
    Pro
    Misc
    Shakacode logoShakaCodeDeveloped by

    Copyright 2026 ShakaCode

    Press Ctrl+K to search anytime
    Burger
    ShakaCode WebsiteCommunityReact on Rails ProGithubSmallView on Github
    Documentation
      Getting Started
      Core Concepts
      Building Features
      API Reference
      Configuration
      Deployment
      Upgrading
      Migrating
      Pro
      Misc
      Shakacode logoShakaCodeDeveloped by

      Copyright 2026 ShakaCode

      Migrating from Webpack to Rspack

      This guide documents the process of migrating from Webpack to Rspack using Shakapacker 9+. Rspack is a high-performance bundler written in Rust that provides significantly faster builds (~20x improvement) while maintaining Webpack API compatibility.

      Prerequisites

      • Shakapacker 9.0+ (Rspack support was added in 9.0)
      • Node.js 20+
      • React on Rails 13+

      Quick Start

      For new projects or simple migrations, the generator handles most of the setup:

      # Generate with Rspack from scratch
      rails generate react_on_rails:install --rspack
      
      # Or switch an existing app
      bin/switch-bundler rspack

      For complex projects with SSR, CSS Modules, or custom configurations, continue reading for important considerations.

      Breaking Changes in Shakapacker 9

      CSS Modules: Named vs Default Exports

      This is the most critical breaking change. Shakapacker 9 changed the default CSS Modules configuration from default exports to named exports (namedExport: true).

      Symptoms:

      • CSS modules returning undefined
      • SSR errors: Cannot read properties of undefined (reading 'className')
      • Build warnings: export 'default' (imported as 'css') was not found

      Affected code pattern:

      // This pattern breaks with Shakapacker 9 defaults
      import css from './Component.module.scss';
      console.log(css.myClass); // undefined!

      Solution: Configure CSS loader to use default exports in your webpack configuration:

      // config/webpack/commonWebpackConfig.js
      const { generateWebpackConfig, merge } = require('shakapacker');
      
      const commonWebpackConfig = () => {
        const baseWebpackConfig = generateWebpackConfig();
      
        // Fix CSS modules to use default exports for backward compatibility
        baseWebpackConfig.module.rules.forEach((rule) => {
          if (rule.use && Array.isArray(rule.use)) {
            const cssLoader = rule.use.find((loader) => {
              const loaderName = typeof loader === 'string' ? loader : loader?.loader;
              return loaderName?.includes('css-loader');
            });
      
            if (cssLoader?.options?.modules) {
              cssLoader.options.modules.namedExport = false;
              cssLoader.options.modules.exportLocalsConvention = 'camelCase';
            }
          }
        });
      
        return baseWebpackConfig;
      };
      
      module.exports = commonWebpackConfig;

      Key insight: This configuration must be inside the function so it applies to a fresh config each time.

      Rspack-Specific Configuration

      Bundler Auto-Detection Pattern

      Use conditional logic to support both Webpack and Rspack in the same configuration files:

      // config/webpack/commonWebpackConfig.js
      const { config } = require('shakapacker');
      
      // Auto-detect bundler from shakapacker config
      const bundler = config.assets_bundler === 'rspack' ? require('@rspack/core') : require('webpack');
      
      // Use for plugins that need the bundler reference
      clientConfig.plugins.push(
        new bundler.ProvidePlugin({
          /* ... */
        }),
      );
      serverConfig.plugins.unshift(new bundler.optimize.LimitChunkCountPlugin({ maxChunks: 1 }));

      Benefits:

      • Single codebase for both bundlers
      • Easy to compare configurations
      • Clear visibility of bundler-specific differences

      Server Bundle: CSS Extract Plugin Filtering

      Rspack uses a different CSS extract loader path than Webpack. Server-side rendering configs that filter out CSS extraction must handle both:

      // config/webpack/serverWebpackConfig.js
      const configureServer = (serverWebpackConfig) => {
        serverWebpackConfig.module.rules.forEach((rule) => {
          if (rule.use && Array.isArray(rule.use)) {
            // Filter out CSS extraction loaders for SSR
            rule.use = rule.use.filter((item) => {
              let testValue;
              if (typeof item === 'string') {
                testValue = item;
              } else if (typeof item.loader === 'string') {
                testValue = item.loader;
              }
      
              // Handle both Webpack and Rspack CSS extract loaders
              return !(
                testValue?.match(/mini-css-extract-plugin/) ||
                testValue?.includes('cssExtractLoader') || // Rspack uses this path
                testValue === 'style-loader'
              );
            });
          }
        });
      };

      Why this matters: Rspack uses @rspack/core/dist/cssExtractLoader.js instead of Webpack's mini-css-extract-plugin. Without this fix, CSS extraction remains in the server bundle, causing intermittent SSR failures.

      Server Bundle: Preserve CSS Modules Configuration

      When configuring SSR, merge CSS modules options instead of replacing them:

      // ❌ Wrong - overwrites namedExport setting
      if (cssLoader && cssLoader.options) {
        cssLoader.options.modules = { exportOnlyLocals: true };
      }
      
      // ✅ Correct - preserves existing settings
      if (cssLoader && cssLoader.options && cssLoader.options.modules) {
        cssLoader.options.modules = {
          ...cssLoader.options.modules, // Preserve namedExport: false
          exportOnlyLocals: true,
        };
      }

      React Runtime Configuration

      SWC React Runtime for SSR

      If using SWC (common with Rspack), you may need to use the classic React runtime for SSR compatibility:

      // config/swc.config.js
      const customConfig = {
        options: {
          jsc: {
            transform: {
              react: {
                runtime: 'classic', // Use 'classic' instead of 'automatic' for SSR
                refresh: env.isDevelopment && env.runningWebpackDevServer,
              },
            },
          },
        },
      };

      Symptom: SSR error about invalid renderToString call or function signature detection issues.

      Additional Configuration

      ReScript Support

      If using ReScript, add .bs.js to resolve extensions:

      // config/webpack/commonWebpackConfig.js
      const commonOptions = {
        resolve: {
          extensions: ['.css', '.ts', '.tsx', '.bs.js'],
        },
      };

      Development Hot Reloading

      Different plugins are required for hot reloading:

      // config/webpack/development.js
      const { config } = require('shakapacker');
      
      if (config.assets_bundler === 'rspack') {
        const ReactRefreshPlugin = require('@rspack/plugin-react-refresh');
        clientWebpackConfig.plugins.push(new ReactRefreshPlugin());
      } else {
        const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
        clientWebpackConfig.plugins.push(new ReactRefreshWebpackPlugin());
      }

      Shakapacker Configuration

      Enable Rspack in config/shakapacker.yml:

      default: &default
        assets_bundler: 'rspack' # or 'webpack'
        webpack_loader: 'swc' # Rspack works best with SWC

      Troubleshooting

      CSS Modules Return undefined

      Symptoms:

      • css.className is undefined
      • SSR crashes with property access errors
      • Works in development but fails in SSR

      Solutions:

      1. Configure namedExport: false (see Breaking Changes section)
      2. Ensure server config preserves CSS modules settings
      3. Filter Rspack's cssExtractLoader from server bundle

      Intermittent SSR Failures

      Cause: Incomplete CSS extraction filtering in server config.

      Solution: Update the CSS extract loader filter to include cssExtractLoader for Rspack (see Server Bundle section).

      Module Resolution Errors

      Symptom: Module not found: Can't resolve './file.bs.js'

      Solution: Add the file extension to webpack's resolve.extensions configuration.

      Third-Party Package Issues

      Some packages may not ship compiled files. Use patch-package to fix:

      pnpm add --save-dev patch-package postinstall-postinstall

      Add to package.json:

      {
        "scripts": {
          "postinstall": "patch-package"
        }
      }

      Performance Benefits

      After migration, expect:

      • Build times: ~53-270ms with Rspack (vs seconds with Webpack)
      • ~20x faster transpilation with SWC
      • Faster CI runs and development iteration

      Reference Implementation

      For a complete working example, see the react-webpack-rails-tutorial Rspack migration PR.