Volume I index
Project Tooling and the First Milestone

React Project Files and Package Scripts

Ishtmeet Singh @ishtms/July 14, 2026/10 min read
#react#vite#npm#package-json#project-structure

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.

text
reactbook-first-screen/
  public/
  src/
  .gitignore
  eslint.config.js
  index.html
  package-lock.json
  package.json
  vite.config.js

The 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.

Run the following command from the project root.

bash
npm run dev

Run 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.

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.

jsx
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.

  • react and react-dom/client are package specifiers. Vite resolves them from installed dependencies.
  • ./App.jsx is a relative source path from main.jsx.
  • ./index.css is 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.

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.

json
{
  "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.

json
"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.

bash
npm run dev

You 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.

json
{
  "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.

Dependencies and Development Dependencies

The generated project separates packages into two groups.

json
{
  "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.

json
"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.

bash
npm ls react react-dom vite

Check package versions from inside the project instead of assuming the generated template has the same versions months later.

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.

bash
npm ci

npm 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.

bash
npm install

npm 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.

text
package.json
package-lock.json

Then a clean installation can recreate node_modules.

Vite Configuration Has a Narrow Job

A React template includes configuration close to this.

js
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.

bash
npm run lint

A 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.

jsx
<img src="/react-mark.svg" alt="" />

An asset imported from src joins Vite's module graph.

jsx
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 directoryPrimary readerJob
index.htmlBrowser and ViteInitial document and module entry
src/main.jsxVite and browser module runtimeClient React entry
src/App.jsxReact through the module graphApplication component
package.jsonnpm and related toolsPackage intent and scripts
package-lock.jsonnpmResolved dependency graph
vite.config.jsVite in Node.jsDevelopment and build configuration
eslint.config.jsESLintStatic analysis configuration
publicVite copy and file servingFixed-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