Skip to content
elephantoo

Setting up with Vite

Lesson 2 of 30 14 min read

Create a React project with Vite, tour the files, run the dev server and build for production.


To build React apps you need a tool that compiles JSX, bundles your modules, and gives you a fast development server with instant updates. The standard choice today is Vite (French for "quick", pronounced "veet"). In this lesson you'll create a project, understand every file it generates, and learn the commands you'll use daily.

What about Create React App? CRA was the old default but has been deprecated since 2025. Use Vite for single-page apps, or a framework such as Next.js when you need server rendering (covered at the end of this course).

Prerequisites#

You need Node.js (a current LTS such as Node 24) and npm. Check:

Terminal
node --version
npm --version

You'll also want a code editor — VS Code is the most popular — and a modern browser with the React Developer Tools extension (Chrome, Edge or Firefox).

Create a project#

Terminal
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
  • npm create vite@latest downloads and runs Vite's project generator.
  • --template react chooses React with JavaScript. Use --template react-ts for TypeScript (recommended once you've done the TypeScript lesson). Without --template, it asks you interactively.
  • npm install downloads React, Vite and other dependencies into node_modules/.
  • npm run dev starts the dev server.
Output
  VITE v8.x  ready in 300 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose

Open http://localhost:5173 and you'll see the starter page with a counter button. Leave the server running while you work.

A tour of the project#

Output
my-react-app/
├─ index.html          ← the single HTML page
├─ package.json        ← dependencies and scripts
├─ vite.config.js      ← Vite configuration
├─ public/             ← static files copied as-is (favicon, robots.txt)
└─ src/
   ├─ main.jsx         ← entry point: mounts React
   ├─ App.jsx          ← the root component
   ├─ App.css          ← styles for App
   ├─ index.css        ← global styles
   └─ assets/          ← images imported from code

(The generator also adds a .gitignore, a linter config such as .oxlintrc.json, and a README.)

index.html

HTML
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <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>

Unlike older tools, Vite treats index.html as the real entry point. The empty <div id="root"> is where React will render your whole app, and the module script loads your code.

src/main.jsx

JSX
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
  </StrictMode>
);
  • react contains the core (components, hooks); react-dom/client connects React to the browser DOM.
  • createRoot(element) creates a React root inside #root, and .render(<App />) draws your app into it.
  • <StrictMode> enables extra development-only checks — it intentionally runs some code twice to reveal bugs. It has no effect in production.
  • Importing a CSS file (import "./index.css") makes Vite inject it into the page.

src/App.jsx

Replace the starter with something minimal:

JSX
import { useState } from "react";
import "./App.css";

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <main>
      <h1>Hello, React! 👋</h1>
      <button onClick={() => setCount((c) => c + 1)}>Clicked {count} times</button>
    </main>
  );
}

Save, and the browser updates instantly without reloading — and without losing the counter's value. That's Hot Module Replacement (HMR).

package.json

JSON
{
  "name": "my-react-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "oxlint",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^19.2.8",
    "react-dom": "^19.2.8"
  },
  "devDependencies": {
    "@types/react": "^19.2.18",
    "@types/react-dom": "^19.2.7",
    "@vitejs/plugin-react": "^6.1.1",
    "oxlint": "^1.81.0",
    "vite": "^8.3.0"
  }
}

(Exact versions depend on when you create the project. Current templates use Oxlint, a very fast linter; older ones use ESLint.)

CommandWhat it does
npm run devStart the dev server with HMR
npm run buildCreate an optimised production build in dist/
npm run previewServe the dist/ build locally to test it
npm run lintCheck code for problems

vite.config.js

JavaScript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
});

The React plugin enables JSX and Fast Refresh. You'll add more here later — Tailwind, path aliases, a dev proxy for your API.

Organising your src folder#

There's no single "correct" structure. A common one as the app grows:

Output
src/
├─ main.jsx
├─ App.jsx
├─ components/     ← reusable UI: Button.jsx, Card.jsx, Modal.jsx
├─ features/       ← feature folders: courses/, auth/, cart/
├─ hooks/          ← custom hooks: useLocalStorage.js
├─ lib/            ← API clients, helpers
└─ styles/

Conventions:

  • One component per file, named in PascalCase: CourseCard.jsx exports CourseCard.
  • Files containing JSX use the .jsx (or .tsx) extension.
  • Keep components that change together close together.

Static assets#

JSX
import logo from "./assets/logo.svg"; // Vite gives you the final URL

export function Logo() {
  return <img src={logo} alt="Elephantoo" width={40} height={40} />;
}

Files in src/assets are processed (hashed file names for caching, small images inlined). Files in public/ are served as-is at the root: public/robots.txt → /robots.txt.

Environment variables#

Create .env (or .env.local, which git should ignore) in the project root:

Terminal
VITE_API_URL=https://api.example.com
SECRET_KEY=do-not-expose
JSX
const apiUrl = import.meta.env.VITE_API_URL;   // "https://api.example.com"
console.log(import.meta.env.SECRET_KEY);         // undefined – not prefixed
console.log(import.meta.env.DEV, import.meta.env.MODE); // true "development" (in dev)

Only variables starting with VITE_ are exposed to your code — and anything exposed ends up in the JavaScript bundle where anyone can read it. Never put secrets in front-end environment variables.

Building for production#

Terminal
npm run build
npm run preview
Output
dist/index.html                   0.46 kB
dist/assets/index-BmS5x0vZ.css    1.39 kB
dist/assets/index-C7a2xyG1.js   190.12 kB │ gzip: 60.04 kB
✓ built in 450ms

dist/ contains plain static files — HTML, CSS and JavaScript — that any static host can serve. You'll deploy them in the Deployment lesson.

  • VS Code extensions: your linter's extension (Oxlint or ESLint), Prettier, and optionally "ES7+ React snippets".
  • React Developer Tools browser extension: adds Components and Profiler tabs to DevTools so you can inspect props, state and renders.
  • Turn on format on save with Prettier.

Common mistakes#

  • Opening index.html directly from the file system instead of using npm run dev — modules and JSX need the dev server.
  • Running npm run dev in the wrong folder (cd my-react-app first).
  • Forgetting to restart the dev server after editing .env or vite.config.js.
  • Naming a file app.js and putting JSX in it — use .jsx.
  • Committing node_modules/ or dist/.

What's next#

Your project is running. Next, learn JSX properly — the syntax you'll write in every component.

Check your understanding

Quick quiz

0/3 answered
  1. 1.Which command creates a new React project with Vite?

  2. 2.In a Vite project, which file is the entry point that mounts React into the page?

  3. 3.How do you expose an environment variable to your React code in Vite?

Finished reading?

Mark this lesson complete to track your progress.