How Trying to Fix Vulnerabilities Made Me Ditch Create React App
Have you ever broken a build just by trying to keep your project secure? You already know the truth — It’s time to let CRA go.
Photo by Arnold Francisca on Unsplash
You know the feeling — you run npm audit to tidy things up. A wall of vulnerabilities appears, so you try npm audit fix first. Nothing changes. The warnings glare back at you, daring you to go further. So you add --force, hit enter… and as you try to start the app, it unravels before your eyes — a sudden realization that the real issue isn’t your dependencies, it’s Create React App itself.
npm start
> my-react-ui@1.0.0 start
> react-scripts startsh: react-scripts: command not foundIf this sounds familiar, you’re not alone. And it’s not your fault.
The Harsh Truth: CRA is Abandoned
Let’s be honest — Create React App served us well, but it’s no longer evolving.
Official Status:
- Last major update: April 2022 (over 3 years ago)
- React documentation status: No longer recommends CRA
- Maintenance: Effectively abandoned
- Security patches: Not being released
- Future updates: None planned
The React documentation now says:
“We recommend using a framework like Next.js, Remix, or Gatsby for new React projects.”
Notice what’s missing? Create React App.
The Real Cost of Staying on CRA
The npm Audit Nightmare
This is the moment the cracks start to show. You run npm audit fix, hoping for a quick cleanup, but the deeper issue isn’t the vulnerabilities — it’s how tightly Create React App locks you in.
CRA pins you to specific versions of hundreds of dependencies. Each of those dependencies has its own nested tree of packages, and when npm audit tries to patch a security issue, that fragile structure collapses. The result? One --force later, your app breaks — not because of a bad patch, but because CRA’s rigid, outdated architecture can’t keep up with modern dependency updates.
You’re forced to choose between security and functionality. That’s not a choice any professional team should have to make.
The Locked Box Problem
CRA follows a philosophy of “zero configuration.” This sounds great until you need to configure something. Then you discover the truth: you have zero configuration options.
- Want to add a custom webpack loader? Can’t do it.
- Need to change the build output structure? Can’t do it.
- Want multiple entry points? Can’t do it.
- Need a custom Babel plugin? Can’t do it.
Your only option? Eject.
The Ejecting Trap: A One-Way Door to Maintenance Hell
When you eject from CRA, here’s what actually happens:
Before ejecting:
{
"dependencies": {
"react": "^18.2.0",
"react-scripts": "5.0.1"
}
}Clean. Simple. Two dependencies.
After ejecting:
You get 50+ dependencies, plus:
config/webpack.config.js(1000+ lines)config/webpackDevServer.config.js(150+ lines)scripts/build.js(500+ lines)scripts/start.js(300+ lines)
You now own and maintain over 3,000 lines of complex build configuration.
And here’s the kicker: ejecting is permanent. There’s no going back. No more CRA updates (not that there are any). You’re on your own.
It’s like buying a locked iPhone, discovering you need to customize it, so you jailbreak it — except once you jailbreak it, Apple will never support it again and you have to maintain the entire operating system yourself.
The Modern Solution
Vite (French for “fast”) represents the modern approach to web development tooling. Built by Evan You (creator of Vue.js) with React as a first-class citizen, it solves all of CRA’s problems.
Why Vite?
The frontend ecosystem has several modern build tools — Parcel, Snowpack, Turbopack, Esbuild — but Vite stands out for a few key reasons:
- Native ES Modules
Vite leverages native ESM in the browser for lightning-fast startup. You see code changes instantly, without long re-bundling times. - Production-Ready Out of the Box
While some tools focus on dev-only speed, Vite combines Esbuild for dev and Rollup for optimized production builds — the best of both worlds. - First-Class React & TypeScript Support
Vite has official plugins and tight integration for React, JSX, TS, and CSS Modules. Migration from CRA feels almost seamless. - Massive Ecosystem & Community
Backed by Evan You (creator of Vue) and widely adopted across frameworks (React, Svelte, Solid, Preact), Vite has matured into a cross-framework standard. - Faster Hot Module Replacement (HMR)
Changes appear in milliseconds — even in large codebases — thanks to on-demand file serving and efficient module graph tracking. - Future-Proof Tooling
With ongoing investment, broad adoption, and framework alignment (including React’s official docs recommending Vite), it’s not just another bundler — it’s the new baseline for modern frontend development.
Complete Vite Configuration
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000
},
build: {
outDir: 'build'
}
});How Vite Works
In development:
- You start the dev server
- Vite starts INSTANTLY (no bundling needed)
- Browser requests files as native ES modules
- Vite serves them directly
- Ready in 1–3 seconds
You change ONE file:
- Vite sends ONLY that file to the browser
- Browser hot-swaps the module
- Update appears in < 100ms
No bundling. No rebuilding. Just instant updates.
Get Tefe Mebuiefene’s stories in your inbox
Join Medium for free to get updates from this writer.
In production:
- Vite uses Rollup (optimized bundler)
- Bundles everything efficiently
- Tree-shaking actually works
- Smaller bundle sizes
- Faster builds (3–5x faster than Webpack)
Best of both worlds: fast development, optimized production.
Configuration Without Ejecting
Remember how CRA forces you to eject to customize anything? Vite gives you full control without ejecting.
Want to add SVG support?
npm install vite-plugin-svgrimport svgr from 'vite-plugin-svgr';export default defineConfig({
plugins: [react(), svgr()]
});Want to customize the build output?
export default defineConfig({
build: {
outDir: 'dist',
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
ui: ['primereact', 'primeicons']
}
}
}
}
});Want to add a proxy for API calls?
export default defineConfig({
server: {
proxy: {
'/api': 'http://localhost:8080'
}
}
});Every customization is clean, readable, and reversible. No ejecting. No maintenance nightmare.
The Migration: Easier Than You Think
The thought of migrating might seem daunting, but it’s more straightforward than you’d expect.
Prerequisite
- Node version: Vite requires Node.js 14.18+, but for the best performance and compatibility, use Node.js ≥ 18 (required for Vite 5+)
- React Version: Vite supports React 16.8 and later, but React 18+ is recommended for the best performance and compatibility.
Migration Steps
- Install Vite
npm install --save-dev vite @vitejs/plugin-react2. Create a config file for Vite
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
});What Needs to Change
Move index.html file from public/ to root directory and update script reference:
<script type="module" src="/src/index.js"></script>Scripts in your package.json:
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}Environment Variables:
# Before (CRA)
REACT_APP_API_URL=https://api.example.com# After (Vite)
VITE_API_URL=https://api.example.com// Before
const apiUrl = process.env.REACT_APP_API_URL;// After
const apiUrl = import.meta.env.VITE_API_URL;Success Metrics
Measure the impact:
- ✅ Dev server startup time: < 3 seconds
- ✅ HMR update time: < 100ms
- ✅ Production build time: < 2 minutes
- ✅ Bundle size reduction: > 20%
- ✅ Zero regression bugs
- ✅ Developer satisfaction: > 90%
The Bottom Line
Create React App served its purpose — it simplified React setup when tooling was complicated. But the ecosystem has evolved. Today, CRA is slow, outdated, and unsupported.
The question isn’t “Should we migrate?” The question is “How much longer can we afford NOT to?”
Have you migrated from CRA to Vite? Share your experience in the comments below. In the next post, I’ll walk through the real challenges I faced while migrating — and how I solved them.








