Lesson 12 of 12

React Project with Vite

Throughout this track you have run React straight in the browser with Babel. That is perfect for learning, but it is not how real apps are built. In this final lesson you will create a proper React project with Vite, a fast build tool, on your own computer, and see how the code you already know fits into separate files.

Why use a build tool?

A build tool does the heavy work before your site reaches visitors instead of in their browser. With Vite you get:

  • JSX compiled ahead of time, so visitors download plain, small JavaScript – no Babel in the page.
  • A development server that updates the page instantly when you save a file, often keeping the component’s state (Fast Refresh).
  • npm packages: you install React and other libraries with npm and import them by name, e.g. import { useState } from "react".
  • A production build that bundles, minifies and fingerprints your files for fast loading and caching.

Watch out: Old tutorials use create-react-app. The React team deprecated it for new apps in February 2025. Do not start new projects with it; use Vite, as shown here, or a framework.

Step 1: Install Node.js

Vite runs on Node.js, which also gives you npm, the package manager. Install the current LTS version from the official Node.js website. Vite needs Node.js 20.19 or newer (or 22.12 or newer on the 22 line). Check your version in a terminal with node -v. You will also want a code editor such as VS Code.

Step 2: Create a React project with Vite

Open a terminal in the folder where you keep your projects and run the commands below. The --template react option picks the plain JavaScript React template; react-ts would give you TypeScript instead. Depending on the version, create-vite may ask a question or two – read each prompt and accept the defaults if you are unsure.

npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev

The extra -- passes the --template option through npm to create-vite. When the dev server starts, it prints a local address, normally http://localhost:5173. Open it in your browser and you will see the starter page. Leave the terminal running while you work; press Ctrl + C to stop the server.

Step 3: Understand the files

