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.
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.
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-rewiredandcustomize-craare effectively unmaintained. Thereact-app-rewiredGitHub repository states: “This project is in maintenance mode... it may break with future versions of react-scripts.” Similarly,customize-crahasn’t seen meaningful updates since 2021. Do not use them in new projects.
Let’s compare how each approach handles a typical request: aliasing src to @.
react-scripts (No Customization)You can’t do this. CRA doesn’t expose Webpack config. Your only options are:
../../../components/Button)// No solution without ejecting or third-party tools
import Button from '../../../components/Button'; // verbose
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';
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.
Suppose you want to use babel-plugin-styled-components for better debugging.
react-scriptsNot possible without ejecting.
react-app-rewiredYou 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-craUse 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.
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:
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).
If you’re maintaining a legacy CRA app that already uses react-app-rewired, proceed cautiously:
react-scripts to a known-working version in package.jsonFor new projects, do not start with react-app-rewired. Instead:
| Package | Standalone? | Purpose | Safe for New Projects? | Config Style |
|---|---|---|---|---|
react-scripts | ✅ Yes | Official CRA build tool | ✅ Yes | Zero-config |
react-app-rewired | ✅ Yes | Enables custom Webpack/Babel in CRA | ❌ No | Manual JS overrides |
customize-cra | ❌ No | Helper utilities for react-app-rewired | ❌ No | Declarative helpers |
react-scripts if you can live within CRA’s boundaries. It’s stable, secure, and officially supported.react-app-rewired and customize-cra in new codebases. Their maintenance status and fragility make them technical debt traps.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.
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.
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.
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📌📌 Breaking change: With the 1.0 release of customize-cra breaking changes have been made to the addLessLoader customizer to support the changes to create-react-app in #7876. Please follow the migration guide in #253.
This project provides a set of utilities to customize create-react-app versions 2 and 3 configurations leveraging react-app-rewired core functionalities.
This project relies on react-app-rewired. You'll need to install that in order for customize-cra to work.
yarn add customize-cra react-app-rewired --dev
"Stuff can break" - Dan Abramov
Using this library will override the default behavior and configuration of create-react-app, therefore invalidating the guarantees that come with it. Use with discretion!
customize-cra takes advantage of react-app-rewired's config-overrides.js file. By importing customize-cra functions and exporting a few function calls wrapped in our override function, you can easily modify the underlying config objects (webpack, webpack-dev-server, babel, etc.) that make up create-react-app.
Note: all code should be added to config-overrides.js at the same level as package.json.
See the api docs for documentation for each function.
webpackTo use these plugins, import the override function, and call it with whatever plugins you need. Each of these plugin invocations will return a new function, that override will call with the newly modified config object. Falsy values will be ignored though, so if you need to conditionally apply any of these plugins, you can do so like below.
For example:
const {
override,
addDecoratorsLegacy,
disableEsLint,
addBundleVisualizer,
addWebpackAlias,
adjustWorkbox
} = require("customize-cra");
const path = require("path");
module.exports = override(
// enable legacy decorators babel plugin
addDecoratorsLegacy(),
// disable eslint in webpack
disableEsLint(),
// add webpack bundle visualizer if BUNDLE_VISUALIZE flag is enabled
process.env.BUNDLE_VISUALIZE == 1 && addBundleVisualizer(),
// add an alias for "ag-grid-react" imports
addWebpackAlias({
["ag-grid-react$"]: path.resolve(__dirname, "src/shared/agGridWrapper.js")
}),
// adjust the underlying workbox
adjustWorkbox(wb =>
Object.assign(wb, {
skipWaiting: true,
exclude: (wb.exclude || []).concat("index.html")
})
)
);
webpack-dev-serverYou can use the overrideDevServer function to override the webpack-dev-server config. It works the same way as override:
const {
override,
disableEsLint,
overrideDevServer,
watchAll
} = require("customize-cra");
module.exports = {
webpack: override(
// usual webpack plugin
disableEsLint()
),
devServer: overrideDevServer(
// dev server plugin
watchAll()
)
};
MobXIf you want CRA 2 to work with MobX, use the addDecoratorsLegacy and disableEsLint.
See api.md for documentation on the functions provided by customize-cra.
For more information about contributing to this project, like a directory map or a how-to for reporting an issue about the project, please see contributing.md.
Thanks goes to these wonderful people (emoji key):
dqu 💬 | Breeze 💻 | Terryand 💻 | m-weeks 🐛 | 吴超 💡 | James Thistlewood 💻 | taddj 💬 |
MeiLin 💻 | Graham Crockford 🤔 | afei 💻 | zoomdong 💻 | Danilo Campana Fuchs 💻 | Rodrigo Narvaez 💻 | blackmatch 💻 |
billypon 💻 | Juetta 💻 | LING_ZI_QING 💻 📖 |
This project follows the all-contributors specification. Contributions of any kind welcome!