Himanshu SharmaSoftware engineer
Let’s talk

React · Lesson 4 of 38

What is inside a Vite React project

Tour every file in the Vite React template: index.html, main.jsx, App.jsx, package.json, public, assets and config, and how they fit together.

12 minGetting startedUpdated 24 October 2026

By the end of this lessonSay what each file and folder in a Vite React project is for, and trace how the page gets from index.html to your App component.

The template creates about a dozen files. You will work in two of them most of the time, but knowing what the others do means that nothing in your project is a mystery box. This tour uses the files that npm create vite@latest my-app -- --template react generated, checked on create-vite 9.3.1. Newer versions may differ in small details, but the shape stays the same.

The folder at a glance

text
my-app/
├── index.html
├── package.json
├── vite.config.js
├── .gitignore
├── .oxlintrc.json
├── README.md
├── public/
│   ├── favicon.svg
│   └── icons.svg
└── src/
    ├── main.jsx
    ├── App.jsx
    ├── App.css
    ├── index.css
    └── assets/
        ├── hero.png
        ├── react.svg
        └── vite.svg

After npm install you also get node_modules/ (the downloaded packages) and a lock file, and after npm run build you get dist/ (the finished site). The last two are generated: never edit them by hand.

index.html: the only page

A React app of this kind is a single-page application: the browser loads one HTML file, and React draws everything else with JavaScript. Here it is, complete:

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-app</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

Two lines matter. <div id="root"></div> is an empty container where React will put the interface. <script type="module" src="/src/main.jsx"> loads your code as a JavaScript module. This is where you change the page title (<title>) and add things that must exist before React starts, such as fonts or analytics tags. Because the visible content is made by JavaScript, search engines and link previews see an empty page unless you use server rendering, a reason some teams pick a framework like Next.js for content sites.

main.jsx: the entry point

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>,
)

Read it from the bottom up. document.getElementById('root') finds the container from index.html. createRoot(...) tells React DOM (the part of React that talks to the browser) to take control of that element. .render(...) draws the given element tree inside it, here <App /> wrapped in <StrictMode>. The imports at the top bring in React's tools, the global stylesheet and your App component.

StrictMode does not show anything. In development it runs extra checks: it calls components twice and runs effects an extra time to reveal bugs such as impure rendering. Its checks disappear in the production build. The StrictMode and debugging lesson explains what to expect. The lesson editors in this course mount your App component the same way as this file does.

You rarely edit main.jsx. Later you will wrap <App /> in things like a router or a context provider here.

App.jsx: the root component

App is the top of your component tree, and where you replace the starter content with your own. The template's version is long because it contains the demo page; the essential structure is:

jsx
import { useState } from 'react'
import './App.css'

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

  return (
    <>
      <h1>Get started</h1>
      <button type="button" onClick={() => setCount((count) => count + 1)}>
        Count is {count}
      </button>
    </>
  )
}

export default App

(This is a trimmed copy of the template's component.) Notice import './App.css': Vite lets you import a CSS file straight into a component, and adds its rules to the page. The <>...</> is a fragment, which groups elements without adding an extra element to the page; the next lesson explains it.

The src/assets folder and the public folder

There are two places for images and other files, and the difference matters.

src/assets public
How you use a file import logo from './assets/react.svg' then <img src={logo} /> write the path directly: <img src="/favicon.svg" />
Processed by Vite yes: the file name gets a content hash, small files may be inlined no: copied exactly as it is
Missing file build error, so you find out early a broken link you may not notice
Good for images and fonts your components use robots.txt, favicons, files that need a fixed URL

Putting an image in src/assets is the default choice. In the production build above, react.svg became dist/assets/react-CHdo91hT.svg: the hash lets browsers cache it forever and still refetch when the file changes.

CSS files

src/index.css holds global styles such as fonts and colours, imported once in main.jsx. src/App.css holds styles for the demo page, imported by App.jsx. They are ordinary CSS. If you are new to CSS, the CSS course covers it, and the lesson on styling React apps shows the options. You can delete the template's CSS and write your own.

package.json

json
{
  "name": "my-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "oxlint",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^19.3.1",
    "react-dom": "^19.3.1"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^6.1.2",
    "oxlint": "^1.88.0",
    "vite": "^8.3.4"
  }
}
  • scripts are the commands you run with npm run.
  • dependencies are the packages your app needs when it runs: react (the core) and react-dom (the browser renderer). They are two packages because React can render to other places too, such as phones.
  • devDependencies are tools used only while building: Vite, the React plugin that compiles JSX, and the linter. (The template also lists the @types packages, used by editors for hints.)
  • "type": "module" makes .js files use import/export.
  • The ^ in ^19.3.1 means "this version or a newer compatible one". npm install records the exact versions in the lock file (package-lock.json), which you should commit so everyone gets the same versions.

