Layer 1 · Node

In 2009 someone took the browser's engine out of the browser.

Chrome's JavaScript engine is called V8. Ryan Dahl lifted it out, dropped the page, the DOM and the security sandbox, and bolted on the things a browser deliberately withholds: files, network ports, processes. He called it Node.

Same language. Different room. Click each line to run it, then flip the switch and run it in the other room.

same code, two rooms
Other people's folders

npm is a website full of folders, and a tool that downloads them into yours.

A package is a folder with a package.json in it. Publishing means uploading the folder. Installing means downloading it into node_modules/, where import knows to look.

That's genuinely all it is. Install one.

your project folder
my-app/
├── package.json
├── src/
│ └── page.tsx
└── node_modules/ (empty)
But

Packages have packages.

Install next and you get next. But next depends on styled-jsx, postcss, @swc/helpers, sharp… and each of those depends on more. npm walks the whole graph and downloads all of it. (react isn't on that list: next expects you to install it yourself, which is why the command has three names.)

This is why node_modules is the heaviest object in the universe, and why it's in .gitignore: you never commit it, you regenerate it from the list.

0
folders in node_modules
node_modules/
node_modules/ after `npm install next react react-dom`:
next/
react/
react-dom/
… and everything they need. Press install to unfold it.
The ID card

package.json is the app's ID card. And npm run dev just reads it.

Click any highlighted key on the right. In particular, look at scripts: npm run dev is not magic. It looks up the string under "dev" and runs it in a shell.

Click a key.
package.json
{ "name": "my-app", "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "eslint ." }, "dependencies": { "next": "^16.1.0", "react": "^19.0.0", "react-dom": "^19.0.0", "@prisma/client": "^6.5.0" }, "devDependencies": { "typescript": "^5.7.0", "@types/react": "^19.0.0", "prisma": "^6.5.0", "tailwindcss": "^4.0.0" } }
Place your bet

What does npm run build actually execute?

The string. next build, in a shell, with node_modules/.bin on the path.

That's why you can rename scripts freely, why npm run with no name lists them, and why a broken build is almost never npm's fault: it's whatever command is under that key.

pick one
The same tree everywhere

^19.0.0 means "19-point-anything". The lockfile pins the exact one.

package.json says what you're okay with. package-lock.json (or pnpm-lock.yaml, bun.lock) records what you actually got, down to the byte hash, for every package in the graph.

Commit the lockfile. Run npm ci on the server. Every machine gets the identical thousand folders. Delete it and two laptops can quietly diverge.

Try it: publish a new zod, then install on the server, with and without the lockfile.

two machines

your laptop

react 19.2.0
next 16.1.0
zod 3.24.1

the server

react 19.2.0
next 16.1.0
zod 3.24.1

With the lockfile: identical.

Two ways up

Node isn't only the tool runner. It's the server.

When you deploy a Next.js app, what runs on Vercel or your VPS is a Node process listening on a port. Your server components, server actions and API routes all execute inside it.

But there's a snag. Your files end in .tsx. Node can't read those. Neither can the browser.