React App with Vite: Project Structure Explained
This article explains your first Vite React application's file structure, tracing how index.html, main.jsx, and App.jsx work together to render your UI. You'll understand the mount point, the JavaScript entry point, and how React initializes on the client side. This foundation is essential for confidently building larger applications.
Key Takeaways
index.htmlis the HTML shell with a<div id="root"></div>mount point where React injects your appmain.jsxis the JavaScript entry point that creates a React root and renders theAppcomponentApp.jsxis your root React component; all custom UI and state logic start here- The rendering flow is: browser loads HTML → loads main.jsx → React renders App to #root → browser displays UI
- The
src/directory contains all your application code (components, styles, assets)
What Is the Vite React Project Structure?
The Vite React project structure organizes your code into logical directories and entry points. Unlike older setups (Create React App), Vite treats index.html as a source file and leverages native ES modules for fast development. The three core files are index.html (the HTML shell), main.jsx (the JavaScript entry point), and App.jsx (your root component). Together, they form the bootstrap chain: the browser loads HTML, executes JavaScript that initializes React, and React renders your component tree into the mount point.
How Does index.html Work as the Mount Point?
Understanding the Root Element
index.html is the main HTML file your browser loads. Unlike traditional multi-page apps, a Vite React application is a single-page application (SPA); index.html provides the initial HTML shell and a designated target (<div id="root"></div>) where React injects the entire UI.
Open your project's index.html file:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
The <div id="root"></div> is the mount point. This is where React takes control of the DOM. When React initializes, it finds this element and replaces its contents with your rendered component tree. The <script type="module" src="/src/main.jsx"></script> tag loads your JavaScript as an ES module, enabling import and export statements natively in the browser.
Why Is the Mount Point Essential?
The mount point serves a critical purpose: it tells React exactly where to inject your application into the HTML DOM. React uses ReactDOM.createRoot(element) to create a root for this element, then renders your component hierarchy inside it. Without this mount point, React has no target to attach to. The type="module" attribute also signals the browser to parse main.jsx as an ES module, which Vite leverages for its speed and Hot Module Replacement (HMR) during development.
What Does main.jsx Do as the JavaScript Entry Point?
The Role of main.jsx
main.jsx is the first JavaScript file Vite loads. Its job is to import React, ReactDOM, and your root component, then tell React to render that component into the mount point.
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
Each line serves a specific purpose:
import React from 'react';— Imports the React library, which is required for JSX transformation and core hooks.import ReactDOM from 'react-dom/client';— ImportsReactDOM, the package that provides DOM-rendering methods. Theclientexport (introduced in React 18) is the client-side entry point.import App from './App.jsx';— Imports your root component, where all your custom UI logic resides.import './index.css';— Imports global CSS that applies to the entire application.ReactDOM.createRoot(document.getElementById('root'))— Finds the#rootelement inindex.htmland creates a React root. This root is the container where React manages all DOM updates..render(<React.StrictMode><App /></React.StrictMode>)— Tells React to render theAppcomponent inside the root, wrapped inStrictMode(a development helper that catches unsafe practices).
React.StrictMode is a wrapper that activates additional development-time checks. It does not render any visible UI; it just adds warnings to help you find bugs during development.
How Does App.jsx Structure Your Root Component?
The src Directory Organization
The src/ directory holds all your application's editable code:
src/
├── assets/
│ └── react.svg
├── App.css
├── App.jsx
├── index.css
└── main.jsx
assets/— Static files (images, SVGs, fonts) imported and used by componentsApp.css— CSS specific to the App component (or global if imported in main.jsx)App.jsx— Your root React component; the entry point for your custom UIindex.css— Global styles imported in main.jsx to apply sitewidemain.jsx— The JavaScript entry point
Inside App.jsx: The Root Component
The App component is the top of your component tree. All other components are nested within it or imported into it.
import { useState } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';
function App() {
const [count, setCount] = useState(0);
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank" rel="noopener noreferrer">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank" rel="noopener noreferrer">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
);
}
export default App;
Key elements:
import { useState } from 'react';— Imports theuseStatehook for managing component state- Asset imports —
./assets/react.svgis relative to the current file;/vite.svgrefers to thepublicfolder <>and</>— A React Fragment, which groups multiple JSX elements without adding an extra DOM nodeconst [count, setCount] = useState(0);— Declares a state variablecountinitialized to 0onClick={() => setCount((count) => count + 1)}— An event handler that incrementscountwhen the button is clicked, triggering a re-render
When you click the button, setCount updates the state, React re-renders the component, and only the count display updates in the DOM—a key React optimization.
What Is the Complete Rendering Flow?
Step-by-Step: From File to Browser Display
- Browser loads
index.html— When you runnpm run dev, Vite servesindex.htmlto the browser. index.htmlloadsmain.jsx— The<script type="module" src="/src/main.jsx"></script>tag tells the browser to load and executemain.jsxas an ES module.main.jsxinitializes React — It imports React, ReactDOM, and yourAppcomponent, then callsReactDOM.createRoot(document.getElementById('root')).render(<App />)to renderAppinto the#rootmount point.App.jsxruns and returns JSX — TheAppfunction executes, initializes its state withuseState, and returns JSX describing the UI (logos, heading, button, paragraph).- React builds the Virtual DOM — React takes your JSX and creates a Virtual DOM tree, an in-memory representation of the UI.
- React renders to the Real DOM — React compares its Virtual DOM to the browser's Real DOM, identifies differences, and applies only the necessary changes to the browser DOM.
- Browser displays the UI — The browser updates the visual display, and you see your interactive Vite + React page.
This entire process takes milliseconds. When you later click the button, the flow repeats: setCount triggers a state update, React re-renders App, diffs the Virtual DOM, and updates only the count text in the Real DOM—not the entire page.
Best Practices for Your React Project Structure
Organizing Your Application as It Grows
As your application grows beyond a single App.jsx, follow these practices:
- Modular Components — Break
App.jsxinto smaller, focused components (e.g.,Header.jsx,Sidebar.jsx,ProductList.jsx). Import and compose them withinApp.jsx. - Clear File Naming — Use descriptive names:
UserProfile.jsx, notComponent.jsx. - Consistent Styling — Choose one styling approach (global CSS, CSS Modules, or CSS-in-JS) and apply it consistently across your project.
- Understand
vite.config.js— As your project grows, customize Vite's behavior invite.config.js(e.g., setting path aliases like@for thesrcfolder).
Common Customizations
- Change the page title — Edit the
<title>tag inindex.html. - Modify your root component — Update
App.jsxto build your own UI. - Add global styles — Modify
index.cssor import other stylesheets intomain.jsx.
Frequently Asked Questions
What is the difference between index.html and App.jsx?
index.html is the static HTML shell that the browser loads first; it contains the mount point (<div id="root"></div>) and the script tag. App.jsx is a React component (JavaScript) that renders the actual UI content. React renders App.jsx into the #root element at runtime. index.html provides the initial structure; App.jsx provides the dynamic content.
Why does main.jsx import App but also import CSS files?
main.jsx is the entry point where all imports flow through before your application runs. By importing CSS in main.jsx (or in App.jsx), you ensure that styles are loaded and applied to your page before the component tree renders. Vite processes these imports and bundles them; the CSS is injected into the HTML as <style> tags or linked stylesheets.
What is React.StrictMode and do I need it?
React.StrictMode is a development-only wrapper that activates additional checks and warnings for its child components. It helps you find unsafe patterns like deprecated lifecycle methods or unexpected side effects. It does not render any visible UI and has zero impact in production. You can remove it if you prefer, but it is recommended for learning and development.
Can I have multiple mount points in index.html?
Technically yes, but it is not recommended. A Vite React application typically has one root component mounted to one mount point (#root). If you need multiple independent React applications on a single page, you would create multiple roots and mount them to different elements, but this is an advanced pattern. For most applications, one mount point and one root suffice.
What does Hot Module Replacement (HMR) mean in the context of Vite?
Hot Module Replacement is Vite's feature that updates your code in the browser without a full page reload. When you save a change to App.jsx or index.css, Vite detects the change and injects only the modified module into your running application, preserving state. This dramatically speeds up the development feedback loop. HMR is why Vite development feels so fast compared to Create React App or webpack-based setups.
Further Reading
- Vite Official Guide: Getting Started
- React Official Docs: Start a New React Project
- MDN Web Docs: ES Modules
Last updated: June 2, 2026 by Dr. Alex Turner