Project Setup with Create React App
Create React App (CRA) was the official way to bootstrap React applications from 2016 to around 2022. It gave developers a zero-configuration, batteries-included starter that handled Webpack, Babel, ESLint, and testing setup behind the scenes. For years, nearly every React tutorial started with npx create-react-app my-app.
Today, Vite is the recommended choice for new projects. However, CRA is still maintained and you will encounter it in existing codebases, bootcamp curricula, and older tutorials. Understanding CRA is therefore still practically important.
Creating a CRA Project
npx create-react-app my-app cd my-app npm start
npx downloads and runs create-react-app without installing it globally. After a minute or two (CRA installs many dependencies), your app is running at http://localhost:3000.
For TypeScript:
npx create-react-app my-app --template typescript
Understanding the Generated Structure
my-app/ ├── public/ │ ├── favicon.ico │ ├── index.html # the HTML shell (do not delete the #root div) │ ├── manifest.json # PWA manifest │ └── robots.txt ├── src/ │ ├── App.css # styles for the App component │ ├── App.js # root App component │ ├── App.test.js # example test │ ├── index.css # global styles │ ├── index.js # entry point — renders <App> into #root │ ├── logo.svg # the React spinning logo │ ├── reportWebVitals.js # optional performance measuring │ └── setupTests.js # configures Jest + React Testing Library ├── .gitignore ├── package.json └── README.md
Key Files Explained
public/index.html — The HTML shell. CRA injects the compiled JavaScript bundle here automatically. The crucial element is the <div id="root"> — this is where React mounts:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
<!-- CRA injects the bundle script tag here automatically -->
</body>
</html>src/index.js — The React entry point. In CRA, this uses the older ReactDOM.render in React 17 projects, but newer CRA projects use createRoot:
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'
import reportWebVitals from './reportWebVitals'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
// Measure performance (optional — remove if not needed)
reportWebVitals()src/App.js — The root component. Delete the boilerplate and start building here.
Available Scripts
npm start— start the development server at http://localhost:3000 with hot reloadnpm run build— create an optimized production build in thebuild/foldernpm test— run tests in watch mode using Jest and React Testing Librarynpm run eject— permanently exposes all configuration files (Webpack, Babel, ESLint). One-way operation — cannot be undone.
CRA's Build Limitations
CRA uses Webpack under the hood. On small projects this is fine. On larger codebases, the pain points become significant:
Slow cold starts — bundling everything upfront means waiting 30–120 seconds for the dev server on large projects
Slow HMR — replacing a module requires re-bundling parts of the dependency graph. On big apps, a file save can take several seconds to reflect in the browser
Large node_modules — CRA installs 1,000+ packages by default
Webpack config hidden — you cannot customize the build without ejecting or using workarounds like CRACO
No SSR — CRA only produces client-rendered apps
Migrating from CRA to Vite
If you have an existing CRA project and want to move to Vite, the migration is straightforward for most projects:
# 1. Install Vite and the React plugin npm install --save-dev vite @vitejs/plugin-react # 2. Remove CRA dependencies npm uninstall react-scripts
Create vite.config.js at the project root:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})Move public/index.html to the project root and update it: remove the %PUBLIC_URL% references and add a script tag pointing to src/index.jsx:
<!-- index.html at project root -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>Update the scripts in package.json:
{
"scripts": {
"start": "vite",
"build": "vite build",
"preview": "vite preview"
}
}Rename .js files that contain JSX to .jsx (or configure Vite to handle .js files with JSX). Update any process.env.REACT_APP_* environment variables to import.meta.env.VITE_*.
When CRA Still Makes Sense
You are working on an existing CRA project and a migration is not currently justified
A bootcamp or course curriculum requires CRA — follow along and learn the concepts; the React knowledge transfers directly to Vite
You need to replicate a tutorial or reproduce a bug report that was written using CRA