Next.js Cheatsheet - Next.js Framework Reference

For React developers building full-stack apps who must decide between SSR and SSG. The core of Next.js is not a list of APIs but clarifying who renders on the server, what interacts on the client, and when data goes stale — server components cannot attach onClick, and ISR serves cached data that can lag behind. By the end you can build a practical App Router page and layout, draw the client/server component boundary, weigh static generation vs ISR, and handle middleware and data fetching.

Web Services·43 commands·Last updated 2026-07-21
nextjsreactssrssgFrontend

Typical Use Case

For engineers taking an App Router project from runs locally to behaves in production. The cost is rarely the markup but the rendering boundary: which data is fetched at build time, per request, or on the client. The sheet covers the file conventions (page, layout, loading, error, not-found), route groups, the fetch cache options, the dynamic and revalidate switches, Route Handlers and middleware, and metadata. Use it as a review checklist when a page slows down on every request.

App Router Basics 5

app/page.tsx
Page component, Server Component by default
app/layout.tsx
Root or nested layout, wraps child pages
app/loading.tsx
Loading UI, shown automatically with Suspense
app/error.tsx
Error boundary UI, catches runtime errors
app/not-found.tsx
404 page, shown when route is not found

Data Fetching 6

fetch(url, { cache: "force-cache" })
Static data fetching, default caching strategy
fetch(url, { cache: "no-store" })
Dynamic data fetching, fresh on every request
fetch(url, { next: { revalidate: 60 } })
ISR incremental static regeneration, revalidate after 60s
generateStaticParams()
Generate static paths for dynamic routes
"use server"
Mark server actions, callable from client forms
server-only
Ensure code only runs on server, never leaks to client

Rendering Strategies 5

export const dynamic = "force-static"
Force static rendering, HTML generated at build time
export const dynamic = "force-dynamic"
Force dynamic rendering, generated per request
export const revalidate = 3600
Page-level ISR interval in seconds
"use client"
Mark as Client Component, use browser APIs and interactivity
export const runtime = "edge"
Use Edge Runtime, lower latency

Layout & Templates 5

export const metadata = { title: "Page Title" }
Static metadata, supports title/description/OG
export async function generateMetadata()
Dynamic metadata generation, receives params and searchParams
app/@modal/default.tsx
Parallel route slot, independently rendered area
app/(...)/[...slug]/page.tsx
Intercepting route, intercepts external links within current layout
template.tsx
Template component, similar to layout but remounts on navigation

API & Middleware 6

app/api/route.ts
Route Handler, create API endpoints
export async function GET(request)
Handle GET request in Route Handler
middleware.ts
Middleware file, runs before requests for routing decisions
NextResponse.redirect(new URL("/login", request.url))
Redirect request in middleware
NextResponse.next()
Continue request processing in middleware
cookies().get("token")
Read cookies on server

Static Assets & Optimization 5

import Image from "next/image"
Optimized Image component, lazy loading and WebP auto-conversion
import { Inter } from "next/font/google"
Google Font optimization, self-hosted to avoid external requests
import Script from "next/script"
Script loading optimization, supports afterInteractive/lazyOnload strategies
export const metadata = { robots: { index: true, follow: true } }
SEO crawler configuration
<Link href="/about" prefetch={true}>
Link auto-prefetch, loads page resources on hover

Deployment & Configuration 5

output: "standalone"
Standalone deployment output, includes dependencies, ideal for Docker
output: "export"
Static export to plain HTML/CSS/JS, deployable to any static host
process.env.NEXT_PUBLIC_API_URL
Client env vars, exposed with NEXT_PUBLIC_ prefix
next build
Production build, generates optimized code and static assets
next start
Start production server, reads .next directory build output

Parameter matrix

