🚀 Chapter 1: Introduction & Setup
Understanding what Next.js actually is, why the App Router replaced everything you may have read online, and getting a Next.js 16 project running from zero.
📖 Plain English Explanation
React gives you a way to build user interfaces. That's all it gives you. It does not tell you how to route between pages, how to fetch data on a server, how to bundle your code, how to optimize images, or how to deploy.
Next.js is React plus all the answers to those questions. It is a framework built on top of React that makes the boring-but-essential decisions for you.
Think of it like this:
- React = an engine. Powerful, but you can't drive an engine.
- Next.js = the whole car. Engine, chassis, steering, brakes, dashboard.
You can build the rest of the car yourself (Vite + React Router + TanStack Query + your own server). Many teams do. Next.js is for when you'd rather write features than assemble a car.
The one thing you must understand before anything else
Next.js has two routers, and they are completely different frameworks wearing the same name:
| Pages Router | App Router | |
|---|---|---|
| Folder | pages/ | app/ |
| Introduced | 2016 | Next.js 13 (2022), stable in 14 |
| Data fetching | getServerSideProps, getStaticProps | async components, fetch |
| Components | All client components | Server Components by default |
| Status in 2026 | Maintenance mode | The only one being developed |
This book covers the App Router exclusively. Not because Pages Router is bad, but because it is finished — it receives security fixes and nothing else. Every new Next.js feature since 2023 (Server Components, Server Actions, streaming, Partial Prerendering, Cache Components) exists only in the App Router.
If you find a tutorial that says getServerSideProps, _app.js, or pages/api/hello.js — that tutorial is teaching the old framework. Close it.
🕰️ Which version is this book?
Next.js 16.3 (released 3 August 2026). This matters more than it usually does.
Next.js 16 (October 2025) was a large breaking release. A very large fraction of the Next.js content on the internet — blog posts, YouTube courses, Stack Overflow answers, and the training data inside AI assistants — describes Next.js 14 or 15. Following it will produce code that does not compile.
Here is the short list of what changed, so you can immediately recognize outdated material:
| Old (Next 14/15) | New (Next 16) |
|---|---|
middleware.ts | proxy.ts |
const { slug } = params | const { slug } = await params |
cookies() returns store | await cookies() |
experimental: { ppr: true } | cacheComponents: true |
revalidateTag('posts') | revalidateTag('posts', 'max') |
unstable_cacheLife | cacheLife |
next dev --turbopack | next dev (Turbopack is the default) |
next lint | ESLint / Biome directly |
serverRuntimeConfig | environment variables |
import Image from 'next/legacy/image' | import Image from 'next/image' |
Every chapter in this book flags these with a ⚠️ Changed in Next.js 16 callout so you can spot them at a glance.
🧱 What Next.js gives you
1. File-system routing
You create a file, you get a URL. No route configuration object anywhere.
app/page.tsx → /
app/about/page.tsx → /about
app/blog/[slug]/page.tsx → /blog/hello-world
2. Server Components by default
In a plain React app, every component ships to the browser as JavaScript. In the App Router, components run on the server unless you opt them into the browser. This means you can talk to a database directly inside a component:
// app/page.tsx — this code never reaches the browser
import { db } from '@/lib/db'
export default async function Page() {
const users = await db.user.findMany()
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>
}
// app/page.js — this code never reaches the browser
import { db } from '@/lib/db'
export default async function Page() {
const users = await db.user.findMany()
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>
}
Notice: an async component. That is not possible in client-side React. Chapter 10 covers why.
3. A build system you don't configure
Bundling, code splitting, minification, TypeScript compilation, CSS handling, and tree shaking are all set up. Since Next.js 16, the bundler is Turbopack (written in Rust), and it is on by default.
4. Built-in optimizations
next/image— automatic resizing, modern formats, lazy loadingnext/font— self-hosted fonts with zero layout shiftnext/link— prefetching and instant client-side navigation- Automatic code splitting per route
5. A server, when you want one
Route Handlers give you API endpoints. Server Actions let you mutate data from a form without writing an API at all.
🛠️ Requirements
Next.js 16 raised its floor. Check these before you start:
node --version # must be 20.9.0 or newer (Node 18 is no longer supported)
| Requirement | Minimum |
|---|---|
| Node.js | 20.9.0 (LTS) |
| TypeScript | 5.1.0 |
| React | 19.2 |
| Chrome / Edge / Firefox | 111+ |
| Safari | 16.4+ |
If you're on Node 18, upgrade first — Next.js 16 will refuse to run.
# with nvm
nvm install 22
nvm use 22
📦 Creating a project
The official scaffolder is create-next-app. Run it with your package manager of choice:
# npm
npx create-next-app@latest my-app
# pnpm
pnpm create next-app my-app
# yarn
yarn create next-app my-app
# bun
bun create next-app my-app
You'll be asked a series of questions:
✔ Would you like to use TypeScript? › Yes
✔ Which linter would you like to use? › ESLint
✔ Would you like to use Tailwind CSS? › Yes
✔ Would you like your code inside a `src/` directory? › No
✔ Would you like to use App Router? › Yes ← say Yes
✔ Would you like to use Turbopack? › Yes
✔ Would you like to customize the import alias? › No
The only answer that really matters is App Router: Yes. Everything else is preference.
To skip the prompts entirely:
npx create-next-app@latest my-app --typescript --tailwind --app --no-src-dir --import-alias "@/*"
Then:
cd my-app
npm run dev
Open http://localhost:3000. You have a running Next.js app.
📁 What you just got
my-app/
├── app/
│ ├── favicon.ico
│ ├── globals.css # global stylesheet
│ ├── layout.tsx # root layout — wraps every page
│ └── page.tsx # the / route
├── public/ # static files served at /
├── next.config.ts # framework configuration
├── package.json
├── tsconfig.json
└── eslint.config.mjs
Two files do all the work right now.
app/layout.tsx — the root layout. It renders on every single page and must contain <html> and <body>:
// app/layout.tsx
import type { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
title: 'My App',
description: 'Built with Next.js',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
// app/layout.js
import './globals.css'
export const metadata = {
title: 'My App',
description: 'Built with Next.js',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
app/page.tsx — the UI for /:
// app/page.tsx
export default function Page() {
return <h1>Hello, Next.js 16</h1>
}
// app/page.js
export default function Page() {
return <h1>Hello, Next.js 16</h1>
}
Both files are identical in TypeScript and JavaScript apart from the type annotations.
⚡ Turbopack is now the default
⚠️ Changed in Next.js 16
Turbopack went stable and became the default bundler for both
next devandnext build. The--turbopackflag is no longer needed.
// package.json — Next.js 15 (old)
{
"scripts": {
"dev": "next dev --turbopack",
"build": "next build --turbopack",
"start": "next start"
}
}
// package.json — Next.js 16 (current)
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
Turbopack also caches compiler output to disk between runs (filesystem caching), so the second next dev of the day starts far faster than the first.
If you have a custom webpack config, next build will now fail on purpose, to stop you silently ignoring it. Three ways out:
next build --turbopack # use Turbopack, ignore the webpack config
next build --webpack # opt out of Turbopack entirely
...or migrate the config to the top-level turbopack option (Chapter 2).
🧭 The four commands
next dev # development server with hot reload
next build # production build
next start # serve the production build (run build first)
next typegen # generate route types (PageProps, LayoutProps, RouteContext)
⚠️ Changed in Next.js 16
next lintwas removed.next buildno longer runs linting either. Run ESLint or Biome directly:json{ "scripts": { "lint": "eslint ." } }A codemod handles the migration:
npx @next/codemod@canary next-lint-to-eslint-cli .
⚠️ Changed in Next.js 16
next devandnext buildnow write to separate output directories (next devuses.next/dev), so you can run both at once. A lockfile also prevents twonext devprocesses fighting over the same project.
🤖 A note on AI assistants
Next.js 16 ships a genuinely useful feature for this exact problem: it can generate an AGENTS.md file that points your AI coding assistant at the version-matched docs bundled inside node_modules/next/dist/docs/.
npx @next/codemod@canary agents-md
The generated block says, bluntly:
This is NOT the Next.js you know. This version has breaking changes — APIs, conventions, and file structure may all differ from your training data.
If you use Copilot, Cursor, or Claude Code on a Next.js project, run this. It is the difference between an assistant that writes middleware.ts and one that writes proxy.ts.
⚠️ Common Pitfalls
1. Following a Pages Router tutorial by accident
❌ pages/index.js ← Pages Router
❌ getServerSideProps() ← Pages Router
❌ pages/api/users.js ← Pages Router
❌ _app.js / _document.js ← Pages Router
✅ app/page.tsx ← App Router
✅ async function Page() ← App Router
✅ app/api/users/route.ts ← App Router
✅ app/layout.tsx ← App Router
Fix: if the tutorial mentions a pages/ folder, it is teaching a different framework.
2. Expecting useState to work in a page by default
// ❌ app/counter/page.tsx — this crashes
export default function Page() {
const [count, setCount] = useState(0) // Error: useState only works in Client Components
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
// ✅ app/counter/page.tsx
'use client'
import { useState } from 'react'
export default function Page() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
Fix: hooks and event handlers need 'use client' at the top of the file. Chapter 10 explains the boundary in depth.
3. Running on Node 18
error - Next.js requires Node.js 20.9.0 or later
Fix: nvm install 22 && nvm use 22.
4. A stale --turbopack flag with a webpack config
If a plugin injects a webpack option into your config, next build fails with "a webpack configuration was found" even though you never wrote one.
Fix: find the plugin, or run next build --webpack while you migrate.
5. Forgetting <html> and <body> in the root layout
// ❌ app/layout.tsx
export default function RootLayout({ children }) {
return <div>{children}</div> // Error: missing <html> and <body>
}
The root layout is the only place these tags exist. Next.js will not add them for you.
🎯 When & Why to Use Next.js
Reach for Next.js when you need:
✅ SEO — content rendered on the server, indexable by crawlers
✅ Fast first paint — HTML arrives ready, not assembled by JS
✅ A backend and frontend in one codebase
✅ Content that changes (blogs, docs, e-commerce, dashboards)
✅ A team that would rather ship features than configure tooling
Reach for something else when:
❌ You're building a pure SPA behind a login with no SEO needs
→ Vite + React Router is lighter and simpler
❌ You're building a static marketing site with no interactivity
→ Astro ships less JavaScript
❌ You need a mobile app
→ React Native / Expo
❌ Your team is deeply invested in a different backend
→ Next.js can still be the frontend, but you lose half the value
Being honest about this matters. Next.js is not automatically the right answer, and "we used Next.js because it's popular" is how teams end up fighting a framework instead of using it.
🏋️ Mini Practice Problems
Problem 1: Spot the router
For each snippet, say whether it's Pages Router or App Router:
// A
export async function getStaticProps() {
return { props: { posts: [] } }
}
// B
export default async function Page() {
const posts = await getPosts()
return <PostList posts={posts} />
}
// C
export default function handler(req, res) {
res.status(200).json({ name: 'John' })
}
// D
export async function GET() {
return Response.json({ name: 'John' })
}
Problem 2: Fix the version
This code was written for Next.js 15. Update it for Next.js 16:
// app/blog/[slug]/page.tsx
export default function Page({ params }: { params: { slug: string } }) {
return <h1>{params.slug}</h1>
}
Problem 3: Build the smallest app
From an empty folder, create the minimum set of files needed for http://localhost:3000/hello to render the text "Hello". How many files is it? (Answer: three — package.json, app/layout.tsx, app/hello/page.tsx.)
Problem 4: Read the error
You run next build and get:
Error: Turbopack build failed because a webpack configuration was found
You have never written a webpack config. List two possible causes and two fixes.
💼 Interview Notes
Common Questions
Q: What problem does Next.js solve that React doesn't? React is a view library — it renders components. Next.js adds routing, server rendering, data fetching, bundling, image/font optimization, and a deployment story. React tells you how to draw; Next.js tells you how to ship.
Q: What is the difference between the Pages Router and the App Router?
The Pages Router renders everything as Client Components and fetches data through special exported functions (getServerSideProps, getStaticProps). The App Router is built on React Server Components: components run on the server by default, data is fetched with plain async/await inside components, and UI can stream in pieces. The Pages Router is in maintenance mode.
Q: What are the rendering strategies in Next.js?
- Static (SSG) — HTML generated at build time
- Dynamic (SSR) — HTML generated per request
- ISR — static HTML regenerated on a schedule or on demand
- Partial Prerendering (PPR) — a static shell with dynamic holes streamed in, now delivered through Cache Components
- Client-side — rendered in the browser after hydration
Q: Why did Next.js switch to Turbopack? Webpack is written in JavaScript and re-does work on every change. Turbopack is written in Rust, does incremental compilation, and caches to disk. Result: much faster cold starts and hot updates, especially on large apps.
Q: Is Next.js only for Vercel?
No. output: 'standalone' produces a self-contained Node server you can run in Docker anywhere. Some features (ISR at the edge, image optimization at scale) are easier on Vercel, but nothing is locked in. Chapter 24 covers self-hosting.
🏢 Asked at Companies
- Vercel: "Walk me through what happens between a user clicking a
<Link>and the new page appearing." - Netflix: "When would you choose the App Router over a plain SPA, and when would you not?"
- Shopify: "How do you decide between static generation and server rendering for a product page?"
- Stripe: "What is the actual cost of adding
'use client'to a component?"
📊 Visual Memory Aid
THE NEXT.JS STACK
┌─────────────────────────────────────────────┐
│ Your app │
│ app/page.tsx, app/layout.tsx, components/ │
├─────────────────────────────────────────────┤
│ Next.js 16 │
│ ├─ Routing (file system) │
│ ├─ Rendering (RSC, streaming, PPR) │
│ ├─ Data (fetch, cache, actions) │
│ ├─ Optimization (image, font, script) │
│ └─ Bundling (Turbopack) │
├─────────────────────────────────────────────┤
│ React 19.2 │
│ Server Components, Suspense, Transitions │
├─────────────────────────────────────────────┤
│ Node.js 20.9+ │
└─────────────────────────────────────────────┘
TWO ROUTERS, ONE NAME
pages/ ──────────► maintenance mode, do not learn
app/ ──────────► everything in this book
🎯 Key Takeaways
- Next.js is React plus routing, rendering, data, and build tooling — it makes the decisions React deliberately leaves open.
- Only the App Router matters. The Pages Router still runs, but receives no new features. If a tutorial has a
pages/folder, skip it. - Next.js 16 broke a lot of what's written online — async
params,proxy.ts,cacheComponents,revalidateTag(tag, profile). Watch for the ⚠️ callouts. - Turbopack is the default bundler now. Drop
--turbopackfrom your scripts; keep a webpack config only if you deliberately opt out with--webpack. - Node 20.9+ and TypeScript 5.1+ are hard requirements. Check before you scaffold, not after.
Next Chapter: Project Structure & Configuration →
Practice: Scaffold a fresh app with create-next-app, delete everything inside app/page.tsx, and rebuild it from memory. Then add app/about/page.tsx and navigate between them.