Next.js Fundamentals
Next.js is a React framework that decides where and when each page renders. Compare static, dynamic and streamed rendering request by request, and learn the App Router's project structure, layouts and extended fetch.
🎯 What you'll learn
- What Next.js actually is
- Project structure, layout.tsx vs page.tsx
- Rendering models: static, dynamic, streaming, client
- The build pipeline, data fetching and metadata
📚 Before you start
1What Next.js Actually Is
React is a rendering library — it turns component trees into UI, but it has no opinion on routing, data fetching, bundling, or how your app gets from source files to a running server. Next.js is a framework built on top of React that supplies all of that: a file-system router, a build pipeline (based on Webpack or Turbopack), a server runtime, and a rendering model that mixes server-rendered and client-rendered output in the same tree.
React → component model, reconciliation, hooks
Next.js → routing, bundling, server runtime, rendering strategy, deployment target
Your components → written in React, orchestrated by Next.js conventionsThe unit you install is next, plus react and react-dom as peer dependencies — Next.js does not replace React, it wraps it.
2Project Structure (App Router)
my-app/
app/
layout.tsx # root layout — wraps every page, required
page.tsx # route: /
globals.css
about/
page.tsx # route: /about
blog/
[slug]/
page.tsx # route: /blog/:slug
public/ # static assets served at /
next.config.js
package.jsonEvery folder under app/ is a route segment. A folder becomes a navigable URL only when it contains a page.tsx — folders without one are just structural (e.g. for co-locating components) and add nothing to the URL unless combined with routing conventions covered in the Routing topic.
3layout.tsx vs page.tsx
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<nav>My Site</nav>
{children}
</body>
</html>
);
}// app/page.tsx
export default function HomePage() {
return <h1>Welcome</h1>;
}layout.tsx renders the shared chrome (nav, footer, providers) and receives children — the matched page or nested layout. page.tsx is the actual leaf content for that route. The root layout.tsx is mandatory because it owns the <html> and <body> tags; Next.js does not inject them for you elsewhere.
4Rendering Models
| Model | When it runs | Output |
|---|---|---|
| Static Rendering (SSG) | at build time | pre-rendered HTML, served from cache/CDN |
| Dynamic Rendering (SSR) | per request, on the server | fresh HTML generated per request |
| Client Rendering (CSR) | in the browser | JS executes, renders/updates the DOM |
| Streaming | server, incrementally | HTML sent in chunks as pieces become ready |
A component is static by default unless it reads something request-specific (cookies, headers, search params, an uncached fetch) or is explicitly marked dynamic — at which point Next.js opts the route into per-request rendering.
// app/page.tsx — static by default: no request-specific data read
export default async function Page() {
const data = await fetch("https://api.example.com/posts", {
cache: "force-cache", // default — enables static rendering
}).then((r) => r.json());
return <PostList posts={data} />;
}// app/page.tsx — dynamic: reading cookies opts the route out of static rendering
import { cookies } from "next/headers";
export default async function Page() {
const theme = (await cookies()).get("theme")?.value ?? "light";
return <div data-theme={theme}>...</div>;
}5The Build Pipeline
next dev → dev server, fast refresh, on-demand compilation (Turbopack or Webpack)
next build → production build: type-checks, bundles, statically analyzes every route,
pre-renders static routes to HTML, generates the route manifest
next start → runs the production server produced by `next build`next build prints a route table classifying every route as ○ (Static), ƒ (Dynamic), or streaming-capable — this is Next.js telling you, per route, which rendering model it picked based on the code you wrote, not a setting you configure globally.
6Data Fetching Is Just fetch (Extended)
Next.js patches the global fetch on the server to add caching and revalidation options that plain fetch doesn't have:
fetch(url, { cache: "force-cache" }); // cache indefinitely (default for GET)
fetch(url, { cache: "no-store" }); // always fetch fresh, forces dynamic rendering
fetch(url, { next: { revalidate: 60 } }); // Incremental Static Regeneration — revalidate every 60sThis is the mechanism ISR (Incremental Static Regeneration) is built on: a statically rendered page is served from cache, and after revalidate seconds elapse, the next request triggers a background re-render that replaces the cached HTML.
7Metadata
// app/page.tsx
export const metadata = {
title: "My App",
description: "A Next.js app",
};Exporting a metadata object (or a generateMetadata async function for dynamic values) from a page.tsx/layout.tsx is how Next.js builds <head> tags — no manual <Head> component like the old Pages Router required.
Under the HoodHow it works internally▾
next build is a static analysis pass over your route tree, not just a bundler run. When you run next build, Next.js's compiler walks every file under app/, determines for each route segment whether it can be fully pre-rendered (no dynamic APIs used, no uncached fetch) or must be server-rendered per request, and emits a route manifest — a JSON description of every route, its rendering strategy, and which JS chunks it needs. This manifest is what the Next.js server (or the platform running it, e.g. Vercel) consults at request time to decide: serve a cached HTML file straight from disk/CDN, or invoke a server-side render function.
The dev server and the production server are genuinely different code paths. next dev runs an on-demand compiler: routes are compiled lazily, the first time they're requested, which is why the first hit to a fresh page in dev is slow and subsequent hits are fast (module compilation is cached in memory). next build + next start runs the fully compiled, statically analyzed production server — every route's code and rendering strategy is fixed ahead of time, so per-request work is just "run the render function this route was determined to need," not "figure out what this route needs."
Static rendering means literally writing an HTML file (plus a serialized RSC payload) to disk at build time. For a route Next.js determines is static, next build executes your Server Components once during the build, captures the resulting HTML and the React Server Component "flight" payload (the serialized tree format covered in depth in the Server Components topic), and writes both to .next/server/app/. At request time, the server (or CDN) just streams those pre-built files back — no React rendering happens per request at all for a purely static route.
⚠️ Common Pitfalls
- Forgetting the root
layout.tsxmust contain<html>and<body>— omitting them is a build error, since Next.js does not inject them automatically outside the Pages Router's_document.jsequivalent. - Assuming every component is "server-side rendered" the same way — a route can mix static, dynamic, and streamed segments; "SSR" isn't one blanket mode, it's a per-route (and per-component, once Client Components enter the picture) decision Next.js makes from your code.
- Not realizing
fetchcaching defaults changed across Next.js versions — historicallyfetchcached by default; more recent versions default tono-storefor certain contexts. Always pass an explicitcache/next.revalidateoption rather than relying on the implicit default. - Treating
public/andapp/the same — only literal static files (images, fonts,favicon.ico) belong inpublic/, served as-is at the root URL; components and routes belong inapp/. - Expecting
next devperformance to predict production performance — dev mode trades runtime speed for fast iteration (unminified bundles, on-demand compilation); always benchmark against anext build && next startrun.
📋 Quick Reference
app/layout.tsx → shared shell, must include <html>/<body> at root
app/page.tsx → route leaf content
app/<segment>/page.tsx → nested route
public/ → static files served at /
metadata export → builds <head> tags
fetch(url, {cache: "force-cache"}) → static (default)
fetch(url, {cache: "no-store"}) → dynamic, per-request
fetch(url, {next: {revalidate: N}}) → ISR, revalidate every N seconds
next dev / next build / next start → dev server / production build / production server🧠 Key takeaways
- Next.js adds routing, rendering strategies, data fetching, bundling and deployment conventions to React.
app/folders define routes; layout.tsx persists across navigations, page.tsx is the route's UI.- Rendering: static (build/ISR), dynamic (per request), streaming (Suspense), plus client components for interactivity.
- Extended
fetchcontrols caching/revalidation; exportmetadatafor SEO.
🎤 Interview Q&A
Q1What does Next.js add on top of React?core▾
File-based routing, server-side rendering, static generation and incremental revalidation, React Server Components, data fetching and caching, route handlers (backend endpoints), image/font optimization, and build tooling.
- App Router vs Pages Router? App Router (app/) uses Server Components and nested layouts; Pages Router is the older model.
Q2Explain SSR, SSG, ISR and CSR in Next.js.core▾
SSR: render per request on the server. SSG: render at build time. ISR: static pages regenerated in the background after a revalidate interval or on demand. CSR: render in the browser. The App Router chooses static or dynamic rendering per route based on data usage.
- What makes a route dynamic? Using cookies/headers, uncached fetches, or dynamic config.
Q3How does data caching work in the App Router?senior▾
Server-side fetches can be cached and revalidated (revalidate, revalidateTag, revalidatePath); the router cache stores RSC payloads on the client. Recent versions make caching more explicit and opt-in.
- How do you force fresh data?
cache: 'no-store'or dynamic rendering.
Q4What are layouts and why are they useful?core▾
layout.tsx wraps child routes and persists across navigation without re-rendering, ideal for shells, navigation and shared providers. Nested layouts compose per route segment.
- What are loading.tsx and error.tsx? Suspense and error boundary UIs for a segment.
Q5How do you deploy Next.js?core▾
On Vercel (zero-config), as a Node server (next start), as a standalone output in Docker, or via adapters (OpenNext for AWS). Consider caching, ISR storage and image optimization in self-hosting.
- What's output: 'standalone'? A minimal server bundle for container deployments.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.
✅ Check yourself
Answer the questions — feedback is instant.