ParameterEffectExample
app/page.tsxThe only page file for route /; the file name is the URL, and one route can have only one page.tsxapp/page.tsx
app/layout.tsxLayout; a parent layout does not re-render when child routes change; must contain <html> and <body>app/layout.tsx
app/loading.tsxSame-level Suspense fallback UI, shown automatically during route segment changes and data waitsapp/loading.tsx
app/error.tsxCatches render errors in the same page segment and must export a client component; provides reset() to retryapp/error.tsx
app/not-found.tsxThe 404 page thrown by notFound(); put the global 404 at the rootapp/not-found.tsx
app/(marketing)/about/page.tsxRoute group, wrapped in parentheses and not present in the URL; used to hold shared layouts without polluting the pathapp/(marketing)/about/page.tsx
app/blog/[slug]/page.tsxDynamic segment; [slug] is passed via params; paired with generateStaticParams to prerender all entries at build timeapp/blog/[slug]/page.tsx
app/(...)/[...slug]/page.tsxCatch-all segment (..) and optional segment (...) ellipsis syntax, used to match multi-level paths outside appapp/(...)/[...slug]/page.tsx
useRouter()Get the router in a client component and navigate with router.push / replace; cannot be called in a server componentuseRouter()
<Link href="/about">Client-side navigation; prefetches the JS and data of subsequent routes by default; replaces a bare <a> to avoid a full page reload<Link href="/about">
useSearchParams()Read the query string; it marks the whole subtree as dynamic rendering (CSR bailout) and must be wrapped in SuspenseuseSearchParams()
fetch(url, { cache: "force-cache" })Force caching and reuse long-term; equivalent to statically rendering that datafetch(url, { cache: "force-cache" })
fetch(url, { cache: "no-store" })Hit the origin on every request; the cache is used only once per request; suitable for personalized and real-time datafetch(url, { cache: "no-store" })
fetch(url, { next: { revalidate: 60 } })ISR semantics: the cache expires after 60 seconds and re-fetches in the background on the next requestfetch(url, { next: { revalidate: 60 } })
generateStaticParams()Enumerate parameters for a dynamic route at build time; every returned value is statically prerenderedgenerateStaticParams()
"use server"Marks a function as a Server Action that a client form can call directly; embedded third-party credentials do not enter the bundle"use server"
server-onlyModule-level import guard that fails the build if referenced by a client component; used to protect secrets and DB connectionsserver-only
"use client"Declares the client boundary; this file and the modules it imports enter the browser bundle; useState/useEffect live on this side"use client"
export const dynamic = "force-dynamic"Force the whole segment to dynamic rendering on every request; suitable for real-time pages; turns off all caching for the segmentexport const dynamic = "force-dynamic"
export const dynamic = "force-static"Force static rendering; ignores dynamic APIs and freezes the result; suitable for content that never changesexport const dynamic = "force-static"
export const revalidate = 3600Page-level ISR interval (seconds); simpler than setting revalidate per fetchexport const revalidate = 3600
export const runtime = "edge"Switch to the Edge Runtime; lower cold start but no Node APIs; using Node-only libraries fails the buildexport const runtime = "edge"
export async function generateMetadata()Generate a dynamic title/description from params, producing the <head> and OG tags for each routeexport async function generateMetadata()
app/@modal/default.tsxSlot directory for Parallel Routes; default.tsx is a required soft fallbackapp/@modal/default.tsx
template.tsxA template that remounts on every navigation; put enter animations here; the only difference from layout is whether it remountstemplate.tsx
app/api/route.tsRoute Handler entry point; export functions like GET/POST per method, replacing the pages/api styleapp/api/route.ts
export async function GET(request)GET handler; request is a standard Web Request, and you can read the query string directly with new URL(request.url)export async function GET(request)
middleware.tsDefined at the root (or under src); the matcher determines the interception scope; used for auth and rewritesmiddleware.ts
NextResponse.redirect(new URL("/login", request.url))Server-side redirect; the origin must be derived from request.url and must not be an absolute domain (multi-domain deployments would redirect to the wrong host)NextResponse.redirect(new URL("/login", request.url))
NextResponse.next()Let the request continue; omitting this line also continues by default, but writing it explicitly is clearerNextResponse.next()
cookies().get("token")Read a cookie in a server component / route handler; reading it makes that route dynamic renderingcookies().get("token")
import Image from "next/image"Built-in image component that auto-generates srcset/sizes and uses the optimization pipeline by default; forgetting width/height triggers a missing-dimensions warningimport Image from "next/image"
import { Inter } from "next/font/google"Self-host fonts at build time, eliminating external requests and CLS; the build fails when the network is restrictedimport { Inter } from "next/font/google"
output: "standalone"The build output ships a standalone server directory with a trimmed node_modules; ideal for Docker imagesoutput: "standalone"
output: "export"Pure static export; only for fully static sites; any Route Handler, ISR, or dynamic route will failoutput: "export"
process.env.NEXT_PUBLIC_API_URLVariables with the NEXT_PUBLIC_ prefix are inlined into the client bundle; changes require a rebuild and are exposed to everyoneprocess.env.NEXT_PUBLIC_API_URL
next build / next startBuild and start in production mode; dev and production differ in caching and routing behavior, so diagnosing production issues requires next startnext build

