craco, customize-cra, and react-app-rewired are utilities designed to modify the internal webpack configuration of Create React App (CRA) without forcing developers to eject. Ejecting is a one-way operation that exposes all build configurations but removes the ability to receive upstream updates from CRA. These tools intercept the build process to allow custom babel plugins, webpack loaders, or alias configurations while keeping the CRA abstraction intact. They are primarily useful for legacy projects stuck on CRA that require specific build tweaks not supported by the default setup.
When working with Create React App (CRA), you often hit a wall where the default build setup does not support a specific library or configuration need. The official solution is to eject, but that exposes you to complex webpack configs and loses future updates from the CRA team. Tools like craco, react-app-rewired, and customize-cra exist to solve this problem β they let you override the config safely. However, the ecosystem has shifted, and choosing the right tool requires understanding their architecture and current maintenance status.
Before diving into configuration tools, it is vital to acknowledge that the React team no longer recommends Create React App for new projects. The official React documentation now suggests using frameworks like Next.js or build tools like Vite. These tools natively support configuration without needing wrappers.
If you are maintaining an existing CRA codebase, these packages are still relevant. If you are starting fresh, you should likely avoid CRA entirely. For the sake of this comparison, we assume you are maintaining a legacy CRA project that cannot be migrated immediately.
Each tool uses a different file to define overrides. This affects how your project is organized and how easy it is for new developers to find build settings.
craco uses a dedicated config file at the root called craco.config.js. It wraps the CRA scripts, so you must update your package.json scripts to use craco instead of react-scripts.
// package.json
{
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test"
}
}
// craco.config.js
module.exports = {
webpack: {
configure: (config) => {
// Modify config here
return config;
}
}
};
react-app-rewired uses a file called config-overrides.js at the root. You also need to update package.json to use react-app-rewired commands.
// package.json
{
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test"
}
}
// config-overrides.js
module.exports = function override(config, env) {
// Modify config here
return config;
};
customize-cra does not have its own config file. It is used inside config-overrides.js alongside react-app-rewired. It provides helper functions to reduce boilerplate.
// config-overrides.js
const { override, addBabelPlugin } = require('customize-cra');
module.exports = override(
addBabelPlugin('lodash')
);
A common requirement is adding support for a new file type, such as SVGs or custom fonts. Here is how each package handles adding a webpack rule.
craco provides a helper to push rules safely without breaking existing ones. You often need to find the right module rule index.
// craco.config.js
module.exports = {
webpack: {
configure: (config) => {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack']
});
return config;
}
}
};
react-app-rewired requires you to manually manipulate the config array. You have full control but also full responsibility to avoid duplicates.
// config-overrides.js
module.exports = function override(config) {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack']
});
return config;
};
customize-cra simplifies this with specific helpers, but you are limited to the helpers they provide. For custom loaders, you often fall back to raw config manipulation anyway.
// config-overrides.js
const { override, addWebpackModuleRule } = require('customize-cra');
module.exports = override(
addWebpackModuleRule({
test: /\.svg$/,
use: ['@svgr/webpack']
})
);
The ease of adding common features varies significantly between these tools.
craco has a dedicated plugin system. You can install community plugins like @craco/craco-plugin-postcss or craco-plugin-eslint without writing config code. This reduces the risk of breaking your build during upgrades.
// craco.config.js
const CracoEsbuildPlugin = require('craco-esbuild');
module.exports = {
plugins: [
{
plugin: CracoEsbuildPlugin,
options: { /* plugin options */ }
}
]
};
react-app-rewired has no formal plugin system. You rely on community snippets or write your own logic in config-overrides.js. This offers flexibility but increases maintenance burden.
// config-overrides.js
// No plugin system β you write the logic manually
module.exports = function override(config) {
// Manual implementation of feature
return config;
};
customize-cra acts as a collection of plugins for react-app-rewired. It offers helpers like addBabelPlugin or disableEsLint. It is useful if your needs match the available helpers.
// config-overrides.js
const { override, disableEsLint } = require('customize-cra');
module.exports = override(
disableEsLint()
);
When choosing a build tool wrapper, you must consider how well it tracks with upstream changes to CRA.
craco (specifically the @craco/craco package) is actively maintained to support CRA v5 (webpack 5). The original craco package is deprecated. If you use the scoped package, you are on the most stable path for CRA.
react-app-rewired has seen slower development. While it works with CRA v5, some users report issues with webpack 5 changes requiring forks or patches. It is stable for simple overrides but riskier for complex builds.
customize-cra depends entirely on react-app-rewired. If the underlying tool struggles with a CRA update, this library will too. It adds a layer of abstraction that can sometimes obscure error messages during build failures.
| Feature | craco | react-app-rewired | customize-cra |
|---|---|---|---|
| Config File | craco.config.js | config-overrides.js | config-overrides.js |
| Plugin System | β Yes, formal API | β No, manual code | β οΈ Helper functions only |
| CRA v5 Support | β
Strong (@craco/craco) | β οΈ Works, some issues | β οΈ Depends on rewire |
| Complexity | Low (Convention-based) | Medium (Manual config) | Low (If helpers fit) |
| Standalone | β Yes | β Yes | β No (Requires rewire) |
If you must stay on Create React App, craco (specifically @craco/craco) is the superior choice for most teams. Its plugin system reduces the amount of custom webpack code you need to write and maintain. It aligns better with modern webpack 5 standards used in CRA v5.
Use react-app-rewired only if you have very specific, low-level webpack needs that craco plugins cannot address, and you are comfortable managing the config object manually. Use customize-cra only as a helper alongside react-app-rewired if its specific functions save you time.
However, the best architectural decision is to plan a migration away from CRA. Tools like Vite offer similar speed with native configuration support, removing the need for these wrappers entirely. Use these packages as a bridge, not a permanent solution.
Choose react-app-rewired if you need direct, low-level access to the webpack config object and prefer a simple override file without a plugin system. It is suitable for small, specific tweaks in legacy CRA projects. However, be aware that development has slowed, and compatibility with CRA v5 may require additional workarounds compared to craco.
Choose customize-cra only if you are already using react-app-rewired and want helper functions to simplify webpack modifications. It is not a standalone tool but rather a companion library that provides functional utilities to modify the config object. Avoid using this for new projects as it depends on the older react-app-rewired architecture.
Choose craco (specifically @craco/craco) if you need a modern, plugin-based approach to configuring CRA v5. It offers a cleaner API than older tools and has a growing ecosystem of pre-built plugins for common tasks like Tailwind CSS or SASS support. It is the most maintainable option for teams committed to staying on CRA.
βΉοΈ Before submitting an issue to this repo - Ensure it's a issue with the code in this repo, not a how do I configure something with Webpack question (post something on Stack Overflow or Spectrum). It's your config you "own" it.
Tweak the create-react-app webpack config(s) without using 'eject' and without creating a fork of the react-scripts.
All the benefits of create-react-app without the limitations of "no config". You can add plugins, loaders whatever you need.
As of Create React App 2.0 this repo is "lightly" maintained mostly by the community at this point.
β οΈ Please Note:
By doing this you're breaking the "guarantees" that CRA provides. That is to say you now "own" the configs. No support will be provided. Proceed with caution.
"Stuff can break" β Dan Abramov https://twitter.com/dan_abramov/status/1045809734069170176
Note: I personally use next.js or Razzle which both support custom Webpack out of the box.
You can try customize-cra for a set of CRA 2.0 compatible rewirers, or any of the alternative projects and forks that aim to support 2.0:
Create your app using create-react-app and then rewire it.
npm install react-app-rewired --save-dev
npm install react-app-rewired@1.6.2 --save-dev
config-overrides.js file in the root directory/* config-overrides.js */
module.exports = function override(config, env) {
//do stuff with the webpack config...
return config;
}
+-- your-project
| +-- config-overrides.js
| +-- node_modules
| +-- package.json
| +-- public
| +-- README.md
| +-- src
react-scripts in npm scripts for start, build and test /* package.json */
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
"eject": "react-scripts eject"
}
Note: Do NOT flip the call for the eject script.
That gets run only once for a project, after which you are given full control over the webpack configuration making react-app-rewired no longer required.
There are no configuration options to rewire for the eject script.
npm start
npm run build
You can set a custom path for config-overrides.js. If you (for instance) wanted to use a 3rd-party config-overrides.js that exists in node_modules, you could add the following to your package.json:
"config-overrides-path": "node_modules/some-preconfigured-rewire"
By default, the config-overrides.js file exports a single function to use when customising the webpack configuration for compiling your react app in development or production mode. It is possible to instead export an object from this file that contains up to three fields, each of which is a function. This alternative form allows you to also customise the configuration used for Jest (in testing), and for the Webpack Dev Server itself.
This example implementation is used to demonstrate using each of the object require functions. In the example, the functions:
.env variables.env file variables.module.exports = {
// The Webpack config to use when compiling your react app for development or production.
webpack: function(config, env) {
// ...add your webpack config
return config;
},
// The Jest config to use when running your jest tests - note that the normal rewires do not
// work here.
jest: function(config) {
// ...add your jest config customisation...
// Example: enable/disable some tests based on environment variables in the .env file.
if (!config.testPathIgnorePatterns) {
config.testPathIgnorePatterns = [];
}
if (!process.env.RUN_COMPONENT_TESTS) {
config.testPathIgnorePatterns.push('<rootDir>/src/components/**/*.test.js');
}
if (!process.env.RUN_REDUCER_TESTS) {
config.testPathIgnorePatterns.push('<rootDir>/src/reducers/**/*.test.js');
}
return config;
},
// The function to use to create a webpack dev server configuration when running the development
// server with 'npm run start' or 'yarn start'.
// Example: set the dev server to use a specific certificate in https.
devServer: function(configFunction) {
// Return the replacement function for create-react-app to use to generate the Webpack
// Development Server config. "configFunction" is the function that would normally have
// been used to generate the Webpack Development server config - you can use it to create
// a starting configuration to then modify instead of having to create a config from scratch.
return function(proxy, allowedHost) {
// Create the default config by calling configFunction with the proxy/allowedHost parameters
const config = configFunction(proxy, allowedHost);
// Change the https certificate options to match your certificate, using the .env file to
// set the file paths & passphrase.
const fs = require('fs');
config.https = {
key: fs.readFileSync(process.env.REACT_HTTPS_KEY, 'utf8'),
cert: fs.readFileSync(process.env.REACT_HTTPS_CERT, 'utf8'),
ca: fs.readFileSync(process.env.REACT_HTTPS_CA, 'utf8'),
passphrase: process.env.REACT_HTTPS_PASS
};
// Return your customised Webpack Development Server config.
return config;
};
},
// The paths config to use when compiling your react app for development or production.
paths: function(paths, env) {
// ...add your paths config
return paths;
},
}
The webpack field is used to provide the equivalent to the single-function exported from config-overrides.js. This is where all the usual rewires are used. It is not able to configure compilation in test mode because test mode does not get run through Webpack at all (it runs in Jest). It is also not able to be used to customise the Webpack Dev Server that is used to serve pages in development mode because create-react-app generates a separate Webpack configuration for use with the dev server using different functions and defaults.
Webpack is not used for compiling your application in Test mode - Jest is used instead. This means that any rewires specified in your webpack config customisation function will not be applied to your project in test mode.
React-app-rewired automatically allows you to customise your Jest configuration in a jest section of your package.json file, including allowing you to set configuration fields that create-react-app would usually block you from being able to set. It also automatically sets up Jest to compile the project with Babel prior to running tests. Jest's configuration options are documented separately at the Jest website. Note: Configuration arrays and objects are merged, rather than overwritten. See #240 and #241 for details
If you want to add plugins and/or presets to the Babel configuration that Jest will use, you need to define those plugins/presets in either a babel section inside the package.json file or inside a .babelrc file. React-app-rewired alters the Jest configuration to use these definition files for specifying Babel options when Jest is compiling your react app. The format to use in the Babel section of package.json or the .babelrc file is documented separately at the Babel website.
The jest field in the module.exports object in config-overrides.js is used to specify a function that can be called to customise the Jest testing configuration in ways that are not possible in the jest section of the package.json file. For example, it will allow you to change some configuration options based on environment variables. This function is passed the default create-react-app Jest configuration as a parameter and is required to return the modified Jest configuration that you want to use. A lot of the time you'll be able to make the configuration changes needed simply by using a combination of the package.json file's jest section and a .babelrc file (or babel section in package.json) instead of needing to provide this jest function in config-overrides.js.
When running in development mode, create-react-app does not use the usual Webpack config for the Development Server (the one that serves the app pages). This means that you cannot use the normal webpack section of the config-overrides.js server to make changes to the Development Server settings as those changes won't be applied.
Instead of this, create-react-app expects to be able to call a function to generate the webpack dev server when needed. This function is provided with parameters for the proxy and allowedHost settings to be used in the webpack dev server (create-react-app retrieves the values for those parameters from your package.json file).
React-app-rewired provides the ability to override this function through use of the devServer field in the module.exports object in config-overrides.js. It provides the devServer function a single parameter containing the default create-react-app function that is normally used to generate the dev server config (it cannot provide a generated version of the configuration because react-scripts is calling the generation function directly). React-app-rewired needs to receive as a return value a replacement function for create-react-app to then use to generate the Development Server configuration (i.e. the return value should be a new function that takes the two parameters for proxy and allowedHost and itself returns a Webpack Development Server configuration). The original react-scripts function is passed into the config-overrides.js devServer function so that you are able to easily call this yourself to generate your initial devServer configuration based on what the defaults used by create-react-app are.
The paths field is used to provide overrides for the create-react-app paths passed into webpack and jest.
Some third party tools, like react-cosmos relies on your webpack config.
You can create webpack.config.js file and export rewired config using following snippet:
const { paths } = require('react-app-rewired');
// require normalized overrides
const overrides = require('react-app-rewired/config-overrides');
const config = require(paths.scriptVersion + '/config/webpack.config.dev');
module.exports = overrides.webpack(config, process.env.NODE_ENV);
Then just point to this file in tool configuration.
At this point in time, it is difficult to change the entry point from the default src/index.js file due to the way that file is included by create-react-app. The normal rewiring process gets bypassed by several of the create-react-app scripts.
There are three work-arounds available here:
require('./index.tsx');
react-dev-utils/checkRequiredFiles function to always return true (causing create-react-app to no longer try to enforce that the entry file must exist).It is possible to use a custom version of the react-scripts package with react-app-rewired by specifying the name of the scripts package in the command line option --scripts-version or setting REACT_SCRIPTS_VERSION=<...> via the environment.
A working example for using the scripts version option is:
{
"scripts": {
"start": "react-app-rewired start --scripts-version react-scripts-ts",
"build": "react-app-rewired build --scripts-version react-scripts-ts",
"test": "react-app-rewired test --scripts-version react-scripts-ts",
"eject": "react-scripts eject"
}
}
React-app-rewired imports your config-overrides.js file without the '.js' extension. This means that you have the option of creating a directory called config-overrides at the root of your project and exporting your overrides from the default index.js file inside that directory.
If you have several custom overrides using a directory allows you to be able to put each override in a separate file. An example template that demonstrates this can be found in Guria/rewired-ts-boilerplate at Github.
If you need to change the location of your config-overrides.js you can pass a command line option --config-overrides
When developing this project, ensure you have yarn installed.
To run the test app, navigate to the directory and run:
yarn setup
yarn start
(when you are finished, run yarn teardown to clean up)
Here is a list of all the available commands to help you in development
yarn setup - installs dependences and links test/react-appyarn start - starts the react appyarn build - builds the react appyarn test - tests the react appyarn teardown - unlinks test/react-app and removes dependencies