C

Project Structure

In this lesson we will walk through the structure of the react-project scaffolded by Vite in the previous lessons. Understanding what each file is responsible for will make it easier to navigate every lesson project in this course.

1.Directory layout

After creating the project, react-project/ contains the following files and folders:

react-project/
├── public/
│ ├── favicon.svg
│ └── icons.svg
├── src/
│ ├── assets/
│ │ ├── hero.png
│ │ ├── react.svg
│ │ └── vite.svg
│ ├── App.css
│ ├── App.tsx
│ ├── index.css
│ └── main.tsx
├── .gitignore
├── README.md
├── eslint.config.js
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.app.json
├── tsconfig.node.json
└── vite.config.ts

We can group these into four categories: entry points, static assets, build and type configuration, and linting.

2.Entry points

These three files are the runtime entry chain of the application. Vite starts from index.html in the browser, which loads main.tsx, which in turn renders the root component App.tsx.

`index.html`

The single HTML page served to the browser. Unlike traditional bundlers, Vite treats index.html as the true entry point of the application — it is processed by Vite’s dev server and build pipeline.

The important line is <script type="module" src="/src/main.tsx">, which tells Vite to load the TypeScript entry module as a native ES module.

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

`src/main.tsx`

The JavaScript/TypeScript entry point. It imports the global stylesheet, mounts the React application into the #root DOM node declared in index.html, and wraps it in <StrictMode> to catch common mistakes during development.

src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)

`src/App.tsx`

The root React component rendered by main.tsx. This is the file you edit first to start building your UI — Vite’s Hot Module Replacement will reflect changes in the browser instantly without a full page reload.

3.Static assets

React supports two different ways of referencing static files, each with its own location.

`public/` — served as-is

Files placed in public/ are copied to the root of the build output without processing and are referenced by an absolute URL starting with /.

Use this folder for files that must keep a stable, predictable URL — for example favicon.svg (referenced from index.html via /favicon.svg) or icons.svg (referenced from components via <use href="/icons.svg#...">).

Because files here are not processed by the bundler, they are not hashed and can be cached aggressively by a CDN.

`src/assets/` — imported as modules

Files placed under src/assets/ are imported from code like any other module:

import reactLogo from './assets/react.svg'

Vite rewrites the import into a URL and, for production, emits the asset with a content-based hash in its filename. This gives you automatic cache-busting and lets the bundler eliminate unused assets via tree-shaking.

Rule of thumb: if an asset is referenced from a component, put it in src/assets/; if it must be reachable by a fixed URL, put it in public/.

4.Build and type configuration

These files configure Vite and the TypeScript compiler.

`package.json`

Declares the project name, scripts, and dependencies. In a workspace setup (see the previous lesson), dependency versions are set to "*" and resolved from the workspace root.

The relevant scripts are:

  • bun run dev — start the Vite dev server with HMR
  • bun run build — type-check with tsc -b and produce a production build
  • bun run preview — serve the production build locally
  • bun run lint — run ESLint across the project

`vite.config.ts`

The Vite configuration file. By default it only registers the @vitejs/plugin-react plugin, which enables JSX compilation and Fast Refresh during development.

This is where you would later add path aliases, proxy rules, or additional plugins.

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})

`tsconfig.json` — the project root config

A small file that uses TypeScript project references to split the type-checking into two independent sub-projects: one for application code, one for build-time Node.js code. It contains no compiler options itself.

tsconfig.json
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

`tsconfig.app.json` — application code

The config used when type-checking everything under src/. It targets modern browsers (ES2023 + DOM types), enables strict checks, and is tuned for a bundler ("moduleResolution": "bundler", "noEmit": true) — TypeScript only type-checks, Vite/Rollup handles the actual code generation.

tsconfig.app.json
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2023",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

`tsconfig.node.json` — build-time code

A separate config used to type-check files that run in Node.js during the build — primarily vite.config.ts. It uses Node’s @types/node typings instead of DOM types, so browser globals are not accidentally available in build configuration.

tsconfig.node.json
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

5.Linting

`eslint.config.js`

ESLint’s modern flat configuration file. The template composes four rule sets:

  • @eslint/js — baseline JavaScript rules
  • typescript-eslint — TypeScript-aware rules
  • eslint-plugin-react-hooks — enforces the Rules of Hooks
  • eslint-plugin-react-refresh — warns about patterns that break Fast Refresh in Vite

The dist/ folder is globally ignored so build artefacts are not linted.

eslint.config.js
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])

6.Miscellaneous

  • .gitignore — standard ignore list for a Node/Vite project (node_modules, dist, local env files, editor folders).
  • README.md — template documentation generated by Vite with hints on enabling type-aware lint rules and switching between React plugins.