Add a package with npm install name, and remove one with npm uninstall name; npm edits package.json for you.

vite.config.js and the other files

javascript
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'

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

The configuration is tiny: it enables the React plugin, which compiles JSX and adds hot reloading that keeps component state while you edit. You will touch this file when you need something like a proxy for API calls or a different base path for deployment.

.gitignore lists what Git should not track (node_modules, dist, logs, editor folders). .oxlintrc.json configures the linter, with two React rules switched on: one enforces the rules of hooks (an error) and one warns when a file exports things that break fast refresh. README.md is documentation for the repository, so replace it with a description of your project.

How a page gets built: the whole path

  1. The browser requests / and gets index.html with an empty #root.
  2. The <script type="module"> loads src/main.jsx.
  3. main.jsx imports React, index.css and the App component, and each of those imports its own files.
  4. createRoot(...).render(<App />) runs, React calls App(), which returns JSX, and React puts the matching elements into #root.
  5. When state changes (a button click), React calls the affected component functions again and updates only the parts of the page that changed.

Everything you learn in this course happens in step 4 and 5: components that return JSX, and state that makes them run again.

Suggested structure as the app grows

The template keeps everything in src. As you add components, a common layout is:

text
src/
├── components/     reusable pieces: Button.jsx, TaskItem.jsx
├── pages/          one component per screen: Home.jsx, About.jsx
├── hooks/          custom hooks: useLocalStorage.js
├── App.jsx
└── main.jsx

There is no required structure. Start flat, and create a folder when you have several files that belong together. Name component files after the component, with a capital letter (TaskItem.jsx), because the name of a component must start with a capital.

Common mistakes

  • Editing the files in dist or node_modules, which are regenerated.
  • Putting images that components use in public, then breaking the path when the site is deployed under a sub-folder.
  • Deleting the <div id="root"> from index.html, which makes React fail with "Target container is not a DOM element".
  • Renaming main.jsx without changing the script tag in index.html.
  • Naming a component file in lower case, or a component function in lower case; React treats lower-case tags as HTML elements.
  • Forgetting the .jsx extension for files that contain JSX; Vite expects it.

Exercise

  1. Open your project and find each file listed in this lesson. Delete everything inside App.jsx's return and replace it with an h1.
  2. Change the <title> in index.html and check the browser tab.
  3. Put an image in src/assets, import it into App.jsx and show it with <img>. Then look in dist after npm run build to see its new name.
  4. Add a robots.txt file to public and open /robots.txt on the dev server.

Key points

  • index.html has an empty div with id root and a script tag that loads src/main.jsx; everything else is built by React.
  • main.jsx calls createRoot on that div and renders the App component inside StrictMode.
  • Files in src are processed by Vite and can be imported; files in public are copied as they are and referred to by absolute path.
  • package.json lists the dependencies and the scripts; node_modules and dist are generated and not committed to Git.

Check yourself

1. Which file creates the React root and renders the App component?
2. Where would you put a file that must be served exactly as it is, such as robots.txt?
3. What does the empty div with id root in index.html do?
4. Which folder should you never commit to Git?