Keentune

Next.js curriculum

22 chapters
·
227 concepts
·
free
Everything the adaptive question bank can teach and test in Next.js, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
New here? Read the Next.js guide
A free 16-minute primer — the mental model, the mistakes beginners make, and what to practise first.
A. Project setup and the file system
the app directory turns on the App Router, and app and pages can coexist in one project
src/app is supported, but config files stay at the project root either way
only files with a reserved name become routes, so components and tests can live inside route folders
a folder prefixed with _ is excluded from routing entirely
public/ files are served from the URL root, unhashed and unoptimized, so cache-busting is on you
pageExtensions decides which file suffixes the router even looks at
paths in tsconfig.json gives @/ imports with no extra bundler config
Next generates next-env.d.ts and ships a TS plugin; the generated file must not be edited
B. Layouts, pages and the segment tree
a folder creates a URL segment; without a page file that segment is not addressable
page.tsx renders the segment's own UI and is the file that makes the route public
layout.tsx receives children and wraps every page beneath it
the root layout is mandatory and is the only place <html> and <body> are rendered
navigating within a layout's subtree preserves its state and does not re-render it
template.tsx behaves like a layout but creates a fresh instance on every navigation
layouts nest by folder depth, and a child cannot skip a parent layout
route groups can each own a root layout, and moving between them costs a full page load
a layout receives params but never searchParams, because it does not re-render per query
route.ts and page.tsx cannot sit at the same segment; whichever exists owns every verb
the special files nest in a fixed order: layout → template → error → loading → not-found → page
C. Dynamic segments and route organization
[slug] matches exactly one segment and arrives in params
[...slug] matches one or more segments and yields an array
[[...slug]] additionally matches the parent route with no segments at all
params and searchParams are Promises in the App Router and must be awaited
returning the param sets prerenders exactly those paths at build time
dynamicParams = false turns an unlisted path into a 404 instead of rendering it on demand
a (folder) groups routes for layout purposes without contributing to the URL
@slot folders render several pages into one layout as named props
default.tsx is what an unmatched parallel slot renders on a hard navigation or reload
(.), (..) and (...) load another route inside the current layout, which is how a shareable modal works
the App Router has no built-in i18n config, so a locale is a [lang] dynamic segment with Proxy choosing the redirect
D. Server and Client Components
every component under app is a Server Component until a boundary says otherwise
"use client" marks an entry point, and everything imported below it joins the client bundle
the directive belongs at the very top of the file, above the imports
a Server Component may be async and await data; a Client Component may not
state, effects and event handlers require a Client Component, not a prop-only server one
props crossing server→client must be serializable, so functions and class instances are rejected
passing a Server Component as children of a Client Component keeps it rendering on the server
the server sends a serialized component tree, not just HTML, so a navigation can patch the tree in place
Client Components are prerendered to HTML on the server and then hydrated, so they are not client-only
React Context only exists inside the client tree, so providers sit at a boundary
import 'server-only' turns an accidental client import of server code into a build error
pushing "use client" toward the leaves is the lever that shrinks shipped JavaScript
E. Rendering: prerender, dynamic and streaming
a build produces an HTML shell plus an RSC payload that can be served instantly
cookies(), headers(), searchParams and connection() push a component to request time
one route can serve a prerendered shell and stream dynamic holes into it
with cacheComponents: true PPR is the default; the experimental_ppr export was removed in 16
the <Suspense> fallback is what lands in the static shell while its child streams
uncached data with neither a Suspense boundary nor use cache is a build error, not a silent fallback
wrapping purely synchronous work in <Suspense> does not opt it out of prerendering
loading.tsx is a whole-segment boundary; <Suspense> is a per-component one you place yourself
pure computation, module imports and synchronous I/O complete during prerender
Math.random(), Date.now() and crypto.randomUUID() must be cached or deferred behind connection()
an empty-fallback <Suspense> above <body> defers the whole app to request time, shell included
F. Fetching data on the server
awaiting fetch inside an async Server Component needs no hook and no loading state
a Server Component can query the database directly, so an internal Route Handler is a wasted hop
identical fetch GETs in one render pass run once, shared across layout, page and generateMetadata
React.cache gives the same per-request dedupe to a database call that fetch gets for free
awaiting one request before starting the next creates a waterfall that Promise.all avoids
kicking off a fetch before you await it warms the request cache while other work proceeds
an unawaited promise can be handed to a Client Component and unwrapped with use()
since Next 15 fetch does not cache unless you opt in
reading searchParams on a page defers that page's render to request time
G. Caching with Cache Components and use cache
cacheComponents: true in next.config is what enables use cache and the PPR default
the directive can mark a whole file, one component, or a single function
at file level every export must be an async function
caching the fetch function and caching the rendered component are different reuse and invalidation tradeoffs
the key is built from build ID, function identity, arguments and captured closure values
a variable read from an outer scope is silently bound in and joins the cache key
arguments use Server Component serialization, so class instances, functions and URL objects are rejected
return values use the looser client serialization, so a cached component may return JSX it could not accept
a non-serializable children or action may pass through a cached component as long as it is never inspected
cookies() and headers() cannot be called inside a cached scope; read them outside and pass the value in
React.cache state set outside a use cache scope is invisible inside it
the default store is an in-memory LRU, so serverless instances often do not share entries between requests
a platform cache handler makes entries durable and shared at the cost of a network roundtrip
per-user caching for compliance cases where runtime data cannot be lifted out of the scope
with Draft Mode enabled, cached functions re-execute per request and results are not stored
H. Revalidation and invalidation
named profiles from seconds to max set stale, revalidate and expire together
stale governs the client, revalidate the background server refresh, expire the hard cutoff
a seconds profile or a sub-five-minute expiry is excluded from the prerender and becomes a dynamic hole
cacheTag() labels a cached scope so it can be invalidated by name later
revalidateTag keeps serving stale content while fresh content regenerates in the background
updateTag expires the entry now for read-your-own-writes, and is legal only inside a Server Action
path invalidation drops everything on a route, so tags are preferred when you know them
refresh() re-requests the current route's payload without invalidating any cache entry
the client router enforces a minimum stale time regardless of the configured profile
Incremental Static Regeneration is this same mechanism viewed at page granularity
I. The previous caching model (pre-Cache-Components)
without Cache Components you opt a single request in with cache: 'force-cache'
next: { revalidate: n } puts a time window on one fetch
next: { tags: [...] } is the fetch-level equivalent of cacheTag
wraps a non-fetch async function with a key prefix, tags and a revalidate window
export const dynamic selects between auto, force-dynamic, force-static and error
under force-static, cookies(), headers() and useSearchParams() return empty values instead of throwing
export const revalidate = n sets the window for the whole segment
dynamic, dynamicParams, revalidate and fetchCache no longer exist once cacheComponents is on
revalidate, dynamic, runtime and fetchCache are per-segment exports read at build time, so they cannot be computed at request time
J. Mutations: Server Actions and forms
'use server' marks a function, or a whole file of functions, as server-callable
handing an action to <form action> produces a working POST even before hydration
a form-invoked action receives FormData, so values arrive as strings and need parsing
.bind is how a record id travels alongside the form payload
returns the previous action result plus a pending flag, and keeps the form progressively enhanced
a child component reads the enclosing form's pending state without prop drilling
renders the expected outcome immediately and reverts if the action fails
one response carries both the action's return value and a freshly rendered payload for the route
updateTag, revalidatePath, refresh, a cookie write or redirect include the re-render; a plain return does not
a stale-while-revalidate revalidateTag is the exception: the change shows up on a later read
the client dispatches actions one at a time, so Promise.all over actions does not parallelize them
redirect() throws a control-flow error, so code after it never runs and a wrapping try/catch will swallow it
an action is a POST endpoint anyone can call, so rendering the form conditionally is not authorization
an uncontrolled form is reset by the browser on a successful action, so a manual reset is only needed for controlled inputs
K. Route Handlers and the request/response layer
each HTTP verb is its own named export, and an unsupported verb returns 405
handlers take a Web Request and return a Web Response, not Node's req/res
handlers run per request unless a GET explicitly opts into force-static
non-GET verbs are never cached, even beside a cached GET in the same file
with Cache Components on, a GET handler prerenders unless it touches runtime or uncached data
use cache cannot sit in the handler body; the cached work must move to a helper function
cookies() and headers() are async in the App Router and must be awaited
a cookie write belongs on the outgoing response, not on the incoming request object
returning a ReadableStream streams the body incrementally instead of buffering it
Next adds no CORS headers, so the handler or a headers config rule must
after() schedules work to run once the response is sent, so logging and analytics stop delaying the reply
L. Proxy (formerly Middleware)
Next 16 renames Middleware to Proxy: proxy.ts replaces middleware.ts with identical behavior
a single proxy.ts beside app or pages handles the whole project, though it may import modules
the exported config.matcher decides which paths invoke it at all
a rewrite keeps the URL and changes what is served; a redirect changes the URL the browser shows
request headers can be forwarded modified and response headers set on the way back out
fetch cache, revalidate and tag options are ignored in Proxy, and it is the wrong layer for a session lookup
a cookie-presence check belongs here; real authorization belongs next to the data
M. Navigation, links and prefetching
<Link> performs a client-side transition while a plain <a> reloads the document and loses state
links entering the viewport prefetch in production only, never during next dev
a static route prefetches whole; a dynamic one prefetches only down to the first loading boundary
prefetched static payloads are reused for a few minutes; dynamic ones are not cached unless staleTimes says so
router.prefetch() warms a route on hover, on scroll, or on an analytics signal
the App Router hooks import from next/navigation, not next/router
replace overwrites the current history entry, so the back button skips it
reading the active route from a Client Component without prop drilling
useSearchParams without a Suspense boundary bails the whole route out of static rendering
navigation scrolls to the top by default and scroll={false} opts out
a pending indicator for a transition that has started but not yet committed
View Transitions animate between routes by tagging elements, rather than by animating the layout that wraps them
N. Errors, not-found and interrupts
error.tsx must be a Client Component and receives error plus a reset function
a segment's error.tsx renders inside its layout, so an error thrown by that layout bubbles up
global-error.tsx is the last resort and must render its own <html> and <body>
notFound() throws and renders the nearest not-found.tsx, so nothing after the call executes
the root not-found.tsx is also what an entirely unmatched URL gets
server error details are stripped in production and correlated through the error digest
return a typed failure from an action for expected errors and throw only for genuine bugs
a catch-all try/catch swallows Next's control-flow errors unless they are rethrown
forbidden() and unauthorized() render their own segment files behind the authInterrupts flag
O. Metadata, SEO and OG images
exporting a metadata object from a layout or page is the static form
the dynamic form is an async function that receives params and can await data
a child's metadata overrides the parent field by field; it does not deep-merge nested objects
relative Open Graph and Twitter image URLs need metadataBase to resolve to absolute ones
title.template composes child titles and title.absolute opts out of the template
opengraph-image, icon, robots and sitemap files take precedence over their config equivalents
ImageResponse renders JSX to a PNG so social cards are generated, not designed by hand
viewport width and themeColor moved out of metadata into generateViewport
sitemap.ts and robots.ts are route handlers that produce the file, so they can read a database
structured data is injected as a <script type="application/ld+json">, not through the metadata object
generateMetadata reading runtime data is accounted for apart from the page's own render
P. Images, fonts, scripts and assets
width/height or fill reserve the box before the bytes arrive
importing a local image supplies its dimensions and enables an automatic blur placeholder
a fill image without sizes makes the browser request the largest candidate at every viewport
external hosts must be allowlisted in remotePatterns; the old domains array was removed
from Next 16 the images.qualities list must contain any quality value you request
images lazy-load by default, and priority is how the LCP image gets preloaded instead
you can bypass the optimizer entirely or delegate resizing to a CDN loader
optimized images are cached for minimumCacheTTL, which is independent of the source's own headers
next/font downloads and self-hosts the files at build time, so no request reaches Google at runtime
declaring subsets is required and display: 'swap' decides what shows during the fallback period
a font loader must be called at module scope with literal arguments so it can run at build time
beforeInteractive, afterInteractive, lazyOnload and worker decide when a third-party script executes
alt is required on next/image; an empty string is the deliberate marker for a decorative image, not an omission
Q. Styling
a .module.css file compiles to locally scoped class names, so collisions are impossible
global stylesheets can be imported from any component, but their rules stay global
Tailwind runs as a PostCSS plugin and its content globs must cover every route folder or classes get purged
runtime CSS-in-JS libraries require a Client Component and a registry to work with streaming
stylesheet order comes from import order, which is why a specificity fight can appear only in a production build
R. Configuration and environment
next.config.ts executes in Node at build and boot time and is never shipped to the browser
.env.local beats .env.development, which beats .env, and .env.local is skipped in the test environment
only NEXT_PUBLIC_-prefixed variables reach the browser, and they are inlined at build time
an inlined public value cannot change without a rebuild, so per-environment values must be read server-side
declarative path mapping, where permanent picks 308 over 307, and it is cheaper than doing it in Proxy
response headers per path pattern, which is where security headers belong
basePath serves the app under a sub-path; assetPrefix moves only the static assets to a CDN
packages excluded from server bundling because they need real Node module resolution
compiling an untranspiled workspace or npm dependency instead of failing on its syntax
typedRoutes turns href into a checked union of the routes that actually exist
S. Runtimes, build output and deployment
Node is the default runtime; the Edge runtime exposes only a Web-API subset
the Edge runtime does not support Incremental Static Regeneration
export const runtime = 'edge' opts one segment in, and 'experimental-edge' no longer exists
next build prints each route as static or dynamic, and that table is how you verify prerendering
output: 'standalone' emits a minimal self-contained server for a small container image
output: 'export' gives up Server Actions, Route Handlers, Proxy and on-demand image optimization
a shared cache handler is what makes revalidation consistent across replicas
a new build ID is part of every cache key, so a deploy discards the entries from the previous build
the CDN caches responses by header while the framework cache has its own tags and lifetimes
the Node version comes from the platform and the engines field, not from anything Next itself exports
T. Security and data protection
centralizing authorized reads in one module beats repeating checks per page
the server-only package turns a leaked secret import into a build error rather than a runtime surprise
everything an action or component returns is serialized to the client, so shape it to what the UI renders
React's taint APIs make a specific object or value throw if it ever crosses to the client
action POSTs are rejected when Origin does not match Host, and proxy domains need allowedOrigins
action request bodies are capped by default and the limit is configurable
closure values captured by an inline action are encrypted, so multi-instance deploys need a shared encryption key
a strict Content-Security-Policy needs a per-request nonce generated in Proxy and read during render
a session cookie needs httpOnly, secure and a sameSite value; readable-from-JS is the defect the attributes exist to prevent
an SRI hash makes the browser refuse a script whose bytes changed in transit or at the CDN — it is tamper detection, not confidentiality
U. Pages Router: the legacy contrast
build-time data fetching that produces a fully static page
false, true and 'blocking' decide what an unlisted path gets
per-request data fetching, which permanently opts the page out of static optimization
returning revalidate from getStaticProps is the original Incremental Static Regeneration
returned props are JSON-serialized, so a Date arrives on the page as a string
_app wraps every page while _document shapes the HTML shell and never runs in the browser
one default-export handler per file with Node-style req/res, unlike the verb-export Route Handler
next/router exposes query, isReady and route events that the App Router hooks deliberately do not
next/head is the Pages-Router metadata mechanism, replaced by the metadata export
a page with no server data function is emitted as static HTML automatically
app and pages coexist during a migration, and app wins when both define the same path
V. Tooling, testing and diagnostics
Next 16 uses Turbopack for next dev and next build unless you opt back to webpack
Turbopack's on-disk cache speeds up rebuilds and has nothing to do with the data cache
unit-test renderers cannot render async Server Components, so those paths need end-to-end tests
next/dynamic splits a client chunk and can skip server rendering with ssr: false
locale-dependent formatting, current time and browser-only branches make server and client HTML diverge
next dev --inspect attaches a Node inspector to the server process, which is where a Server Component actually runs
instrumentation.ts exports register(), which runs once per server process before any request is served — the hook for tracing and monitoring setup
React Compiler memoizes automatically at build time; Next 16 ships support for it as an opt-in, not as the default
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All about Next.js practice
Also on your phone
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC