Back to CourseBeginner3 min read
React /

Project Structure and Files

Walk through every file a new React project creates, and learn how to organise components as the project grows.

Bishal Biswas

Bishal Biswas

Self Learner

Published on: May 2, 2026
Last updated: September 5, 2026
Beginner Topic

What Vite generates

my-app/
  node_modules/
  public/
    vite.svg
  src/
    assets/
      react.svg
    App.css
    App.jsx
    index.css
    main.jsx
  .gitignore
  index.html
  package.json
  vite.config.js

Fewer files than you might expect. Let us go through the ones that matter.

index.html

The only real HTML page, and it sits at the project root rather than inside public/.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <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 carry the whole app:

  • <div id="root"> is the empty box React fills in.
  • The <script type="module"> loads your code.

Edit the <title> here, and add fonts or meta tags here too.

src/main.jsx

The entry point.

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

This file connects React to #root and renders your top component. You will rarely change it, except to wrap the app in providers for routing or state.

src/App.jsx

Your top level component, and where you actually start working.

src/index.css and src/App.css

Plain CSS, imported from JavaScript. Anything you import here is global.

import './index.css';

public/

Files here are copied to the output untouched, and referenced from the root.

<img src="/logo.png" alt="Logo" />

Use it for favicon.ico, robots.txt, and anything that must keep its exact filename.

Do not put images here if you can import them instead. Imported assets get hashed filenames and can be cached forever:

import logo from './assets/logo.png';
<img src={logo} alt="Logo" />

.jsx or .js?

Both work in Vite, but .jsx signals that a file contains JSX. Most teams use .jsx for components and .js for plain helpers. With TypeScript it is .tsx and .ts.

Organising as you grow

Small projects: group by type

src/
  components/
    Button.jsx
    Card.jsx
    Header.jsx
  pages/
    Home.jsx
    About.jsx
  hooks/
    useLocalStorage.js
  utils/
    formatDate.js
  App.jsx
  main.jsx

Simple and obvious. It works well up to twenty or thirty components.

Larger projects: group by feature

src/
  features/
    auth/
      LoginForm.jsx
      useAuth.js
      authApi.js
    cart/
      CartItem.jsx
      CartSummary.jsx
      useCart.js
  components/
    Button.jsx
    Modal.jsx
  App.jsx

Everything for one feature lives together. Deleting a feature means deleting one folder, and working on it means opening one folder instead of hopping between four.

components/ keeps only the genuinely shared pieces.

Do not start here. Begin with the simple layout and reorganise when the pain is real. Premature structure is as unhelpful as no structure.

Naming

components/Button.jsx
components/UserProfileCard.jsx
hooks/useLocalStorage.js
utils/formatDate.js

The conventions that almost everyone follows:

  • Components are PascalCase, matching the component name inside.
  • Hooks start with use, which is a real rule, not just style.
  • Utilities are camelCase.
  • One component per file, named after the file.

Component folders

Once a component has its own styles and tests:

components/
  Button/
    Button.jsx
    Button.module.css
    Button.test.jsx
    index.js

The index.js re-exports so imports stay short:

export { default } from './Button.jsx';
import Button from './components/Button';

Absolute imports

import Button from '../../../components/Button.jsx';
import Button from '@/components/Button.jsx';

Set the alias once in vite.config.js and never count ../ again.

What not to commit

node_modules/
dist/
.env.local

node_modules is huge and rebuildable. dist is generated. .env.local holds secrets.

Environment variables

VITE_API_URL=https://api.example.com
console.log(import.meta.env.VITE_API_URL);

Only variables starting with VITE_ are exposed, which is a safety feature.

Everything here ends up in the browser. Anyone can read it in DevTools. Never put an API secret or private key in a VITE_ variable. Those belong on a server.

Quick recap

  • index.html holds #root and loads main.jsx.
  • main.jsx connects React to the page, App.jsx is where you work.
  • public/ files keep their names, imported assets get hashed for caching.
  • Start by grouping by type, move to grouping by feature when it hurts.
  • Components are PascalCase, hooks must start with use.
  • Set up an @ alias instead of long relative paths.
  • VITE_ variables are public, so never put secrets in them.
script.js

// ---------- index.html: the only real HTML page ----------
// <div id="root"></div>
// <script type="module" src="/src/main.jsx"></script>
// ---------- src/main.jsx: connects React to the page ----------
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
import './index.css';                        // global styles
createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>
);
// ---------- Assets: import them so they get hashed and cached ----------
import logo from './assets/logo.png';         // becomes logo.a3f9c2.png
// <img src={logo} alt="Logo" />
// Files in public/ keep their exact name and are referenced from the root:
// <img src="/favicon.ico" alt="" />
// ---------- A component folder, once it has styles and tests ----------
// components/Button/Button.jsx
// components/Button/Button.module.css
// components/Button/index.js   ->  export { default } from './Button.jsx';
// which keeps the import short:
// import Button from '@/components/Button';
// ---------- Environment variables ----------
// .env file:  VITE_API_URL=https://api.example.com
const apiUrl = import.meta.env.VITE_API_URL;
console.log('API base:', apiUrl);
console.log('Running in dev?', import.meta.env.DEV);
// WARNING: every VITE_ variable is bundled into the browser code and is
// readable by anyone in DevTools. Secrets belong on a server, never here.
// ---------- Grow the structure only when it hurts ----------
// Start:   src/components, src/pages, src/hooks, src/utils
// Later:   src/features/auth, src/features/cart, plus shared src/components

Was this explanation helpful?

Your feedback helps us improve the content for everyone.