react-scripts vs customize-cra vs react-app-rewired
Customizing Create React App Without Ejecting
react-scriptscustomize-crareact-app-rewiredSimilar Packages:

Customizing Create React App Without Ejecting

react-scripts is the official build and development toolchain for Create React App (CRA), providing a zero-configuration setup for React applications. react-app-rewired and customize-cra are complementary tools designed to override or extend CRA's webpack and Babel configurations without running npm eject. react-app-rewired acts as a drop-in replacement for react-scripts that enables custom configuration via a config-overrides.js file, while customize-cra provides a set of utility functions specifically for use within that overrides file to simplify common customization patterns like adding plugins, modifying loaders, or adjusting Webpack rules.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-scripts2,782,306103,824116 kB2,3734 years agoMIT
customize-cra169,7692,779-1156 years agoMIT
react-app-rewired09,87848.9 kB18-MIT

Customizing Create React App: react-scripts vs react-app-rewired vs customize-cra

Create React App (CRA) abstracts away complex build tooling behind react-scripts, offering a streamlined developer experience. But what happens when you need to tweak Webpack, add a Babel plugin, or adjust PostCSS settings? That’s where react-app-rewired and customize-cra come in — though they solve the problem differently, and not all paths are equally sustainable. Let’s break down how these tools work, their trade-offs, and when to use each.

🛠️ Core Roles: What Each Package Actually Does

react-scripts is the official engine behind CRA. It bundles Webpack, Babel, ESLint, Jest, and other tools into a single dependency. You never configure these directly — everything is hidden behind abstraction.

// package.json (standard CRA)
"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build"
}

react-app-rewired replaces react-scripts in your npm scripts and injects a custom configuration file (config-overrides.js) before CRA’s internal config runs. It patches the Webpack and Jest configs at runtime without ejecting.

// package.json (with rewired)
"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build"
}

customize-cra is not a standalone tool. It’s a library of helper functions meant to be used inside config-overrides.js when you’re already using react-app-rewired. It simplifies common overrides with readable APIs.

// config-overrides.js
const { override, addWebpackAlias } = require('customize-cra');

module.exports = override(
  addWebpackAlias({ '@': path.resolve(__dirname, 'src') })
);

⚠️ Critical Note: As of 2024, both react-app-rewired and customize-cra are effectively unmaintained. The react-app-rewired GitHub repository states: “This project is in maintenance mode... it may break with future versions of react-scripts.” Similarly, customize-cra hasn’t seen meaningful updates since 2021. Do not use them in new projects.

🔧 Common Customization: Adding a Webpack Alias

Let’s compare how each approach handles a typical request: aliasing src to @.

With react-scripts (No Customization)

You can’t do this. CRA doesn’t expose Webpack config. Your only options are:

  • Use relative paths (../../../components/Button)
  • Or eject (which copies all config into your project permanently)
// No solution without ejecting or third-party tools
import Button from '../../../components/Button'; // verbose

With react-app-rewired (Manual Override)

You write raw Webpack manipulation code in config-overrides.js.

// config-overrides.js
const path = require('path');

module.exports = function override(config) {
  config.resolve.alias = {
    ...config.resolve.alias,
    '@': path.resolve(__dirname, 'src')
  };
  return config;
};

Now you can import cleanly:

import Button from '@/components/Button';

With customize-cra (Declarative Helper)

You use a helper function to avoid touching Webpack internals directly.

// config-overrides.js
const { override, addWebpackAlias } = require('customize-cra');
const path = require('path');

module.exports = override(
  addWebpackAlias({
    '@': path.resolve(__dirname, 'src')
  })
);

Same result, but less error-prone if you’re unfamiliar with Webpack’s structure.

🧪 Adding a Babel Plugin

Suppose you want to use babel-plugin-styled-components for better debugging.

react-scripts

Not possible without ejecting.

react-app-rewired

You must manually merge into the Babel config:

