Next.js is a React framework for building full-stack web applications: the same codebase renders pages on the server, makes them interactive in the browser, caches at several layers, and runs request gates before pages.
1. Rendering
What it is. Rendering is turning components into HTML the browser can show. Next.js has several rendering modes, and the choice is about when the HTML is produced, not whether it is produced.
- SSR (server-side rendering). Pure SSR has no stored-first step: a request arrives, the server renders on the spot, the browser receives HTML. Then hydration: the browser re-renders the component tree in memory, matches the DOM, and attaches event listeners only. Hydration does not re-fetch the page; it makes a static page interactive.
- SSG (static site generation). The HTML is produced at build time. In the App Router, pages are static by default unless dynamic functions are used. generateStaticParams pre-builds known dynamic routes; dynamicParams controls what happens for routes outside that set.
- ISR (incremental static regeneration). A static page with an expiry: expiry equals last-generation time plus revalidate seconds. Requests do not extend the window. On each request past expiry, the stored page is served and regeneration happens in the background (stale-while-revalidate). On-demand regeneration uses revalidatePath and revalidateTag, callable only from a Server Action or Route Handler. Configuration lives at three places: page-level
export const revalidate, per-fetchnext: {revalidate}, and on-demand calls. - RSC (React Server Components). Server components run only on the server: their code is never shipped to the browser, and they can read databases, files, and secrets directly. They cannot use state, effects, or event handlers. Client components, marked 'use client', are shipped and interactive. A server component can import a client component as a leaf; a client component cannot import a server component. Rendered results are passed down via children or props instead.
Streaming. Streaming's only function is to keep one response open, push segments down as they become ready, and terminate with a finish signal. Classic SSR produces one HTML string in byte order. Streaming sends the early parts plus placeholders for the slow parts, in order; the slow part's real content is sent at the end of the stream, and an inline script moves it into its placeholder before hydration. Later content never overtakes earlier content. Late re-send is an old idea independent of streaming: Ajax (2005) is late re-send, driven by declarations the client pulls on. The real distinction is who drives the async: the client pulls per written declarations (Ajax) versus the server keeps pushing (streaming). "Each part usable the moment it arrives" is the generic effect of all incremental delivery, not streaming's unique point.
2. App Router vs Pages Router
What they are. Two routing systems in one framework. The Pages Router puts shared navigation in _app; the App Router puts it in layout. Layouts nest layer by layer.
Navigation. Navigation is driven by address change: the address changes, the App Router finds the page and its layouts, and unchanged layout layers do not re-run.
Reuse. Reuse here means the browser's rendered result is reused, not code reuse. The App Router's "does not run" (navigation code for unchanged layers) is a corollary of reuse. The Pages Router has no rendered-result reuse: every navigation re-computes the navigation, and only the page shows no visible change.
Status. The App Router did not replace the Pages Router. Both are alive; the App Router is newer and the one mainly pushed.
3. Caching
Three layers and one non-layer. Each layer moves only when its own content changes.
- Data Cache. Fetched data, stored on server disk, shared across requests. It stores shared raw material, not pages.
- Full Route Cache. Whole rendered pages, also on server disk. It survives restarts; redeploying clears it.
- Router Cache. Visited pages, in browser memory. It is gone when the tab closes.
- Request Memoization. Not a stored layer: the same data fetched twice within one render is fetched once. Layout reuse is across pages; request memoization is within one render.
Beyond the three layers sit the front layer (CDN: a geographically near copy; the visitor asks it first, a miss goes to the server) and the browser's own HTTP cache, which is not the Router Cache.
Why disk, not memory. Recomputing (network fetch plus render CPU) is far more expensive than one disk read. The operating system keeps frequently read files in memory anyway (page cache). RAM is small, expensive, dies on restart (a cold-start recompute storm), and is per-invocation in serverless. Disk is big, cheap, and restart-persistent: the sweet spot.
Disk reclamation. There is no automatic size-based eviction. Entries leave by expiry (the next visit refetches and overwrites), by manual revalidatePath or revalidateTag, or by redeploy wiping the cache directory.
Personalization. The framework needs no concept of a "user". It only looks at whether a render used request-carried things. Calling cookies(), headers(), or searchParams (request-time APIs) marks the render: a marked route is not storable-shared and re-renders per visit; an unmarked route is storable. Login state is the application's own code; the framework only passes the browser-sent credential through. Cookies move the route to dynamic (Full Route Cache), not the Data Cache's seconds directly; shared data still comes from the Data Cache on dynamic renders.
The failure mode. If per-user content is written carelessly into the shared layer, storage grows with user count and, first, leaks across users: shared-layer keys contain no "whose is this" by default, so personal data stored there is served to others. Correct writing: the shared part is stored once, the personal slice is never stored, fetched fresh each visit, or computed in the browser.
The browser's own HTTP cache is not the Router Cache. The Router Cache is Next.js's own code managing the router cache in browser memory: what to keep and staleness come from Next.js configuration, fresh data means a new request to the server, and there is no HTTP-header negotiation. The browser's own cache is protocol negotiation: the server says in response headers how long to store or not to store, the browser obeys; after expiry the browser asks "changed?" with the content fingerprint, and the server answers 304 (unchanged, keep using) or 200 (changed, here is the new copy). The server controls the browser's own cache via headers, not the Router Cache.
Partial Prerendering. One page, data changes, static-set parts do not change: the static parts are pre-rendered and stored, the dynamic parts are left as placeholders (Suspense), computed fresh per visit and filled in. It is a newer mechanism; availability and configuration differ by version. Current documentation splits a new model (Cache Components, v16) from the previous model; the fetch default differs too: current docs say not cached, v14 and earlier cached forever (revalidate=false means Infinity).
Caching as a security surface. The framework makes security-relevant decisions automatically: cache keys without user identity, and middleware ran-or-not judgments. A wrong automatic decision is a vulnerability, not a cosmetic bug. Two examples: CVE-2025-29927 (a header bypasses the middleware login check) and CVE-2024-46982 (one header pulls a no-store route into the shared cache for the next requester). This is also why Next.js keeps getting CVEs: a huge surface (routing, rendering, caching, middleware in one package, all facing untrusted requests), automatic security-relevant decisions, the most-used React framework drawing the most scrutiny and prompt disclosure, and new protocols (App Router, RSC, server actions) carrying higher bug density.
4. Built-in optimizations
next/image. A huge source image displayed small wastes download: the component serves a right-sized version. High-density screens need twice the physical pixels for the same display size, so it serves per screen, not always at display size.
next/font. Fonts are self-hosted on the application's own server: no external request. Only the needed glyphs are kept (subset), so files are smaller. The placeholder font is metric-matched to the real font, so the real font swaps in without text moving. The old way, an external stylesheet link, showed blank or fallback text and then everything jumped. The font files themselves live on server disk as static files. The placeholder's size numbers come from the real font file's metadata, read at build time and written into the page styles; the placeholder itself is the device's own system font and needs no download.
next/script. Loading strategies for third-party scripts (analytics, ads, chat): before the page is interactive, after the page is interactive (the default), or at idle time. This is not the same mechanism as hydration, but it is related: the default is deliberately placed after hydration so third-party scripts do not compete for resources and slow hydration down. Hydration makes the page interactive; the script strategy decides when outside scripts are inserted.
5. Middleware and Edge Runtime
Middleware. A gate before the page: it runs before the request reaches the page, checking login, redirecting when not logged in, splitting by region.
Edge Runtime. A lightweight JavaScript runtime with only browser-standard APIs (no Node-only things such as disk reads), running on edge nodes near users, with fast startup, for small, fast work. It exists because middleware runs on every request, and closer is faster.
Why a frontend framework owns it. Middleware must use the same rules as routing and rendering; rules written in two places that disagree produce vulnerabilities. The framework owns the programming model (what to write, when it runs); platforms (Vercel, Cloudflare) operate the nodes. This is a contested design. Against it: coupling (code is bound to where it runs), a growing surface (more CVEs), platform lock-in. For it: consistency, and small teams get a global edge without building infrastructure. At enterprise scale the tension sharpens: a real enterprise project has a separate backend that also needs edge computing, so unless the whole stack is one vendor (Vercel plus Next.js), the coupling surface between the framework's edge and the backend's edge is large. Large companies often put the edge layer at the gateway or platform level and let the frontend framework handle pages only.
6. Deployment
The real question is feature matching, not cloud itself. The same feature, on a different deployment, differs in availability and behavior. Cache location, scaling, billing (fixed fee versus usage), and data location are generic cloud questions; the Next.js-specific question is only how its features map to the deployment.
- Vercel. Everything works out of the box: cache, middleware, edge, image optimization.
- Self-hosting. Rendering and the cache layers work, all on the application's own server disk. There is no edge: no global nodes, middleware runs on the application's own server, and far users stay slow. Scaling is manual, and redeploying clears the cache.
- Other clouds (Cloudflare, Netlify, AWS). Adapters connect most features, with limits and differences, not natively. Cloudflare is the inverse shape: platform first, any framework. Workers plus Pages plus a Vite plugin let React, Vue, Svelte, and others run full-stack on Workers, and its edge belongs to the platform while the framework adapts. Its free tier is generous: 100,000 Workers requests per day free.
Choosing a deployment is choosing the row of this table you can accept.