Engineering Notes
Home/Frontend/Ecosystem/Next.js Fundamentals
Frontend · Ecosystem

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.

🎨 Frontend● Beginner⏱ 7 min read▶ 1 interactive🛠️ 3 exercises🎤 5 interview Q&As

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

text
React            → component model, reconciliation, hooks
Next.js          → routing, bundling, server runtime, rendering strategy, deployment target
Your components  → written in React, orchestrated by Next.js conventions

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

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

Every 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

tsx
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <nav>My Site</nav>
        {children}
      </body>
    </html>
  );
}
tsx
// 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

ModelWhen it runsOutput
Static Rendering (SSG)at build timepre-rendered HTML, served from cache/CDN
Dynamic Rendering (SSR)per request, on the serverfresh HTML generated per request
Client Rendering (CSR)in the browserJS executes, renders/updates the DOM
Streamingserver, incrementallyHTML 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.

tsx
// 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} />;
}
tsx
// 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

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

next build walks the app/ route tree, decides static vs dynamic per route, and emits a route manifest plus pre-rendered output for static routes
next build walks the app/ route tree, decides static vs dynamic per route, and emits a route manifest plus pre-rendered output for static routes

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:

tsx
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 60s

This 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

tsx
// 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

  1. Forgetting the root layout.tsx must contain <html> and <body> — omitting them is a build error, since Next.js does not inject them automatically outside the Pages Router's _document.js equivalent.
  2. 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.
  3. Not realizing fetch caching defaults changed across Next.js versions — historically fetch cached by default; more recent versions default to no-store for certain contexts. Always pass an explicit cache/next.revalidate option rather than relying on the implicit default.
  4. Treating public/ and app/ the same — only literal static files (images, fonts, favicon.ico) belong in public/, served as-is at the root URL; components and routes belong in app/.
  5. Expecting next dev performance to predict production performance — dev mode trades runtime speed for fast iteration (unminified bundles, on-demand compilation); always benchmark against a next build && next start run.

📋 Quick Reference

text
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 fetch controls caching/revalidation; export metadata for SEO.

🎤 Interview Q&A

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

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.

Follow-ups interviewers ask
  • 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.

Follow-ups interviewers ask
  • 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.

Follow-ups interviewers ask
  • 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.

Follow-ups interviewers ask
  • 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.

Follow-ups interviewers ask
  • 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.

1. A page calls cookies(). How does it render?
Request-specific data.
2. What does layout.tsx provide?
Persistent shell.
3. Benefit of streaming with Suspense?
Progressive rendering.
4. ISR (revalidate: 60) means…
Static + freshness.