Next.js App Router
Next.js is the React framework for production: file-based routing, server components, static generation, ISR and edge-ready deployments. Learn the app/ directory conventions and data fetching model.
What is this?
Next.js is a full-stack React framework built on the App Router. A file in app/ becomes a route: page.tsx defines the UI, layout.tsx wraps shared chrome, and route.ts creates API endpoints. React Server Components render on the server by default, so pages stream HTML with less JavaScript shipped to the client.
Why do you need it?
- File-based routing removes routing boilerplate and makes every route colocated with its data.
- Server Components render on the server, drastically cutting client JavaScript and time-to-interactive.
- Static generation, SSR and ISR are chosen per route instead of per framework.
- Automatic optimization — image resizing, font subsetting, code splitting — ships with the framework.
- A single codebase serves HTML, API routes and middleware for edge behavior.
Mental model
A restaurant's prep kitchen and dining room
Server and client components
The kitchen (server) does the heavy prep and sends out finished dishes; the dining room (client) only handles the final touches like refills. Next.js decides what is prepped on the server and what needs live interactivity at the table.
How it works
- A request matches a route by the file tree under app/; segments become URL paths.
- layout.tsx renders once and wraps page.tsx, persisting navigation state across child routes.
- Server Components run on the server and stream HTML; 'use client' marks components that render on the client with their JS bundle.
- async page functions can await data directly — the server fetches, resolves and streams the result.
- Next caches outputs per route and per data call; ISR revalidates static pages on a schedule or on demand.
Resolving a request through the App Router.
Key commands
npx create-next-app@latest my-appScaffold a Next.js project with the App Router.
Success! Created my-app ...
npm run devStart the development server with fast refresh.
Ready in ... Local: http://localhost:3000
npm run buildType-check, compile and produce static plus server output.
Route (app) /blog [ISR] ...
npm run startServe the production build.
npm run lintRun ESLint with the next and react-hooks configs.
Examples
A blog page that fetches on the server
import Link from "next/link"; export default async function BlogIndex() { const response = await fetch("https://api.example.com/posts", { next: { revalidate: 3600 }, }); const posts = await response.json(); return ( <main> <h1>Latest posts</h1> <ul> {posts.map((post) => ( <li key={post.id}> <Link href={"/blog/" + post.slug}>{post.title}</Link> </li> ))} </ul> </main> );}Because this is a Server Component (no 'use client'), the function is async and awaits fetch directly on the server. The revalidate option caches the response for an hour (ISR). Link performs client-side navigation, so the page loads only the data it needs.
In production
- Marketing sites and documentation get static generation with ISR so pages are fast and search-friendly.
- E-commerce and content products use dynamic rendering with streaming for personalized data.
- Server Components fetch directly from databases and internal APIs, reducing client round-trips.
- Vercel and self-hosted Node servers run the same app, with edge middleware for auth and geo logic.
Best practices
- Default to Server Components and add 'use client' only where interactivity actually requires it.
- Keep client components at the leaves so the interactive surface and JS bundle stay small.
- Use next/image and next/font instead of raw img and font files for automatic optimization.
- Model every data path with loading.tsx and error.tsx boundaries for streaming fallbacks.
- Set explicit cache behavior with fetch options or the unstable_cache API instead of relying on defaults.
Common mistakes
- Adding 'use client' to the top of a page just to use a small interactive child.
- Using window or document in a Server Component, which crashes server rendering.
- Passing functions or Date objects from server to client components, which are not serializable.
- Using the Pages Router conventions (getServerSideProps) inside the App Router, where they do nothing.
Common issues
Error: Event handlers cannot be passed to Client Component propsA function is passed from a server to a client component, which cannot serialize functions.
Define the handler inside the client component or pass plain data and wire the handler there.
window is not definedA server component references the browser global.
Move the code into a client component, a useEffect, or guard with typeof window !== 'undefined'.
Dynamic server usage ... this page could not be statically generatedThe route reads request-time data (headers, cookies) without opting in.
Mark the route dynamic with export const dynamic = 'force-dynamic' or move the request-time read into a server action.
Debugging tips
- Read the dev overlay's error stack to find whether the error originated in a server or client render.
- Run npm run build and read the printed route table to confirm which routes are static, dynamic or ISR.
- Inspect the network panel to see whether the page streamed HTML before JavaScript arrived.
- Use the React Server Components logo in DevTools to trace which components are client-rendered.
Interview checkpoint
Hands-on lab
- Scaffold a Next.js app
- Create routes with the app/ directory
- Fetch data in a Server Component
- Add loading and error boundaries
Build a Blog Index with the App Router
35 min · intermediate
Quick quiz
Which file defines the UI for a route in the App Router?
Server Components add their JavaScript to the client bundle.
What does next: { revalidate: 60 } do on a fetch?
Cheat sheet
app/page.tsxHome route UIapp/layout.tsxShared layout wrapperapp/[slug]/page.tsxDynamic route segmentapp/loading.tsxStreaming fallback UIapp/route.tsAPI endpoint handlerasync function Page()Server component fetchfetch(url, { next: { revalidate } })ISR caching'use client'Opt into client rendercookies() / headers()Request-time server reads