Creating one
create-react-app is no longer maintained. Vite starts instantly, rebuilds in milliseconds, and its production build is a normal static folder.
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev // http://localhost:5173
npm run build // → dist/
npm run preview // serve the built version locally
The files
index.html ← the real entry point; Vite reads this
src/
main.jsx ← finds #root and renders <App />
App.jsx ← your top component
components/ ← the rest
index.css
public/ ← copied verbatim; reference as /file.png
vite.config.js
package.json
Unlike older tooling, Vite treats index.html as source. The <script type="module" src="/src/main.jsx"> in it is what starts everything.
Mounting
createRootis the React 18 API.ReactDOM.renderis the old one and is gone.- One root per application. You can have several on a page — useful when adding React to an existing site a piece at a time.
- Importing CSS from a component is a bundler feature, not a React one. It works because Vite handles it.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>
);
StrictMode
In development, StrictMode deliberately runs your components twice and mounts, unmounts and remounts every effect once. It is not a bug and it does not happen in production.
It exists to surface two mistakes: a component that is not pure (it changes something outside itself during render) and an effect with no cleanup. If double-rendering breaks your component, the component is wrong.
A request fires twice in development, or a counter increments by two. Both mean the work is happening during render or in an effect without cleanup — fix the cause, do not remove StrictMode.
A component per file
- A component name starts with a capital letter. JSX treats a lower-case name as an HTML tag.
- One component per file, named after the file. Small helper components in the same file are fine.
- A default export per file is the convention here, even though named exports are generally better — the tooling and every tutorial assume it.
export default function Card({ door }) {
return (
<article className="card">
<h3>{door.name}</h3>
<p>{door.days} days</p>
</article>
);
}
import Card from "./components/Card.jsx";
export default function App() {
return (
<main>
<h1>Doors</h1>
<Card door={{ name: "Painted", days: 5 }} />
</main>
);
}
The dev server
- Changes appear without a reload — hot module replacement keeps component state where it can.
- An error appears as an overlay with the file and line, not only in the console.
- The dev server is not the production build. Test
npm run build && npm run previewbefore shipping.
Adding React to an existing page
This is often the right way in: keep the pages you have, and use React only for the parts that are genuinely interactive.
// one widget on a server-rendered page
const el = document.getElementById("configurator");
if (el) createRoot(el).render(<Configurator />);