Common pitfalls

SymptomCalling useRouter / useState in a Server Component throws "useState only works in a Client Component".

CauseApp Router defaults to Server Components; only files with "use client" and their import chain have access to these Hooks.

FixExtract the minimal interactive leaf component into its own file with "use client" at the top; do not add it to the whole layout, or the entire subtree enters the bundle.

SymptomThe first-paint HTML has empty data, and the real content flashes in only after the page loads (hydration mismatch warning).

CauseThe server render used a cached value while the client used a live value, so the two sides disagree; common when reading localStorage or a random number directly.

FixSet the inconsistent part into state inside useEffect, or mark that component "use client" and keep the server and client output identical.

SymptomEven with fetch caching enabled, the API request count still equals the visit count.

Causefetch caching only applies to server rendering and static generation; dynamic APIs like cookies(), headers(), or searchParams in the request turn the whole segment dynamic and invalidate all caching.

FixMove data fetching into a separate static subsegment (no dynamic APIs) and consume it in the page via Suspense + a child component; or verify dynamic is not set to force-dynamic.

SymptomUsing useSearchParams() directly in page.tsx makes the whole page dynamic at build time and throws a missing Suspense boundary error.

CausesearchParams is a dynamic API; reading it opts that route segment out of static rendering, and without a Suspense wrapper Next cannot determine the streaming boundary.

FixMove the searchParams reading logic into a dedicated client child component and wrap it in <Suspense>.

SymptomNextResponse.redirect(new URL("https://example.com/login", request.url)) sends users to the production domain in the staging environment.

CauseThe redirect URL hard-codes an absolute domain and ignores two things: the request origin may differ from the deployment domain (e.g. a staging subdomain).

FixAlways construct it relatively with new URL("/login", request.url) so Next derives the origin from the current request.

SymptomAn API key is written into a variable a client component can read, and the key shows up in the browser Network tab.

CauseVariables with the NEXT_PUBLIC_ prefix, and modules imported into client components without the prefix, are both inlined into the bundle.

FixKeep keys server-only: guard the module with server-only and put the call in a Route Handler or Server Action.

SymptomAfter export const runtime = "edge", the build reports that some Node library is unavailable under the Edge Runtime.

CauseThe Edge Runtime only has Web APIs; it lacks fs, net, and some native npm modules.

FixSwitch back to the default Node runtime; if Edge is truly needed, keep heavy dependencies on the server and call them through a Route Handler.

Symptomnext build passes and next dev works, but the page 404s after next start.

CauseThe file sits at the wrong directory level (e.g. under pages/ instead of app/, or the app directory is neither at the root nor under src); the routing differences between dev and build mask the problem.

FixVerify the directory location and file name; use the route table from next build to confirm the path was registered.

SymptomAfter deploying to a CDN the page keeps serving stale data, and revalidate seems to have no effect.

CauseCDN cache headers take precedence over Next ISR, or with output: "export" there is no ISR at all and revalidate is ignored.

