Programming with AI
ENRU
10.1

The anatomy of a React project

where everything comes from and why there are so many files

What it is

A React project looks frightening: two dozen files at the root, a node_modules folder running to hundreds of megabytes, config files for tools you have never heard of. And your own code is one folder.

This article sorts that out, so that in an unfamiliar project you don't spend half an hour looking for the entry point.

What it looks like in code

measured
weather-app/
├── node_modules/        libraries — don't read, don't commit
├── public/              files served as they are
├── src/                 ← your code, and only it
│   ├── main.tsx         entry point: mounts the app
│   ├── App.tsx          root component
│   ├── components/      components
│   │   └── CityCard.tsx
│   ├── hooks/           your own hooks
│   ├── api/             requests to the server
│   └── types.ts         shared types
├── index.html           the page everything is inserted into
├── package.json         dependencies and commands
├── package-lock.json    exact versions — commit this
├── tsconfig.json        TypeScript settings (article 9.3)
├── vite.config.ts       bundler settings
└── .env                 secrets — into .gitignore (article 5.7)

Only src/ needs reading. Everything else is scaffolding.

The entry point

import { createRoot } from "react-dom/client";
import App from "./App";

createRoot(document.getElementById("root")!).render(<App />);

Three lines, and they hold the whole link between HTML and React: take the #root element from index.html and draw the application inside it. From there on React draws the entire interface, and index.html never changes again.

The exclamation mark after getElementById is an as-like assertion that "the element is definitely there" (article 9.5). Here it is justified: if there is no #root, the application won't start anyway.

What a bundler is

The browser understands neither JSX, nor TypeScript, nor imports from node_modules. The bundler (most often Vite) turns all of that into ordinary JavaScript.

Hence the two commands you meet in every project:

npm run dev     # development mode: an edit is visible at once
npm run build   # a build into dist/ for deployment

What exactly they do is written in package.json, in the scripts section. That is the first place worth looking in an unfamiliar project: it lists everything that can be done with it.

package.json

{
  "scripts": {
    "dev": "vite",
    "build": "tsc --noEmit && vite build",
    "lint": "eslint ."
  },
  "dependencies": {
    "react": "^19.3.1",
    "react-dom": "^19.3.1"
  },
  "devDependencies": {
    "typescript": "^5.9.0",
    "vite": "^7.1.0"
  }
}
SectionWhat's inside
scriptsthe project's commands — read these first
dependencieswhat the application needs to run
devDependenciesneeded only during development

About ^: the sign means "this version or newer, within the same major". Which is why you and the server may end up with different versions — and that is exactly what package-lock.json protects against, by pinning the exact ones. It is always committed (article 5.6).

What to tell the AI

Prompt
Show the contents of scripts from package.json and explain
what each command does. One line per command.
Prompt
I've opened this project for the first time. Which three files
in src should I start reading to understand how it's put
together?
Prompt
Add a CityCard component in src/components/.
Don't change the project structure, don't add new
dependencies.

What to look for

node_modules in git. Hundreds of megabytes of other people's code in the repository. Checked by the presence of a line in .gitignore.

No package-lock.json. The versions aren't pinned, and you and the server will end up with different ones.

Everything in one App.tsx. A thousand-line file instead of components. It will work; changing it is hard, and an AI won't be able to edit one part without touching the rest.

Secrets in frontend code. An important difference from the backend: everything that made it into the build is visible to the user. A VITE_API_KEY variable ends up in the page's code. Real keys are kept on the server, and the frontend talks to its own backend (article 7.8).

A mix of .js and .tsx. A half-finished move to TypeScript: there are no checks in the .js files.

Cheat sheet

  • Read only src/; the rest is scaffolding.

  • The entry point is main.tsx; it mounts the app into #root.

  • package.json → scripts: the list of everything that can be done with the project.

  • ^ in versions permits updates; the exact versions live in package-lock.json.

  • tsc --noEmit inside build — type checking is built into the build.

  • Secrets on the frontend are impossible: everything in the build is visible to the user.