Why Create React App Is Dead
Create React App is officially deprecated and no longer maintained. Vite has taken over as the standard for React tooling: natural ESM in the dev server, sub-100ms hot module replacement, and a configuration that never requires ejecting. This article explains why the switch is worth it and how it goes smoothly.
Table of Contents
- 1. The Problem With Create React App
- 2. Why Vite? The Core Design
- 3. Creating a Vite React Project
- 4. Configuring vite.config.ts Correctly
- 5. Migrating From CRA to Vite
- 6. TypeScript and Path Aliases
- 7. Environment Variables in Vite
- 8. Production Build and Optimizations
- 9. Vite vs. CRA vs. Next.js
- 10. Summary
- 11. FAQ
1. The Problem With Create React App
Create React App was introduced by Facebook in 2016 as a zero-config solution for new React projects. At the time this was revolutionary: Webpack configuration, Babel setup and ESLint integration were complex and time-consuming. CRA bundled everything into a single react-scripts package and enabled an instant start. The problem: CRA became a victim of its own success. The maintenance burden grew too large, Webpack as the core stayed slow, and the team could not keep pace with the tempo of the ecosystem.
Since 2023, Create React App has been officially deprecated, the official React documentation no longer recommends it and points instead to Next.js, Remix and Vite. The reasons are concrete: CRA projects start in 15 to 60 seconds in mid-sized codebases. HMR cycles take several seconds. The internal Webpack configuration is locked away by react-scripts, customization requires ejecting, which breaks the zero-config guarantee. Dependency security issues in outdated transitive packages keep piling up. For every new React application without framework requirements, Vite is the right starting point today.
2. Why Vite? The Core Design
Vite (French for "fast") was created by Evan You, the creator of Vue.js, and is built on a fundamentally different design than Webpack. In development mode Vite does not bundle files at all, instead it uses native ES modules directly in the browser. The browser requests modules on demand, and Vite transforms them on the fly. That means: startup time is proportional to the number of modules actually needed for the initial load, not to the overall size of the application. In practice, a Vite dev server starts in under a second regardless of project size.
Hot module replacement in Vite is precise and fast: when a file changes, Vite sends only the changed module to the browser, no re-bundling, no full recompilation. This results in typical HMR cycles of under 100 milliseconds. For the production build, Vite uses Rollup, a battle-tested bundler that excels at tree-shaking and code-splitting. This is a smart separation: development optimized for speed, production optimized for bundle size and compatibility.
# Create a new React + Vite project (npm, yarn, or pnpm)
npm create vite@latest my-react-app -- --template react-ts
# Alternative: with yarn
yarn create vite my-react-app --template react-ts
# Available templates:
# react - React + JavaScript
# react-ts - React + TypeScript
# react-swc - React + JavaScript + SWC (faster transform)
# react-swc-ts - React + TypeScript + SWC (recommended 2026)
cd my-react-app
npm install
npm run dev # starts in < 300ms, HMR ready
# Project structure created:
# ├── index.html ← entry point (NOT /public, this is intentional)
# ├── vite.config.ts ← Vite configuration
# ├── tsconfig.json ← TypeScript config
# ├── tsconfig.node.json ← separate config for vite.config.ts itself
# ├── public/ ← static assets (copied as-is)
# └── src/
# ├── main.tsx ← React tree root
# ├── App.tsx ← root component
# └── vite-env.d.ts ← Vite type declarations (ImportMeta etc.)
3. Creating a Vite React Project
Getting started with Vite for React is more minimal than with CRA. Scaffolding via npm create vite@latest produces a lean project structure without hidden configuration layers. One important difference from CRA: index.html sits in the project root, not in /public. This is not a bug, it is by design: Vite treats index.html as the entry point of the dependency graph and can therefore reference static assets directly from the HTML file.
The choice of transformer matters for performance: by default, Vite uses Babel for JSX transformation. The alternative is SWC (Speedy Web Compiler), a transformer written in Rust that is 20 to 70 times faster than Babel. The react-swc-ts template is the recommended starting configuration for 2026: TypeScript support without a separate ts-jest or ts-loader configuration, and SWC as the fastest possible transformer. The difference in day-to-day work: noticeably faster HMR cycles with complex components.
4. Configuring vite.config.ts Correctly
The vite.config.ts file is the central configuration point and is far more approachable than an ejected Webpack config. The React plugin (@vitejs/plugin-react or @vitejs/plugin-react-swc) enables JSX transformation and React Fast Refresh. Beyond that, the most common configuration points are: resolve.alias for path aliases, server.port for the dev server port, server.proxy for API proxying during development, and build.outDir for the build output directory.
A major advantage over CRA: this configuration is always accessible and never needs to be exposed via ejecting. Vite plugins are normal npm packages registered in plugins. The plugin ecosystem has grown: plugins for SVGs as React components, for automatic imports, for mock servers, for bundle analysis and for Tauri desktop apps are all readily available. The configuration itself stays clear, a few dozen lines cover most project requirements.
// vite.config.ts - complete practical configuration
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc'; // SWC for fast transforms
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
// Path aliases, mirror in tsconfig.json paths
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@hooks': resolve(__dirname, 'src/hooks'),
'@utils': resolve(__dirname, 'src/utils'),
},
},
server: {
port: 3000,
open: true, // open browser on start
proxy: {
// Proxy API calls to backend during development
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, ''),
},
},
},
build: {
outDir: 'dist',
sourcemap: true, // enable for production debugging
rollupOptions: {
output: {
// Manual chunk splitting, keeps vendor separate from app code
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
},
},
},
},
// Global test configuration (Vitest)
test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/test/setup.ts',
},
});
5. Migrating From CRA to Vite
Migrating from CRA to Vite is done within one to two hours for most projects. The main steps: remove react-scripts from the dependencies, install Vite and the React plugin, move index.html from /public to the project root and replace the %PUBLIC_URL% placeholder pattern with Vite's asset handling, create a vite.config.ts and adjust the npm scripts in package.json (vite instead of react-scripts start).
The most common stumbling block during migration is environment variables. CRA uses the prefix REACT_APP_ for public variables. Vite uses VITE_. All process.env.REACT_APP_* references need to be switched to import.meta.env.VITE_*. A second common stumbling block: CRA allows require() for static assets, Vite does not. All asset imports need to be switched to ESM imports (import logo from './logo.png'). Jest tests built on react-scripts test can be migrated to Vitest, both use similar APIs, the migration is minimal.
6. TypeScript and Path Aliases
Vite supports TypeScript out of the box without additional configuration, it uses TypeScript only for type checking but transforms the files with Babel or SWC (depending on the plugin choice). That means: TypeScript errors do not block the dev server. This is a deliberate design decision: Vite optimizes for speed, and TypeScript type checking is expensive. For CI and production builds, tsc --noEmit should be run separately.
Path aliases need to be configured in two places: in vite.config.ts under resolve.alias and in tsconfig.json under compilerOptions.paths. Both configurations must match, Vite for the actual module resolution in dev/build, TypeScript for IDE support and type checking. The vite-tsconfig-paths library can synchronize both by automatically translating the tsconfig.json paths into Vite aliases, then only tsconfig.json needs to be maintained.
7. Environment Variables in Vite
Vite's environment variable system is more structured than CRA's approach. Files are loaded in this order: .env, .env.local, .env.[mode], .env.[mode].local. The mode is set at startup via the --mode flag or defaults to development (dev server) or production (build). Variables with the VITE_ prefix are exposed in the client bundle and are accessible via import.meta.env.VITE_VARIABLENAME. Variables without the prefix stay server-side, they are not visible in the client.
import.meta.env also contains a few built-in variables: MODE (the current mode), BASE_URL (the configured base URL), DEV and PROD (booleans for the current context). For TypeScript support of your own variables, a src/vite-env.d.ts file can be extended to augment the ImportMetaEnv interface, which gives you autocompletion and type checking for all your own VITE_ variables.
8. Production Build and Optimizations
The Vite production build uses Rollup and is optimal in most projects without further configuration. Code-splitting happens automatically: every dynamic import (const Comp = await import('./Comp')) becomes its own chunk. Static imports are bundled and tree-shaken. CSS is extracted and minified. Assets below 4 KB are inlined as base64 into the bundle, this threshold is configurable via build.assetsInlineLimit.
For large applications, manual chunk splitting via build.rollupOptions.output.manualChunks is recommended. Move React and React DOM into their own vendor chunk, it gets cached separately since it changes less often than the application code. The Rollup visualizer plugin can be used to analyze bundle composition and chunk sizes. For Brotli compression in deployment, the vite-plugin-compression plugin needs only minimal configuration and automatically generates .gz and .br variants of all static assets.
| Feature | Create React App | Vite | Next.js |
|---|---|---|---|
| Status | Deprecated | Active, recommended | Active (framework) |
| Dev server start | 15-60 sec. | < 1 sec. | 2-5 sec. |
| HMR | 1-5 sec. | < 100 ms | ~200 ms |
| Configuration | Ejecting required | vite.config.ts | next.config.js |
| SSR | Not supported | Possible (manual) | Built in |
10. Summary
Create React App had its time, and it introduced a huge number of React developers to the language and the ecosystem. But as a deprecated project without active maintenance, it is no longer a viable foundation for new projects or existing codebases that need to keep evolving. Vite is the modern successor for every scenario that does not require a full framework like Next.js: client-side SPAs, prototypes, internal tools, libraries. The developer experience is better in every dimension, faster startup, faster HMR, transparent configuration without ejecting.
Migrating existing CRA projects can be done in a few hours with a clear migration plan and an understanding of the differences around environment variables, asset handling and TypeScript setup. For new projects, starting in 2026: npm create vite@latest -- --template react-swc-ts is the recommended starting command. SWC as the transformer, TypeScript and React Fast Refresh are ready immediately, and the configuration freedom of Vite grows with the project, without ever needing to eject.
Vite for React: The Essentials at a Glance
CRA is deprecated
Officially no longer recommended. No active maintenance. Security issues in transitive dependencies keep piling up. Not an option for new projects.
Vite: native ESM
Dev server transforms modules on demand. No re-bundling. Start under 1 second regardless of project size. HMR under 100 ms.
Migration
Main steps: install Vite, move index.html, rename REACT_APP_ to VITE_, replace require() with ESM imports.
2026 recommendation
npm create vite@latest -- --template react-swc-ts. SWC, TypeScript and React Fast Refresh out of the box. No hidden configuration.