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
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 deploymentWhat 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"
}
}| Section | What's inside |
|---|---|
scripts | the project's commands — read these first |
dependencies | what the application needs to run |
devDependencies | needed 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
Show the contents of scripts from package.json and explain
what each command does. One line per command.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?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 inpackage-lock.json.tsc --noEmitinsidebuild— type checking is built into the build.Secrets on the frontend are impossible: everything in the build is visible to the user.