// config-overrides.js
module.exports = function override(config, env) {
  // Find the Babel loader rule
  const babelLoader = config.module.rules.find(
    rule => rule.loader && rule.loader.includes('babel-loader')
  );
  if (babelLoader) {
    babelLoader.options.plugins = [
      ...(babelLoader.options.plugins || []),
      'babel-plugin-styled-components'
    ];
  }
  return config;
};

customize-cra

Use the built-in addBabelPlugin helper:

// config-overrides.js
const { override, addBabelPlugin } = require('customize-cra');

module.exports = override(
  addBabelPlugin('babel-plugin-styled-components')
);

Again, customize-cra reduces boilerplate — but only if the helper exists for your use case.

⚠️ Fragility and Maintenance Risks

All customization via react-app-rewired or customize-cra depends on CRA’s internal config structure remaining stable. When CRA updates react-scripts, your overrides can silently break because:

  • Rule names or loader structures change
  • New optimization passes interfere with your modifications
  • Environment-specific configs diverge (e.g., dev vs prod)

For example, CRA v5 introduced significant Webpack 5 changes. Many react-app-rewired setups broke because overrides assumed Webpack 4 patterns.

There’s no compile-time check — your app might build successfully but behave incorrectly at runtime (e.g., missing CSS, broken HMR).

🔄 Realistic Upgrade Path

If you’re maintaining a legacy CRA app that already uses react-app-rewired, proceed cautiously:

  1. Pin react-scripts to a known-working version in package.json
  2. Test thoroughly after any dependency update
  3. Plan a migration to a more flexible build system (e.g., Vite, Remix, or vanilla Webpack)

For new projects, do not start with react-app-rewired. Instead:

  • If you need light customization: use CRACO (though also minimally maintained)
  • If you need serious control: choose Vite + React or Next.js, which offer first-class config extensibility

📊 Summary Table

PackageStandalone?PurposeSafe for New Projects?Config Style
react-scripts✅ YesOfficial CRA build tool✅ YesZero-config
react-app-rewired✅ YesEnables custom Webpack/Babel in CRA❌ NoManual JS overrides
customize-cra❌ NoHelper utilities for react-app-rewired❌ NoDeclarative helpers

💡 Final Recommendation

  • Stick with react-scripts if you can live within CRA’s boundaries. It’s stable, secure, and officially supported.
  • Avoid react-app-rewired and customize-cra in new codebases. Their maintenance status and fragility make them technical debt traps.
  • Migrate away if you’re already using them. Modern alternatives like Vite offer faster builds, better TypeScript support, and explicit config files — without the risk of breaking on every minor update.

The original promise of CRA was “no configuration needed.” If you’ve outgrown that, it’s time to graduate to a tool that embraces configurability rather than fighting against it.

How to Choose: react-scripts vs customize-cra vs react-app-rewired

  • react-scripts:

    Choose react-scripts if you prefer a stable, officially supported, zero-config experience and don’t need to modify the underlying Webpack, Babel, or ESLint configurations. It’s the default and recommended choice for most CRA projects. If your customization needs grow beyond what react-app-rewired can safely support, consider migrating to a framework like Vite or Next.js instead of forcing unsupported overrides.

  • customize-cra:

    Choose customize-cra if you're already using react-app-rewired and want a set of well-tested helper functions to modify CRA’s Webpack config in a more declarative way. It’s ideal for common tasks like adding PostCSS plugins, aliasing modules, or injecting environment variables without writing raw Webpack manipulation code. However, it only works alongside react-app-rewired and doesn’t function independently.

  • react-app-rewired:

    Choose react-app-rewired when you need to customize CRA’s build configuration without ejecting, especially if you require fine-grained control over Webpack or Babel settings. It replaces react-scripts in your npm scripts and loads overrides from config-overrides.js. Be aware that it may break with new CRA releases since it relies on internal implementation details, and it’s best suited for projects where ejecting is not an option but minor tweaks are necessary.

README for react-scripts

react-scripts

This package includes scripts and configuration used by Create React App.
Please refer to its documentation: