React Project Files and Package Scripts
A project directory is not one large program that Vite reads from top to bottom. It contains several kinds of files, read by different programs at different times. The browser reads index.html. JavaScript modules import other modules. npm reads package.json and the lockfile. Vite reads its configuration. ESLint reads a different configuration. Git reads .gitignore.
When those readers are mixed together, project setup feels arbitrary. Identifying the program that reads each file makes the directory easier to debug.
Begin with the Directory, Not an Individual File
A current Vite React project has a layout close to this one.
reactbook-first-screen/
public/
src/
.gitignore
eslint.config.js
index.html
package-lock.json
package.json
vite.config.jsThe root directory holds files that describe the project as a whole. src holds application source. public holds files served without passing through normal source imports.
That distinction is useful before you open anything. Place React components in src because they participate in the module graph. Place a fixed file such as robots.txt in public when it must retain its exact name.
The project root is the directory containing package.json. Commands such as npm run dev use the current directory to decide which project they should run.
Run the following command from the project root.
npm run devRun it from the parent directory and npm may report that it cannot find package.json. The command did not forget how to start Vite. It was asked to operate on a directory that does not describe an npm project.
Follow the Browser Entry
The browser begins with index.html.
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>The div is the React root container. The script points into the application source. Because its type is module, imports inside main.jsx participate in a JavaScript module graph.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
import "./index.css";These four imports do not all resolve in the same way.
reactandreact-dom/clientare package specifiers. Vite resolves them from installed dependencies../App.jsxis a relative source path frommain.jsx../index.cssis a stylesheet import handled by Vite.
The imports form connections. If App.jsx imports ChapterCard.jsx, that file joins the graph. A file sitting in src but never imported is not automatically included merely because it shares the directory.
A source file enters the client application through an import path from an entry module. Its location alone does not make it run.
This fact explains a quiet beginner bug. You create NewApp.jsx, edit it for ten minutes, and see no browser change. If main.jsx still imports ./App.jsx, the browser has no path to the new file.
Read package.json as a Project Record
Open package.json and resist the urge to treat it as installation noise. It records how the directory is meant to be used.
{
"name": "reactbook-first-screen",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
}
}name identifies the package to tools. private: true prevents an accidental npm publish of this application directory. It does not make source code private in a deployed website. Browser JavaScript still has to reach the browser.
type: "module" tells Node.js to treat .js files in this package as ES modules by default. That setting applies when Node loads configuration such as vite.config.js. Browser modules are already identified through the HTML module script and Vite's output.
The scripts object names commands. It does not install the programs mentioned in those commands.
Understand What an npm Script Does
This script is a short instruction.
"dev": "vite"When you run it, npm adds the project's local executable directory to the command search path and starts the local Vite binary.
npm run devYou do not need to install Vite globally. Another project can use another Vite version, and each project script finds the version recorded for that project.
The project defines the script name. npm reserves a few shortcuts, including npm start and npm test, but most names use npm run.
{
"scripts": {
"check": "npm run lint && npm run build"
}
}The && operator runs the second command only if the first exits successfully. This can form a local review gate. It is still ordinary shell behavior invoked through an npm script.
Run npm run without a script name to list the scripts available in the current project. This is safer than guessing command names from another repository.
Dependencies and Development Dependencies
The generated project separates packages into two groups.
{
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^6.0.0",
"vite": "^8.0.0"
}
}Application code imports React and React DOM, so they appear as runtime dependencies. Vite and its React plugin build and serve the application, so they appear as development dependencies.
This division does not decide whether code reaches the browser bundle. Imports and the build process decide that. Nor does devDependencies mean a package is low quality or used only on a developer's laptop. Production build systems commonly install development dependencies so they can compile the application, then deploy the produced files.
The separation describes the package's contract. If you publish a reusable library and its consumers need a package while your library runs, that classification affects their installation. For this browser application, it also makes the toolchain distinct from app runtime packages.
Read a Version Range Carefully
The caret in ^19.2.0 permits compatible updates within the same major version under normal semantic version rules.
"react": "^19.2.0"That range can accept a later React 19 minor or patch when npm resolves dependencies. It does not mean every installation chooses a new version without a record. The lockfile records the resolved package graph.
Check what is installed.
npm ls react react-dom viteCheck package versions from inside the project instead of assuming the generated template has the same versions months later.
Do not edit version numbers in package.json and assume installation changed. Run the package manager so it can resolve the graph, update the lockfile, and install the selected versions.
The Lockfile Records a Resolution
package.json states requested ranges and direct package intent. package-lock.json records the concrete dependency graph chosen by npm, including transitive packages and integrity data.
Commit the lockfile for an application. It lets local machines and automated builds start from the same resolution record.
npm cinpm ci installs from the lockfile and fails when it disagrees with package.json. It removes the existing node_modules directory before installing. This behavior suits automated checks that need a clean, repeatable installation.
npm installnpm install can resolve requested changes and update the lockfile. It suits normal dependency changes during development.
The lockfile does not promise that every operating system, Node.js version, or native package behaves identically. It does remove one large source of variation by fixing package resolutions.
node_modules Is Installed Output
node_modules contains the packages selected by npm. It may be large because one direct dependency can bring many transitive dependencies.
Do not copy it between unrelated systems as a deployment strategy. Do not edit a package file there as the permanent fix. A clean install will replace the edit, and teammates will not receive it.
When dependency installation appears corrupted, first preserve the files that describe intent.
package.json
package-lock.jsonThen a clean installation can recreate node_modules.
Deleting the lockfile changes more than clearing downloaded files. It asks npm to resolve allowed dependency versions again. Do not use that as the first fix for an unexplained error.
Vite Configuration Has a Narrow Job
A React template includes configuration close to this.
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
});Vite loads this file in its own Node.js process. The React plugin integrates JSX transforms and React Fast Refresh with Vite's development server.
defineConfig mainly supplies editor types and a clear configuration form. It does not start the server by itself. The vite command finds and loads the configuration.
Keep configuration small until the project has a requirement. Each alias, transform, and plugin becomes another possible source of errors.
ESLint Reads Source Without Running the App
The generated eslint.config.js describes static checks. ESLint parses source and applies rules. It can report an unused binding or an invalid Hook call without opening the browser.
npm run lintA successful lint run means the configured rules found no violations. It does not prove the UI is correct, accessible, secure, or free of runtime errors. Each tool answers a limited question.
The Vite development server proves that modules can be served and transformed. ESLint applies the configured static rules. A production build proves that deployable output can be produced. A browser check verifies the resulting behavior.
public and Imported Assets
A file in public is served from the site root using its fixed name.
<img src="/react-mark.svg" alt="" />An asset imported from src joins Vite's module graph.
import reactMark from "./assets/react.svg";
<img src={reactMark} alt="" />Vite can process, fingerprint, and rewrite the imported asset URL for a production build. A public file keeps its name and is copied as-is.
Use public when a file needs a fixed external path or cannot be imported. Prefer imports for assets used by source components because missing imports fail early and production filenames can be managed by the build.
Check Which Program Reads the File
When you are unsure which file to change, identify the program that reads it.
| File or directory | Primary reader | Job |
|---|---|---|
index.html | Browser and Vite | Initial document and module entry |
src/main.jsx | Vite and browser module runtime | Client React entry |
src/App.jsx | React through the module graph | Application component |
package.json | npm and related tools | Package intent and scripts |
package-lock.json | npm | Resolved dependency graph |
vite.config.js | Vite in Node.js | Development and build configuration |
eslint.config.js | ESLint | Static analysis configuration |
public | Vite copy and file serving | Fixed-path static files |
Use the table while debugging. If the browser cannot find a source import, inspect the module path. If npm cannot run a script, inspect package.json and the current directory. If JSX does not refresh properly, inspect the Vite React plugin.
Project Checkpoint
Start at index.html and follow the imports to the component tree. Then identify the separate roles of package.json, the lockfile, and node_modules; they are three different records of dependency state.
Source Notes
- npm documentation,
package.json - npm documentation,
npm ci - Vite documentation, Static Asset Handling
- Vite documentation, Using Plugins