Create React App (CRA) is a toolchain for building single-page React applications with zero build configuration. It sets up a modern development environment with Webpack 5, Babel 7, ESLint, Jest, and a development server so you can focus on writing React code rather than wiring together build tools. CRA generates a project scaffold with sensible defaults and provides four core scripts (start, test, build, eject) that cover the full development lifecycle.
Caution
Create React App is officially deprecated. The project maintainers have marked CRA as deprecated and recommend visiting react.dev for modern alternatives such as Next.js, Remix, Gatsby, or Vite-based setups. This guide documents CRA 5.1.0 as-is because millions of existing projects still depend on it. If you are starting a brand-new project, evaluate the alternatives first.
This guide covers create-react-app 5.1.0 and react-scripts 5.1.0, the final release versions. Written from commit 6254386 (2025-02-14) of the source repository.
1. Quick Start
1.1 Create a New Project
The fastest path to a running React app:
npx create-react-app my-app
cd my-app
npm start
Expected output after npx create-react-app my-app:
Creating a new React app in /path/to/my-app.
Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts with cra-template...
...
Success! Created my-app at /path/to/my-app
Inside that directory, you can run several commands:
npm start
Starts the development server.
npm run build
Bundles the app into static files for production.
npm test
Starts the test runner.
npm run eject
Removes this tool and copies build dependencies, configuration files
and scripts into the app directory. If you do this, you can't go back!
We suggest that you begin by typing:
cd my-app
npm start
Happy hacking!
After npm start, open http://localhost:3000 in your browser to see the running app. The page reloads automatically when you edit source files.
1.2 Create a TypeScript Project
npx create-react-app my-app --template typescript
cd my-app
npm start
This scaffolds the project with .tsx files, a tsconfig.json, and TypeScript type definitions pre-installed.
1.3 What You Get
A freshly created project contains:
my-app/
node_modules/
public/
index.html
favicon.ico
manifest.json
robots.txt
src/
App.css
App.js (or App.tsx)
App.test.js (or App.test.tsx)
index.css
index.js (or index.tsx)
logo.svg
reportWebVitals.js (or .ts)
setupTests.js (or .ts)
.gitignore
package.json
README.md
Note
The
public/directory contains static assets that are served as-is. Thesrc/directory is where all your application code lives. Webpack only processes files insidesrc/, so all JS/TS imports must originate there.
2. Installation
2.1 System Requirements
| Requirement | Minimum Version |
|---|---|
| Node.js | 14 or higher |
| npm | 6 or higher |
Verify your versions:
node --version
npm --version
Expected output (example):
v18.19.0
9.2.0
2.2 Installation Methods
Using npx (recommended — always runs the latest version):
npx create-react-app my-app
Using npm global install:
npm install -g create-react-app
create-react-app my-app
Using Yarn:
yarn create react-app my-app
Important
If you previously installed
create-react-appglobally vianpm install -g create-react-app, uninstall it withnpm uninstall -g create-react-appto ensurenpxalways uses the latest version. Stale global installs can cause unexpected behavior.
2.3 Package Manager Detection
CRA automatically detects which package manager you are using. If the npm_config_user_agent environment variable contains "yarn", CRA uses Yarn for dependency installation. Otherwise, it defaults to npm.
2.4 CLI Options
The create-react-app command accepts the following options:
| Option | Description |
|---|---|
--verbose |
Print additional logging output during installation |
--info |
Print environment debugging information (Node, npm, OS versions) |
--scripts-version <version> |
Use a custom or alternative version of react-scripts |
--template <name-or-path> |
Specify a project template (default: cra-template) |
--use-pnp |
Enable Yarn Plug'n'Play mode for zero-install dependencies |
Print environment info:
npx create-react-app --info
Expected output:
Environment Info:
current version of create-react-app: 5.1.0
running from /path/to/create-react-app
System:
OS: macOS 14.2
CPU: (10) arm64 Apple M1 Pro
Binaries:
Node: 18.19.0
npm: 9.2.0
Yarn: 1.22.19
Browsers:
Chrome: 120.0
Firefox: 121.0
Safari: 17.2
Use a custom template:
npx create-react-app my-app --template typescript
npx create-react-app my-app --template cra-template-pwa
Use verbose output for debugging install failures:
npx create-react-app my-app --verbose
2.5 Available Templates
CRA ships two official templates:
| Template | Package Name | Description |
|---|---|---|
| Default (JavaScript) | cra-template |
Standard JavaScript project with React 18 |
| TypeScript | cra-template-typescript |
TypeScript project with tsconfig.json and .tsx files |
Community templates follow the naming convention cra-template-*. You can specify any npm package name or a local file path:
npx create-react-app my-app --template file:../my-custom-template
2.6 Updating react-scripts
To update an existing project to a newer version of react-scripts:
npm install react-scripts@latest
Check the react-scripts changelog for breaking changes before upgrading.
3. Configuration
CRA is designed to work with zero configuration. However, it provides extensive customization through environment variables, .env files, browserslist, proxy configuration, and PostCSS/Tailwind integration — all without ejecting.
3.1 Environment Variables
3.1.1 Custom Environment Variables
Any environment variable prefixed with REACT_APP_ is automatically embedded into your application at build time via Webpack's DefinePlugin. Access them in code through process.env:
// In your React code
const apiUrl = process.env.REACT_APP_API_URL;
const version = process.env.REACT_APP_VERSION;
In public/index.html, reference them with %VARIABLE_NAME%:
<title>%REACT_APP_SITE_TITLE%</title>
Warning
Environment variables are embedded at build time, not at runtime. After running
npm run build, the values are baked into the static bundle. Changing a.envfile requires a rebuild to take effect. Never store secrets inREACT_APP_*variables — they will be visible in the client-side JavaScript bundle.
3.1.2 Built-in Environment Variables
CRA recognizes the following built-in environment variables that control development server behavior and build output:
| Variable | Default | Description |
|---|---|---|
NODE_ENV |
(auto) | Set automatically: development for start, test for test, production for build |
PORT |
3000 |
Port for the development server |
HOST |
0.0.0.0 |
Host binding for the development server |
HTTPS |
false |
Set to true to run the dev server over HTTPS |
SSL_CRT_FILE |
(none) | Path to a custom SSL certificate file (used with HTTPS=true) |
SSL_KEY_FILE |
(none) | Path to a custom SSL key file (used with HTTPS=true) |
BROWSER |
(system default) | Browser to open on npm start. Set to none to disable auto-open |
PUBLIC_URL |
/ |
URL prefix for assets. Use when hosting from a subdirectory |
BUILD_PATH |
build |
Output directory for npm run build |
GENERATE_SOURCEMAP |
true |
Set to false to skip source map generation in production builds |
INLINE_RUNTIME_CHUNK |
true |
Set to false to prevent inlining the Webpack runtime chunk |
IMAGE_INLINE_SIZE_LIMIT |
10000 |
Images smaller than this (in bytes) are inlined as data URIs |
FAST_REFRESH |
true |
Enable/disable React Fast Refresh in development |
DISABLE_ESLINT_PLUGIN |
false |
Set to true to completely disable the ESLint webpack plugin |
TSC_COMPILE_ON_ERROR |
false |
Set to true to allow building even when TypeScript type errors exist |
ESLINT_NO_DEV_ERRORS |
false |
Set to true to downgrade ESLint errors to warnings in development |
NODE_PATH |
(none) | Additional directories to resolve modules from (colon-separated) |
WDS_SOCKET_HOST |
(auto) | WebSocket host for the dev server hot-reload connection |
WDS_SOCKET_PATH |
(auto) | WebSocket path for the dev server hot-reload connection |
WDS_SOCKET_PORT |
(auto) | WebSocket port for the dev server hot-reload connection |
DANGEROUSLY_DISABLE_HOST_CHECK |
false |
Disables the host check on the dev server (security risk) |
3.1.3 Setting Environment Variables
On the command line (Linux/macOS):
PORT=4000 BROWSER=none npm start
On the command line (Windows cmd):
set PORT=4000 && set BROWSER=none && npm start
On the command line (Windows PowerShell):
$env:PORT=4000; $env:BROWSER="none"; npm start
Tip
Use the
cross-envpackage for cross-platform environment variable setting inpackage.jsonscripts:"start": "cross-env PORT=4000 react-scripts start".
3.2 .env Files
CRA supports .env files for defining environment variables declaratively. Files are loaded in the following priority order (highest to lowest):
.env.{NODE_ENV}.local— Local overrides for a specific environment.env.local— Local overrides (loaded in all environments excepttest).env.{NODE_ENV}— Environment-specific defaults.env— Global defaults
Where {NODE_ENV} is development, test, or production.
Example .env file:
REACT_APP_API_URL=https://api.example.com
REACT_APP_VERSION=1.2.3
REACT_APP_FEATURE_FLAG=true
Example .env.development:
REACT_APP_API_URL=http://localhost:8080
REACT_APP_DEBUG=true
Example .env.production:
REACT_APP_API_URL=https://api.production.com
REACT_APP_SENTRY_DSN=https://[email removed]/12345
Note
Variables defined in higher-priority files override those in lower-priority files. A variable set in
.env.development.localoverrides the same variable in.env. The.env.localfile is automatically added to.gitignorein new CRA projects — use it for machine-specific overrides you don't want committed.
Variable expansion is supported within .env files:
REACT_APP_DOMAIN=example.com
REACT_APP_API_URL=https://api.$REACT_APP_DOMAIN/v1
3.3 Browserslist
CRA uses the browserslist configuration in package.json to determine which browsers to target for CSS autoprefixing and JavaScript transpilation:
{
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
The production list controls which transforms Babel applies and which CSS vendor prefixes PostCSS adds. The development list keeps the dev bundle lighter by targeting only modern browsers.
3.4 HTTPS in Development
Run the development server over HTTPS with a self-signed certificate:
HTTPS=true npm start
Your browser will warn about the untrusted certificate. To use a custom trusted certificate:
HTTPS=true SSL_CRT_FILE=./certs/server.crt SSL_KEY_FILE=./certs/server.key npm start
This is useful when working with APIs that enforce secure contexts or when testing service workers locally.
3.5 Proxy Configuration
3.5.1 Simple Proxy
For forwarding API requests to a backend during development, add a proxy field to package.json:
{
"proxy": "http://localhost:4000"
}
Any request to a path not served by the dev server (i.e., not a static file) will be proxied to http://localhost:4000. For example, fetch('/api/users') in your code will be proxied to http://localhost:4000/api/users.
3.5.2 Advanced Proxy with setupProxy.js
For more control, create src/setupProxy.js:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:4000',
changeOrigin: true,
})
);
app.use(
'/ws',
createProxyMiddleware({
target: 'ws://localhost:4001',
ws: true,
})
);
};
Important
The
src/setupProxy.jsfile is only used during development. It is not included in the production build. You must configure your production server's reverse proxy separately. The file must userequire()syntax (CommonJS), not ES moduleimport.
3.6 Absolute Imports
Configure absolute imports by setting baseUrl in jsconfig.json (JavaScript) or tsconfig.json (TypeScript):
jsconfig.json:
{
"compilerOptions": {
"baseUrl": "src"
}
}
Now you can import from src/ without relative paths:
// Instead of: import Button from '../../../components/Button';
import Button from 'components/Button';
3.7 Tailwind CSS
CRA supports Tailwind CSS natively. If a tailwind.config.js file exists at the project root, CRA's PostCSS pipeline automatically includes the Tailwind plugin.
- Install Tailwind:
npm install -D tailwindcss
npx tailwindcss init
- Configure
tailwind.config.js:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
};
- Add Tailwind directives to
src/index.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
3.8 PostCSS and Autoprefixer
CRA includes PostCSS with autoprefixer by default. Vendor prefixes are automatically added based on your browserslist configuration. No additional setup is needed. For example, if you write:
.container {
display: flex;
user-select: none;
}
The output CSS will include the necessary vendor prefixes for your target browsers automatically.
4. Core Usage
4.1 The Four Scripts
Every CRA project comes with four scripts defined in package.json. These are provided by react-scripts and cover the entire development lifecycle.
4.1.1 npm start — Development Server
npm start
Launches a development server at http://localhost:3000 with:
- Hot Module Replacement via React Fast Refresh — edits to React components update in the browser without losing component state
- ESLint overlay — lint errors and warnings appear directly in the browser
- Error overlay via react-error-overlay — runtime errors display a full-screen overlay with the stack trace, clickable to open the source in your editor
- Automatic browser opening — your default browser opens to the app URL
Expected terminal output:
Compiled successfully!
You can now view my-app in the browser.
Local: http://localhost:3000
On Your Network: http://192.168.1.100:3000
Note that the development build is not optimized.
To create a production build, use npm run build.
webpack compiled successfully
To change the port:
PORT=4000 npm start
To prevent the browser from auto-opening:
BROWSER=none npm start
4.1.2 npm test — Test Runner
npm test
Launches Jest in interactive watch mode. By default, it only runs tests related to files changed since the last commit, making the feedback loop fast.
Expected terminal output:
No tests found related to files changed since last commit.
Press `a` to run all tests, or run Jest with `--watchAll`.
Watch Usage
› Press a to run all tests.
› Press f to run only failed tests.
› Press q to quit watch mode.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press Enter to trigger a test run.
CRA's test setup includes:
- @testing-library/react — the recommended library for testing React components
- jest-dom matchers — via
src/setupTests.jswhich imports@testing-library/jest-dom - jsdom environment — tests run in a simulated browser environment
Example test file (src/App.test.js):
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
Running tests in CI (non-interactive mode):
CI=true npm test
In CI mode, Jest runs all tests once without watch mode and exits with a non-zero code if any test fails.
Running with coverage:
CI=true npm test -- --coverage
This generates a coverage report in the coverage/ directory.
Tip
The
src/setupTests.jsfile runs before every test. Use it to configure global test utilities, mock browser APIs, or import custom matchers. CRA scaffolds it withimport '@testing-library/jest-dom';by default.
4.1.3 npm run build — Production Build
npm run build
Creates an optimized production build in the build/ directory (configurable via BUILD_PATH). The build process:
- Minifies JavaScript and CSS
- Generates content-hashed filenames for long-term caching (e.g.,
main.a1b2c3d4.js) - Inlines small images as data URIs (below
IMAGE_INLINE_SIZE_LIMIT, default 10KB) - Generates source maps (unless
GENERATE_SOURCEMAP=false) - Produces a
build/asset-manifest.jsonlisting all generated files
Expected terminal output:
Creating an optimized production build...
Compiled successfully.
File sizes after gzip:
46.61 kB build/static/js/main.a1b2c3d4.js
1.78 kB build/static/js/787.f4c22a95.chunk.js
541 B build/static/css/main.073c9b0a.css
The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.
The build folder is ready to be deployed.
You may serve it with a static server:
serve -s build
Find out more about deployment at https://cra.link/deployment
Customizing the build output directory:
BUILD_PATH=dist npm run build
Skipping source maps (reduces build size and hides source code):
GENERATE_SOURCEMAP=false npm run build
Deploying to a subdirectory (e.g., https://example.com/my-app/):
Set homepage in package.json:
{
"homepage": "https://example.com/my-app"
}
Or use the PUBLIC_URL environment variable:
PUBLIC_URL=/my-app npm run build
4.1.4 npm run eject — Eject Configuration
npm run eject
Caution
Ejecting is a one-way operation. Once you eject, you cannot go back. This copies all configuration files (Webpack, Babel, ESLint, Jest) and transitive dependencies directly into your project. You become fully responsible for maintaining them. Consider this a last resort — most customizations can be achieved through environment variables,
.envfiles,src/setupProxy.js, or packages likecracoandreact-app-rewiredwithout ejecting.
After ejecting, your project gains:
config/directory with Webpack, Jest, and environment configurationscripts/directory withstart.js,build.js, andtest.js- All hidden dependencies added directly to
package.json
4.2 Supported File Types
CRA's Webpack configuration handles a wide range of file types out of the box:
| File Extension | Handling |
|---|---|
.js, .jsx |
Babel-transpiled JavaScript with React JSX support |
.ts, .tsx |
TypeScript compiled with Babel (type-checking via tsc) |
.mjs |
ES modules (processed by Webpack) |
.css |
Standard CSS, injected via style-loader in dev, extracted in production |
.module.css |
CSS Modules with locally scoped class names |
.scss, .sass |
Sass/SCSS (requires sass package) |
.module.scss, .module.sass |
CSS Modules with Sass |
.svg |
Importable as React component or URL |
.json |
Parsed and importable as a JavaScript object |
.bmp, .gif, .jpg, .jpeg, .png |
Inlined as data URI if below IMAGE_INLINE_SIZE_LIMIT (10KB), otherwise emitted as file |
.avif |
AVIF images, same inlining behavior as above |
4.3 CSS and Styling
4.3.1 Standard CSS
Import CSS files directly in your components:
import './App.css';
function App() {
return <div className="app-container">Hello</div>;
}
/* App.css */
.app-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
4.3.2 CSS Modules
Name your CSS file with .module.css to enable locally scoped class names that avoid naming collisions:
import styles from './Button.module.css';
function Button() {
return <button className={styles.primary}>Click me</button>;
}
/* Button.module.css */
.primary {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
}
At build time, the class name primary is transformed to something like Button_primary__x1y2z to guarantee uniqueness.
4.3.3 Sass/SCSS
Install the sass package to enable Sass support:
npm install sass
Then import .scss or .sass files:
import './App.scss';
// App.scss
$primary-color: #282c34;
.app-container {
background-color: $primary-color;
min-height: 100vh;
.header {
font-size: 2rem;
color: white;
}
}
4.3.4 CSS Modules with Sass
Combine CSS Modules and Sass by using the .module.scss or .module.sass extension:
import styles from './Card.module.scss';
function Card({ children }) {
return <div className={styles.card}>{children}</div>;
}
// Card.module.scss
$border-radius: 8px;
$shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.card {
border-radius: $border-radius;
box-shadow: $shadow;
padding: 16px;
}
4.4 Working with Assets
4.4.1 Images
Import images directly. Small images (below IMAGE_INLINE_SIZE_LIMIT, default 10,000 bytes) are inlined as data URIs; larger ones are emitted as files with content hashes:
import logo from './logo.png';
import icon from './small-icon.png'; // < 10KB, will be inlined
function Header() {
return (
<div>
<img src={logo} alt="Logo" />
<img src={icon} alt="Icon" />
</div>
);
}
Supported formats: BMP, GIF, JPEG, PNG, AVIF.
To change the inlining threshold:
# .env
IMAGE_INLINE_SIZE_LIMIT=5000
4.4.2 SVGs
SVGs can be imported two ways:
As a URL (standard image import):
import logo from './logo.svg';
function App() {
return <img src={logo} alt="Logo" />;
}
As a React component (inline SVG with full styling control):
import { ReactComponent as Logo } from './logo.svg';
function App() {
return <Logo className="app-logo" fill="currentColor" />;
}
The ReactComponent named export uses SVGR under the hood to transform the SVG file into a React component.
Tip
Importing SVGs as React components gives you full CSS and prop control over the SVG elements. Use this approach when you need to change colors, sizes, or animate SVG paths dynamically. Use the URL import when you just need a static image.
4.4.3 JSON
JSON files can be imported directly:
import config from './config.json';
console.log(config.apiEndpoint);
4.4.4 The public/ Directory
Files in the public/ directory are served at the root URL and are not processed by Webpack. Use this for:
index.html— the main HTML templatefavicon.ico,manifest.json,robots.txt- Assets that need a fixed URL (e.g.,
og:imagemeta tags)
Reference public assets in HTML using %PUBLIC_URL%:
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
Reference public assets in JavaScript using process.env.PUBLIC_URL:
<img src={process.env.PUBLIC_URL + '/images/banner.png'} alt="Banner" />
Note
Files in
public/are not minified, not content-hashed, and will cause 404 errors if missing at runtime with no compile-time warning. Prefer importing assets throughsrc/whenever possible — Webpack provides compile-time errors for missing files, content-hash caching, and size optimization.
4.5 TypeScript
4.5.1 Starting with TypeScript
Create a new TypeScript project:
npx create-react-app my-app --template typescript
This generates a project with .tsx source files, a tsconfig.json, and all necessary type definitions pre-installed (@types/react, @types/react-dom, @types/jest, @types/node).
4.5.2 Adding TypeScript to an Existing Project
- Install TypeScript and type definitions:
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
-
Rename any file to
.tsx(or.tsfor non-JSX files). -
Restart the development server. CRA automatically generates a
tsconfig.jsonif one doesn't exist.
4.5.3 TypeScript Configuration
CRA manages certain tsconfig.json fields and will overwrite them on start. You can customize other fields freely:
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": "src"
},
"include": ["src"]
}
Building despite type errors:
TSC_COMPILE_ON_ERROR=true npm start
This allows the development server to run even when there are TypeScript type errors, showing them as warnings instead of failures.
4.6 Flow
CRA supports Flow type annotations out of the box. Add a // @flow comment at the top of any file:
// @flow
import React from 'react';
type Props = {
name: string,
age: number,
};
function Greeting({ name, age }: Props) {
return <p>Hello {name}, you are {age} years old.</p>;
}
export default Greeting;
Babel strips Flow annotations during compilation. No additional configuration is required.
5. Common Tasks
5.1 Adding a Router
CRA does not include routing by default. Install React Router:
npm install react-router-dom
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
When deploying a single-page app with client-side routing, configure your server to serve index.html for all routes. Otherwise, direct URL access to /about will return a 404.
5.2 Adding Global State
Using React Context (built-in):
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
return useContext(ThemeContext);
}
5.3 Code Splitting
CRA supports code splitting via React.lazy and Suspense:
import React, { Suspense, lazy } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
Each lazy() import creates a separate JavaScript chunk that is loaded on demand.
5.4 Fetching Data
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/users')
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => setUsers(data))
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
During development, use the proxy field in package.json to avoid CORS issues when your API runs on a different port.
5.5 Progressive Web App (PWA)
CRA includes workbox-webpack-plugin for generating a service worker. To enable offline support:
- In
src/index.js, change the service worker registration:
// Change this:
serviceWorkerRegistration.unregister();
// To this:
serviceWorkerRegistration.register();
- The service worker caches your app shell and static assets. Subsequent visits load instantly from cache.
Warning
Enabling the service worker means users may see stale content until the new service worker activates (typically on the next page load). During development, always use
unregister()to avoid caching development builds.
5.6 Custom Error Overlay
CRA includes react-error-overlay, which displays a full-screen error overlay in development when:
- A JavaScript runtime error occurs
- An unhandled promise rejection happens
- A compilation error exists
The overlay shows the error message, stack trace, and clickable source locations that open the file in your editor. This behavior is automatic in development and is stripped from production builds.
5.7 ESLint Configuration
CRA includes eslint-config-react-app, which bundles rules from:
- ESLint core rules
- React-specific rules (
eslint-plugin-react) - JSX accessibility rules (
eslint-plugin-jsx-a11y) - React Hooks rules (
eslint-plugin-react-hooks) - Flow type rules (when Flow is used)
- TypeScript rules (when TypeScript is used)
Lint errors appear in the terminal and as a browser overlay during development.
Disabling ESLint entirely:
# .env
DISABLE_ESLINT_PLUGIN=true
Downgrading ESLint errors to warnings in development:
# .env
ESLINT_NO_DEV_ERRORS=true
Extending ESLint rules in package.json:
{
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"rules": {
"no-console": "warn",
"react/prop-types": "error"
}
}
}
5.8 Testing Patterns
5.8.1 Testing User Interactions
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments counter on button click', () => {
render(<Counter />);
const button = screen.getByRole('button', { name: /increment/i });
fireEvent.click(button);
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
5.8.2 Testing Async Operations
import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';
test('displays user data after loading', async () => {
render(<UserProfile userId="123" />);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
});
5.8.3 Mocking API Calls
// src/__mocks__/axios.js
export default {
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} })),
};
import axios from 'axios';
import { render, screen, waitFor } from '@testing-library/react';
import UserList from './UserList';
jest.mock('axios');
test('fetches and displays users', async () => {
axios.get.mockResolvedValue({
data: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }],
});
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('Alice')).toBeInTheDocument();
expect(screen.getByText('Bob')).toBeInTheDocument();
});
});
5.8.4 Snapshot Testing
import { render } from '@testing-library/react';
import App from './App';
test('matches snapshot', () => {
const { container } = render(<App />);
expect(container).toMatchSnapshot();
});
Snapshots are stored in __snapshots__/ directories. Update them with:
npm test -- --updateSnapshot
5.8.5 Coverage Reports
CI=true npm test -- --coverage
Expected output:
----------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
----------|---------|----------|---------|---------|
All files | 85.71 | 66.67 | 83.33 | 85.71 |
App.js | 100 | 100 | 100 | 100 |
utils.js | 71.43 | 33.33 | 66.67 | 71.43 |
----------|---------|----------|---------|---------|
5.9 Deployment
5.9.1 Static Server
The simplest way to serve a production build:
npm install -g serve
serve -s build
The -s flag enables single-page app mode, rewriting all routes to index.html.
5.9.2 Deploying to a Subdirectory
Set homepage in package.json:
{
"homepage": "https://mysite.com/app"
}
This sets PUBLIC_URL to /app, and all asset paths will be prefixed accordingly.
5.9.3 Deploying to GitHub Pages
- Install the deployment package:
npm install --save-dev gh-pages
- Add to
package.json:
{
"homepage": "https://<username>.github.io/<repo-name>",
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
}
}
- Deploy:
npm run deploy
5.9.4 Environment-Specific Builds
# Production build (default)
npm run build
# Build with a custom API URL
REACT_APP_API_URL=https://staging-api.example.com npm run build
# Build without source maps
GENERATE_SOURCEMAP=false npm run build
# Build to a custom directory
BUILD_PATH=dist npm run build
5.10 Using Web Workers
CRA does not have built-in Web Worker support in Webpack configuration, but you can use them with a workaround:
const worker = new Worker(new URL('./worker.js', import.meta.url));
worker.postMessage({ type: 'START', data: largeDataSet });
worker.onmessage = (event) => {
console.log('Worker result:', event.data);
};
5.11 Adding Custom Fonts
Via CSS imports:
/* src/index.css */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
}
Via local font files:
/* src/fonts.css */
@font-face {
font-family: 'CustomFont';
src: url('./fonts/CustomFont-Regular.woff2') format('woff2'),
url('./fonts/CustomFont-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
}
Place font files in src/fonts/ so Webpack processes them.
5.12 Analyzing Bundle Size
Add the source-map-explorer to inspect what contributes to your bundle:
npm install --save-dev source-map-explorer
Add a script to package.json:
{
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'"
}
}
Run:
npm run build
npm run analyze
This opens a treemap visualization showing the size contribution of each module.
6. Troubleshooting
6.1 'react-scripts' Is Not Recognized
Symptom: Running npm start fails with 'react-scripts' is not recognized as an internal or external command or command not found: react-scripts.
Cause: The node_modules directory is missing or corrupted.
Fix:
rm -rf node_modules
rm package-lock.json
npm install
On Windows:
rmdir /s /q node_modules
del package-lock.json
npm install
This issue is tracked in #17117.
6.2 ERR_OSSL_EVP_UNSUPPORTED on Node 17+
Symptom: Build or start fails with Error: error:0308010C:digital envelope routines::unsupported.
Cause: Node.js 17+ uses OpenSSL 3 which removed support for certain legacy algorithms that Webpack 5 / react-scripts depends on.
Fix (recommended) — set the OpenSSL legacy provider:
NODE_OPTIONS=--openssl-legacy-provider npm start
Or add it to your package.json scripts:
{
"scripts": {
"start": "react-scripts start",
"start:legacy": "NODE_OPTIONS=--openssl-legacy-provider react-scripts start",
"build:legacy": "NODE_OPTIONS=--openssl-legacy-provider react-scripts build"
}
}
This issue is extensively discussed in #17216. The most reliable long-term fix is to use Node 16 or 18 LTS.
Tip
Use a Node version manager like
nvmorfnmto easily switch between Node versions per project.
6.3 .env Variables Not Working
Symptom: Environment variables defined in .env files are undefined in the app.
Causes and fixes:
-
Missing
REACT_APP_prefix — Only variables starting withREACT_APP_are injected.API_URL=...will not work; useREACT_APP_API_URL=.... -
Server not restarted —
.envfiles are only read when the dev server starts. After editing.env, stop and restart:
# Stop the server (Ctrl+C), then:
npm start
-
Wrong file loaded — Check the loading priority. A value in
.env.development.localoverrides.env. -
Syntax errors — Each line must be
KEY=valuewith no spaces around=. No quotes needed unless the value contains spaces.
These issues appear across #13416, #11951, and #11954.
6.4 Cannot Find Module 'react-scripts/scripts/...' or interopRequireDefault
Symptom: Start or build fails with Cannot find module errors referencing internal react-scripts paths.
Cause: Corrupted node_modules or version mismatch between dependencies.
Fix:
rm -rf node_modules
rm package-lock.json
npm cache clean --force
npm install
If persisting, verify react-scripts is in dependencies (not devDependencies) in package.json:
{
"dependencies": {
"react-scripts": "5.1.0"
}
}
Related: #6398.
6.5 ESLint Cache Constantly Regenerated
Symptom: The .eslintcache file keeps appearing in your project root, cluttering version control.
Fix: Add .eslintcache to your .gitignore:
# .gitignore
.eslintcache
CRA's generated .gitignore should already include this, but older projects may not have it. Related: #10161.
6.6 Build Fails on Netlify / CI Platforms
Symptom: npm run build fails in CI with errors that do not reproduce locally.
Common causes:
-
Case sensitivity — Linux CI servers have case-sensitive file systems.
import Header from './header'fails if the file isHeader.js. Always match exact casing. -
Missing dependencies — Ensure all dependencies are in
dependencies(not justdevDependencies) if they're needed at build time. -
Node version mismatch — Pin your Node version in CI. Add an
.nvmrcfile:
18.19.0
Or set the Node version in your CI config. Related: #17208.
Important
CI environments typically set
CI=trueautomatically. In this mode, ESLint warnings are treated as errors and will fail the build. Fix all warnings locally, or setESLINT_NO_DEV_ERRORS=trueif you need a temporary workaround. SettingCI=falsein your CI environment disables this behavior but is not recommended.
6.7 Optional Chaining / Nullish Coalescing Not Working
Symptom: Syntax like obj?.property or value ?? default causes parse errors.
Cause: Older versions of react-scripts (pre-4.0) do not include Babel plugins for these operators.
Fix: Upgrade to react-scripts 4.0 or later:
npm install react-scripts@5.1.0
Related: #9908.
6.8 Dev Server Occupies Port 3000
Symptom: Something is already running on port 3000 when starting the dev server.
Fix (use a different port):
PORT=3001 npm start
Fix (find and kill the process):
# macOS/Linux
lsof -i :3000
kill -9 <PID>
# Windows
netstat -ano | findstr :3000
taskkill /PID <PID> /F
6.9 Hot Reload Not Working
Symptom: Changes to files do not trigger a browser refresh.
Common causes and fixes:
-
File is outside
src/— Webpack only watches files insidesrc/. Move the file there. -
FAST_REFRESH=false— Check your.envfiles; remove or setFAST_REFRESH=true. -
File system event limits (Linux):
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
- Network file system — Webpack's file watcher may not work on mounted drives or Docker volumes. Use polling:
CHOKIDAR_USEPOLLING=true npm start
6.10 Memory Issues During Build
Symptom: Build crashes with JavaScript heap out of memory.
Fix — increase Node's memory limit:
NODE_OPTIONS=--max-old-space-size=4096 npm run build
For very large projects, you may need 8192 (8GB).
7. FAQ
General
Q: Is Create React App still maintained?
A: No. CRA is officially deprecated as of the final 5.1.0 release. The React team recommends using frameworks like Next.js, Remix, or Vite-based setups for new projects. Existing CRA projects continue to work but will not receive new features or security patches to react-scripts.
Q: Can I use React 18 with CRA?
A: Yes. CRA 5.x supports React 18 out of the box. The default template scaffolds with ReactDOM.createRoot() for concurrent features.
Q: What build tool does CRA use?
A: CRA uses Webpack 5 for bundling and Babel 7 (with babel-preset-react-app) for JavaScript/TypeScript transpilation. The development server is webpack-dev-server.
Q: Is CRA a monorepo?
A: Yes. The CRA source repository is a monorepo managed with Lerna. Packages live in packages/*, including create-react-app (the CLI), react-scripts (the build tooling), babel-preset-react-app, eslint-config-react-app, and others. As an end user, you interact with the create-react-app CLI and the react-scripts package.
Configuration
Q: Can I customize the Webpack config without ejecting?
A: CRA does not expose the Webpack config directly. Community tools like CRACO (@craco/craco) and react-app-rewired allow you to override Webpack, Babel, and ESLint configuration without ejecting. These are third-party solutions and not officially supported.
Q: How do I add environment-specific configuration?
A: Use .env files with the NODE_ENV suffix: .env.development, .env.production, .env.test. Variables in these files are loaded automatically based on which script you run (start = development, build = production, test = test).
Q: Can I change the output directory for builds?
A: Yes. Set the BUILD_PATH environment variable:
BUILD_PATH=dist npm run build
Q: How do I use a different port for the dev server?
A: Set the PORT environment variable:
PORT=8080 npm start
Q: How do I serve my app from a subdirectory?
A: Set homepage in package.json to the full URL or path prefix:
{
"homepage": "/my-app"
}
All asset paths will be prefixed with /my-app.
Styling
Q: Does CRA support CSS-in-JS libraries? A: CRA does not include any CSS-in-JS library by default, but you can install and use any library (styled-components, Emotion, etc.) without additional configuration.
Q: How do CSS Modules work in CRA?
A: Name your CSS file with a .module.css (or .module.scss) extension. Import it and use the exported object for class names. Each class name is locally scoped and uniquely generated at build time to prevent collisions.
Q: Does CRA include PostCSS?
A: Yes. CRA includes PostCSS with autoprefixer enabled by default. Vendor prefixes are added automatically based on your browserslist configuration. If a tailwind.config.js exists, the Tailwind PostCSS plugin is also included automatically.
TypeScript
Q: Does CRA type-check at build time?
A: CRA uses Babel to strip TypeScript types during compilation (it does not run tsc for compilation). Type errors appear in the terminal and browser overlay during development via a separate type-checking process, but do not block the build by default. Set TSC_COMPILE_ON_ERROR=false if you want strict type checking to block compilation.
Q: Can I mix JavaScript and TypeScript files?
A: Yes. CRA supports .js, .jsx, .ts, and .tsx files in the same project. You can incrementally adopt TypeScript by renaming files one at a time.
Q: Does CRA overwrite my tsconfig.json?
A: CRA manages certain compiler options that it depends on (like isolatedModules, noEmit, jsx). It will reset these on startup if you change them. Other settings like strict, baseUrl, and custom paths are yours to configure.
Testing
Q: What testing library does CRA include? A: CRA includes Jest as the test runner and scaffolds projects with @testing-library/react and @testing-library/jest-dom for component testing.
Q: How do I run a single test file?
A: In watch mode, press p then type a regex matching the file name. Or run:
npm test -- --testPathPattern="App.test"
Q: How do I run tests in CI without watch mode?
A: Set CI=true:
CI=true npm test
Q: Where does CRA look for test files? A: Jest finds tests in any of these patterns:
- Files in
__tests__/directories - Files ending with
.test.jsor.test.tsx - Files ending with
.spec.jsor.spec.tsx
Build and Deploy
Q: How large is a typical CRA production build? A: A minimal CRA app produces approximately 40-50KB of gzipped JavaScript. The size grows with your application code and dependencies.
Q: Can I disable source maps in production? A: Yes:
GENERATE_SOURCEMAP=false npm run build
This reduces build output size and prevents exposing source code on production servers.
Q: What is the asset-manifest.json file?
A: After npm run build, CRA generates build/asset-manifest.json containing a mapping of logical file names to their content-hashed counterparts. Server-side rendering setups and deployment tools use this to reference the correct file names.
Q: Does CRA support server-side rendering?
A: CRA is designed for client-side single-page applications. It does not include server-side rendering. For SSR, consider Next.js or use ReactDOMServer with a custom server setup (which requires ejecting or using CRACO).
Progressive Web App
Q: Is the service worker enabled by default?
A: No. New CRA projects call serviceWorkerRegistration.unregister() by default. Change this to serviceWorkerRegistration.register() to enable offline support.
Q: What caching strategy does the service worker use? A: CRA uses workbox-webpack-plugin which generates a precache manifest of your build output. All static assets are cached on first load and served from cache on subsequent visits.
Ejecting
Q: Can I undo an eject?
A: No. Ejecting is irreversible. Once you eject, all configuration files are copied into your project and the react-scripts dependency is removed. There is no automated way to reverse this. Use version control to preserve a pre-eject snapshot.
Q: What files does eject create? A: Ejecting creates:
config/webpack.config.js— full Webpack configurationconfig/webpackDevServer.config.js— dev server configurationconfig/jest/— Jest transform and configurationconfig/env.js,config/paths.js,config/modules.js— environment and path utilitiesscripts/start.js,scripts/build.js,scripts/test.js— the three main scripts
Q: Are there alternatives to ejecting? A: Yes. CRACO and react-app-rewired allow you to override CRA's Webpack, Babel, and ESLint configuration without ejecting. Environment variables also cover many common customization needs.
For full configuration reference, security considerations, architecture internals, and deployment hardening, see the Admin Guide.