Create React App (CRA) is still useful, but modern workflows often prefer lighter, faster tools such as Vite, Parcel, or esbuild. In this guide you’ll build a minimal, production-ready React app using Vite as the dev server and build tool — no CRA, no opaque configuration, and no template generators. You’ll learn how files map to behavior, how to run dev and production builds, and common gotchas.
What you’ll need
- Node.js 14+ (Node 18+ recommended)
- npm or yarn
- A code editor (VS Code recommended)
Goal
Create a simple React app with:
- index.html entry
- src/main.jsx and src/App.jsx
- CSS file
- npm scripts for dev, build, and preview
Step 1 — Create the project folder
Run in your terminal:
mkdir my-react-app && cd my-react-app
npm init -y
Step 2 — Install dependencies
Install React, ReactDOM, and Vite as a dev dependency:
npm install react react-dom
npm install -D vite
Step 3 — Add minimal npm scripts
In package.json add these scripts so you can run the dev server and build:
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview --port 5000"
}
Step 4 — Create the HTML entry
Create an `index.html` at the project root. Vite serves this file and injects the module script to `src/main.jsx`.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My React App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Note: In the snippet above the JSX mount point is `<div id=”root”>` and the module script loads `src/main.jsx`.
Step 5 — Create the React entry and App component
Create `src/main.jsx` and `src/App.jsx`.
src/main.jsx
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import './styles.css'
const container = document.getElementById('root')
const root = createRoot(container)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
src/App.jsx
import React, { useState } from 'react'
export default function App() {
const [count, setCount] = useState(0)
return (
<div className="app">
<h1>Hello, React (Vite)!</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
)
}
Note: Angle brackets in JSX are escaped above using `<` and `>` so the code sample renders safely in the article.
Step 6 — Add a tiny stylesheet
Create `src/styles.css`:
body {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
margin: 0;
padding: 2rem;
background: #f5f7fb;
color: #111827;
}
.app {
max-width: 640px;
margin: 0 auto;
background: white;
padding: 1.25rem;
border-radius: 8px;
box-shadow: 0 6px 18px rgba(17, 24, 39, 0.08);
}
Step 7 — Start the dev server
Run:
npm run dev
Vite will start on port 5173 by default. Open the printed URL (usually http://localhost:5173) and you should see your app.
Quick explanation: How this works
- index.html is the app entry served by Vite.
- `<script type=”module” src=”/src/main.jsx”>` tells the browser to load your module; Vite uses native ES modules in dev.
- Vite handles JSX transforms and HMR (hot module replacement) with near-instant reloads.
Build for production
When you’re ready to produce an optimized build:
npm run build
npm run preview
`vite build` produces an optimized `dist` directory with hashed assets. `vite preview` serves the production build locally so you can validate it.
Optional: TypeScript
To use TypeScript, install TypeScript and types and rename files:
npm install -D typescript @types/react @types/react-dom
mv src/main.jsx src/main.tsx
mv src/App.jsx src/App.tsx
# add tsconfig.json (Vite will use it)
Vite has first-class TypeScript support — it delegates type checking to `tsc` but still handles transforms quickly.
Why use Vite instead of CRA?
- Faster cold-start and HMR — Vite uses ES modules and on-demand transforms.
- Smaller dev dependency surface and simpler configuration.
- Production builds use Rollup underneath for good optimization.
- You get more control if you want to extend the setup.
Alternatives
If you prefer zero-config bundling, Parcel is another great choice. For ultra-fast builds you can explore esbuild or Bun (runtime + bundler), but Vite offers a balanced developer experience and ecosystem.
Troubleshooting
- Blank page: open dev console and ensure `root` element exists and your `script` path in `index.html` is exactly `/src/main.jsx`.
- JSX not compiling: ensure you use Vite’s dev server (npm run dev) — it handles the transform.
- Module not found: check relative imports (start with `./` or `/`).
Next steps
- Add a router: react-router-dom
- Add state management (Zustand, Redux Toolkit)
- Add CSS modules or Tailwind CSS
- Add unit tests (Vitest for Vite-native testing)
Conclusion
You now have a minimal, fast React app without Create React App. The total setup is lightweight and excellent for prototypes or production apps that want control over configuration. From here, incrementally add the tools you need (TypeScript, routing, testing) while keeping build times low.
Happy coding!