File or folderWhat it is for
index.htmlThe single HTML page. In Vite it sits in the project root and loads /src/main.jsx.
src/main.jsxThe entry point. It calls createRoot and renders <App /> – the same job as the last lines of our browser examples.
src/App.jsxThe top-level component. Start editing here.
src/*.cssStylesheets imported straight into JavaScript files.
public/Files copied as they are, such as a favicon.
package.jsonProject name, dependencies (react, react-dom) and the dev, build and preview scripts.
node_modules/Installed packages. Never edit or upload it; npm install recreates it.
vite.config.jsVite settings, including the React plugin.
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>
);

Two differences from our browser examples stand out. Imports use bare package names ("react") instead of CDN URLs, and you do not need import React, because Vite uses the modern JSX transform that adds what it needs automatically. StrictMode turns on extra development-only checks, such as the double effect run you read about in React useEffect.

Step 4: Components in their own files

In a real project, each component usually gets its own file, named after the component. A file shares its component with export default, and other files load it with import. Here is a small tea-stall menu split into two files:

// src/MenuItem.jsx
export default function MenuItem({ name, price, onAdd }) {
  return (
    <li>
      {name} – ₹{price}{" "}
      <button type="button" onClick={() => onAdd(price)}>Add</button>
    </li>
  );
}

// src/App.jsx
import { useState } from "react";
import MenuItem from "./MenuItem.jsx";
import "./App.css";

const menu = [
  { id: 1, name: "Masala chai", price: 20 },
  { id: 2, name: "Filter coffee", price: 25 },
  { id: 3, name: "Bun butter jam", price: 40 }
];

export default function App() {
  const [total, setTotal] = useState(0);

  return (
    <main>
      <h1>Tea stall</h1>
      <ul>
        {menu.map(item => (
          <MenuItem key={item.id} name={item.name} price={item.price}
            onAdd={price => setTotal(t => t + price)} />
        ))}
      </ul>
      <p>Total: ₹{total}</p>
    </main>
  );
}

Save both files and the browser updates at once. Here is the same app in the learning setup, so you can try it on this page before building it on your computer:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>The tea stall app in the learning setup</title>
  <script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
    li { margin-bottom: 6px; }
  </style>
</head>
<body>
  <div id="root"></div>

  <script type="text/babel" data-type="module">
import React, { useState } from "https://esm.sh/[email protected]";
import { createRoot } from "https://esm.sh/[email protected]/client";

function MenuItem({ name, price, onAdd }) {
  return (
    <li>
      {name} – ₹{price}{" "}
      <button type="button" onClick={() => onAdd(price)}>Add</button>
    </li>
  );
}

const menu = [
  { id: 1, name: "Masala chai", price: 20 },
  { id: 2, name: "Filter coffee", price: 25 },
  { id: 3, name: "Bun butter jam", price: 40 }
];

function App() {
  const [total, setTotal] = useState(0);

  return (
    <main>
      <h1>Tea stall</h1>
      <ul>
        {menu.map(item => (
          <MenuItem key={item.id} name={item.name} price={item.price}
            onAdd={price => setTotal(t => t + price)} />
        ))}
      </ul>
      <p>Total: ₹{total}</p>
    </main>
  );
}

createRoot(document.getElementById("root")).render(<App />);
  </script>
</body>
</html>

Step 5: Build and preview

When you are ready to publish, run npm run build. Vite writes an optimised version of your app into a dist folder. Run npm run preview to test that build locally before you upload it. The dist folder is a set of static files – HTML, CSS and JavaScript – that any static web host can serve.

Where to go next

A React project with Vite is a single-page app: one HTML page whose content React changes. For multiple pages with their own URLs, server rendering or full-stack features, the React team points to frameworks built on React, such as Next.js and React Router. You do not need one to keep learning – practise first by building small Vite projects: a to-do list, a quiz, or a weather card that fetches data. When you are preparing for jobs, React Interview Questions and the Front-End Developer Roadmap will help you plan.

Further reading

Key takeaways

  • Real React projects use a build tool; this track uses Vite.
  • create-react-app is deprecated – do not use it for new projects.
  • Create a project with npm create vite@latest my-react-app -- --template react, then npm install and npm run dev.
  • Start editing in src/App.jsx; src/main.jsx renders it with createRoot inside StrictMode.
  • Put components in their own files and share them with export and import.
  • npm run build creates a dist folder of static files; npm run preview tests it.

Quick quiz

Check what you learned – pick an answer.

1. Which command creates a new React project with Vite?

2. What does npm run build produce?

3. Why do Vite projects not need import React at the top of every component file?

Practice exercise

Plan the file structure for a small Vite app, then build it here first. Create a ProfileCard component (name, role, city props) and a SkillList component (an array of skills rendered with keys), and use both from App. In your Vite project each would live in its own file in src/.

Show a hint

Write each component as if it were its own file: one function per component, then combine them in App.

Show the solution
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Profile app</title>
  <script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
    .card { border: 1px solid #ccc; border-radius: 8px; padding: 12px; max-width: 320px; }
  </style>
</head>
<body>
  <div id="root"></div>

  <script type="text/babel" data-type="module">
import React from "https://esm.sh/[email protected]";
import { createRoot } from "https://esm.sh/[email protected]/client";

// would be src/ProfileCard.jsx
function ProfileCard({ name, role, city }) {
  return (
    <section className="card">
      <h2>{name}</h2>
      <p>{role} · {city}</p>
    </section>
  );
}

// would be src/SkillList.jsx
function SkillList({ skills }) {
  return (
    <ul>
      {skills.map(skill => <li key={skill}>{skill}</li>)}
    </ul>
  );
}

const skills = ["HTML", "CSS", "JavaScript", "React", "Figma"];

function App() {
  return (
    <main>
      <ProfileCard name="Nivetha" role="Front-end developer" city="Pondicherry" />
      <h3>Skills</h3>
      <SkillList skills={skills} />
    </main>
  );
}

createRoot(document.getElementById("root")).render(<App />);
  </script>
</body>
</html>

Progress is saved in this browser only.