FixCheck the Cache-Control response header and CDN rules; in static-export scenarios switch to build-time data fetching or revert to a standard deployment.

Troubleshooting

  1. 1Reproduce production issues with production mode

    next build && next start

    next dev does not do full optimization and behaves differently in routing; diagnosing caching and build errors requires next start.

  2. 2Inspect the routes and render modes in the build output

    next build

    Each route in the output is labeled Static / Dynamic / ISR along with size and first-load JS.

  3. 3Confirm whether a client variable value made it into the bundle

    grep -r "NEXT_PUBLIC" .next/static

    Finding the plaintext inside .next/static means the variable was inlined into the client output.

  4. 4Check whether static export is viable

    grep -r "export const dynamic" app

    Any Route Handler, ISR, or force-dynamic segment will make the output: "export" build fail.

  5. 5Check which paths the middleware actually intercepts

    grep -n "matcher" middleware.ts

    An overly broad matcher makes every request go through the middleware, a common source of performance problems.

  6. 6Verify that convention files are recognized by the router

    ls -R app

    Confirm the file names and levels of page.tsx / layout.tsx; typos (like page.ts) do not error, they just produce no route.

Command Examples

A Route Handler serving GET

// app/api/items/route.ts import { NextResponse } from "next/server"; export async function GET() { const res = await fetch("https://api.example.com/items", { cache: "no-store" }); return NextResponse.json(await res.json()); }

The exported function name is the HTTP method, and the file path supplies the URL prefix. no-store forces a real upstream fetch on every request; without it the result is served from a stale cache. Return NextResponse.json(...) rather than a bare object so you keep control over status and headers.

ISR: revalidate in the background after 60 seconds

// app/products/[id]/page.tsx export const revalidate = 60; export async function generateStaticParams() { const ids = await getIds(); return ids.map((id) => ({ id: String(id) })); }

revalidate = 60 marks the page stale after a minute and regenerates it in the background on the next visit: the first request still serves old content but returns instantly, preserving both SEO and first-paint speed. generateStaticParams decides which dynamic paths are pre-rendered at build time; omit it and those pages are only generated on first hit.

Middleware gating unauthenticated requests

// middleware.ts import { NextResponse } from "next/server"; export function middleware(request) { const token = request.cookies.get("token"); if (!token) { return NextResponse.redirect(new URL("/login", request.url)); } return NextResponse.next(); }

Middleware runs before a request reaches its route, making it the earliest place to redirect on auth failure. Note that NextResponse.redirect(new URL(..., request.url)) must build on the full request.url to resolve a correct absolute location; a bare "/login" throws.

Client component using navigation hooks

"use client"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; export function Nav() { const router = useRouter(); const pathname = usePathname(); const params = useSearchParams(); return <button onClick={() => router.push("/about")}>{pathname}</button>; }

The three hooks from next/navigation are client-only; using them in a Server Component is a hard error. useSearchParams pulls the component across the client rendering boundary, so a statically exported page that reads it needs a Suspense wrapper.

Static metadata and dynamic generateMetadata

export const metadata = { title: "Page title", description: "Page description", robots: { index: true, follow: true }, }; export async function generateMetadata({ params }) { const post = await getPost(params.slug); return { title: post.title, description: post.excerpt }; }

Static metadata fits fixed pages; dynamic routes need generateMetadata receiving params to fill in title and description, otherwise every detail page shares one title, which is an outright SEO defect. Do not define both in one file: generateMetadata takes precedence over the static export.

Common Pitfalls

  • "use client" is file-scoped: every module imported by that file is bundled for the browser. Importing a server-only helper for its database or key handling ships that code and its dependencies to the client.
  • Mixing build-time constant lookups on process.env and import.meta.env across files lets the bundler replace those accesses with literals, so client and server disagree and hydration mismatches appear as the page jumping on first paint. Read environment values the conventional Next.js way, on the server.
  • Relying on directory conventions instead of the server-only marker means one wrong import path can pull key-handling logic into the client bundle. Put server-only at the top of the module so the bundler fails loudly.
  • Guessing at fetch caching is expensive: the default may serve stale data, while no-store sends every request upstream and hits both latency and billing. Declare cache: "no-store" or a revalidate interval explicitly for frequently changing data.
  • Heavy work in middleware.ts (database queries, third-party calls) slows every request that passes through, and the Edge Runtime does not support all Node APIs, so failures surface as errors inside someone else library. Keep middleware light and push the real work into a route or a Server Action.
  • With output: "export" anything depending on the server runtime either fails the build or comes back empty: Route Handlers, middleware, server-side fetching, and on-demand generation of dynamic routes. Confirm the page really can be produced statically before choosing static export.

Tips

  • App Router is the default routing solution in Next.js 13+, replacing Pages Router
  • Page components are Server Components by default, add "use client" for client-side interactivity
  • Use next/link Link component instead of a tags for automatic prefetching and client-side navigation
  • Fetch data directly in Server Components with async/await, no useEffect needed
  • Use generateStaticParams for static generation of dynamic routes

FAQ

Where do I put "use client" when a page throws a client-related error?

Under the App Router, app/page.tsx is a Server Component by default; add "use client" only when the component needs browser APIs, event handlers or state, and understand that it converts that file plus its whole import tree into client code. The heuristic: if the component uses onClick, useState or useEffect, mark the smallest possible layer — usually a leaf component — and work upward, rather than slapping the directive on the top-level page. Server Components should not fetch inside useEffect; just async/await fetch directly in the component. The opposite tool is server-only, which guarantees a module never leaks into the client bundle, keeping secrets safe.

How do I choose between static generation, ISR and dynamic rendering?

Three routes for three kinds of data. fetch(url, { cache: "force-cache" }) is the default static path — HTML is produced at build time, right for content that rarely changes. fetch(url, { next: { revalidate: 60 } }) is ISR: the page is revalidated in the background after 60 seconds, balancing speed against freshness. fetch(url, { cache: "no-store" }) is dynamic, refetching on every request for personalised or live data. At the page level you can pin the policy with export const dynamic = "force-static" or "force-dynamic", or set an ISR interval for the whole page with export const revalidate = 3600. Dynamic routes additionally need generateStaticParams() to enumerate the paths to pre-render.

Why prefer a Server Action over a Route Handler for form submissions?

Because a Server Action is a first-class capability inside your component tree: putting "use server" at the top of the function marks it as a server action, and you can pass it straight to a client form's action prop, skipping hand-written fetch calls and state bookkeeping. A Route Handler — an async function GET(request) exported from app/api/route.ts — is for external consumers: other programs calling you over defined HTTP semantics, or endpoints needing custom methods and response shapes. On the security side, server-only keeps secrets server-side, and sessions should be read with cookies().get("token") on the server rather than round-tripping a token through the client.

When does logic belong in middleware.ts rather than in each page?

middleware.ts runs before a request reaches any route, which makes it the right home for cross-cutting rules applied uniformly: auth checks, redirects, header rewrites. NextResponse.redirect(new URL("/login", request.url)) sends unauthenticated users away, and NextResponse.next() lets the request continue. Doing the same check page by page is what invites bugs — every new route is a chance to forget, and the logic ends up scattered. Middleware has the request object, so path and cookie decisions are easy; keep it cheap, though, since it runs on every request. Anything expensive belongs in a page or a Route Handler, not here.

How do static export and standalone output differ in next.config?

Pick based on the deployment target. output: "export" emits pure HTML/CSS/JS that any static host can serve, but the cost is that server-side capabilities — Route Handlers, Server Actions, ISR revalidation — stop working, so pages must be resolvable at build time. When you need a Node runtime and want a self-contained artifact, use output: "standalone", which bundles the required node_modules slice and fits Docker images neatly. Validate production behaviour locally with next build followed by next start serving the .next directory, never next dev. For environment-specific URLs use process.env.NEXT_PUBLIC_API_URL, remembering that NEXT_PUBLIC_-prefixed values get inlined into the client bundle.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us