Next.js
Next.js 16 Cheat Sheet: 75 App Router Patterns for Production
A practical Next.js 16 cheat sheet with 75 commands, file conventions, Server Component patterns, cache APIs, SEO fields, security checks, and deploy tips.
Use this as a build-side reference for Next.js 16 App Router projects. Each entry answers one narrow question, with copyable code where syntax matters.
Project setup and commands
Start, inspect, and upgrade a Next.js 16 project with repeatable commands. The examples use npm, but the framework concepts are independent of the package manager.
- 1
Create a new App Router project
create-next-app configures Next.js, React, TypeScript, and the selected tooling. Commit package-lock.json with the project.
npx create-next-app@latest my-app --ts --app --src-dir - 2
Use the current Node.js baseline
Next.js 16 requires Node.js 20.9 or newer. Pin a supported version in local development and CI so builds do not depend on a runner's changing default.
- 3
Know the four normal scripts
next dev starts development, next build creates the production output, next start serves that output, and your configured linter runs separately.
"scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "eslint ." } - 4
Install reproducibly in CI
Use npm ci when package-lock.json is committed. It rejects lockfile drift and installs the resolved dependency tree without rewriting the lockfile.
npm ci && npm run build - 5
Use Turbopack by default
In Next.js 16, both next dev and next build use Turbopack by default. Remove old --turbo flags unless a script needs to support another version.
- 6
Temporarily keep a webpack build
A project with custom webpack configuration can explicitly opt out while that configuration is migrated. Test both output and performance before switching the production builder.
next build --webpack - 7
Keep application code under src
src/app and src/lib separate application code from root configuration. The public directory and files such as next.config.ts remain at the project root.
- 8
Use an import alias
A stable alias avoids long relative paths when code moves between route folders. Keep the alias in tsconfig.json or jsconfig.json.
import { getUser } from "@/lib/data";
Pages, layouts, and routing
The App Router is a file-system router. These conventions control URLs, shared UI, dynamic parameters, and advanced navigation behavior.
- 9
Create a page
A page.tsx file makes its folder's route publicly accessible. The default export is the UI for that URL.
export default function Page() { return <h1>About</h1>; } - 10
Create a required root layout
app/layout.tsx wraps every route and must render html and body. Put global CSS, language, site-wide providers, and metadata defaults here.
- 11
Nest layouts by route segment
A layout inside app/dashboard wraps the dashboard page and all descendants. Layouts persist across client navigation within their subtree.
- 12
Group routes without changing the URL
Parentheses create a route group. app/(marketing)/pricing/page.tsx still resolves to /pricing.
app/(marketing)/pricing/page.tsx - 13
Create a dynamic segment
Square brackets capture one URL segment. In Next.js 16, await params before reading the value.
export default async function Page({ params }) { const { slug } = await params; return <h1>{slug}</h1>; } - 14
Capture several segments
[...parts] is a required catch-all and [[...parts]] is optional. Use them for hierarchical docs or other paths whose depth is data-driven.
- 15
Prebuild known dynamic paths
generateStaticParams returns parameter objects for routes Next.js should generate at build time.
export function generateStaticParams() { return posts.map((post) => ({ slug: post.slug })); } - 16
Reject unknown dynamic paths
Set dynamicParams to false when only values returned by generateStaticParams should resolve. Other parameter values return 404.
export const dynamicParams = false; - 17
Render a missing resource
Call notFound when the route exists but its requested entity does not. Next.js renders the nearest not-found.tsx boundary.
if (!post) notFound(); - 18
Reset state with a template
template.tsx looks like a layout but remounts on navigation. Use it when state and effects should reset; keep persistent shells in layouts.
- 19
Render simultaneous route slots
Folders such as @team and @analytics define parallel route slots passed to a parent layout. Supply default.tsx fallbacks for unmatched slots on a hard navigation.
- 20
Open a route as a modal
Intercepting routes can show another route inside the current layout during soft navigation while preserving its full-page result for refreshes and shared links.
Server and Client Components
Server Components are the App Router default. Add browser JavaScript only at components that need interaction or browser-only APIs.
- 21
Keep pages server-side by default
A Server Component can await data, use private environment variables, and render without sending its component code to the browser.
- 22
Declare a Client Component
Place the directive before imports. The client boundary includes that module and the client-side dependency graph it imports.
"use client"; import { useState } from "react"; - 23
Use clients for interaction
State, effects, event handlers, custom hooks, window, localStorage, and other browser APIs belong in Client Components.
- 24
Use servers for private work
Database queries, service credentials, large server-only libraries, and most content rendering belong in Server Components or server-only modules.
- 25
Pass serializable props to clients
Strings, numbers, booleans, arrays, plain objects, and supported React values can cross the boundary. Shape data narrowly and do not pass full private records.
- 26
Mark private modules server-only
The side-effect import makes a client import fail at build time, which protects data-access code from accidental browser use.
import "server-only"; - 27
Move the client boundary downward
Keep a server-rendered page and layout, then isolate a search box, menu, picker, or chart as the smallest practical Client Component.
- 28
Pass server UI through client children
A Client Component can receive a Server Component as a child or another prop. This keeps the server-rendered subtree out of the client's own module graph.
- 29
Add providers as deep as possible
Context is unavailable in Server Components. Render a focused Client Component provider around only the subtree that consumes it, not the whole document by default.
Fetching, streaming, and caching
Choose freshness intentionally. Next.js 16 Cache Components are opt-in; the cheat sheet labels APIs that require that configuration.
- 30
Fetch in an async Server Component
Call an API, ORM, or database from the component that renders the result. You do not need an internal Route Handler only to call your own server.
export default async function Page() { const products = await getProducts(); return <ProductList products={products} />; } - 31
Start independent work together
Promise.all prevents an avoidable request waterfall when neither operation needs the other's result.
const [user, projects] = await Promise.all([ getUser(), getProjects(), ]); - 32
Stream with a route loading file
loading.tsx wraps the segment in a Suspense boundary and provides immediate fallback UI during navigation and request-time rendering.
- 33
Stream one slow region
Place Suspense around the slow component so the rest of the page can render first. Make the fallback resemble the final dimensions.
<Suspense fallback={<ActivitySkeleton />}> <RecentActivity /> </Suspense> - 34
Memoize one render pass
React cache can deduplicate the same server-side data function and arguments during a render. It is not a persistent application data cache.
import { cache } from "react"; export const getUser = cache(async (id) => db.user.findUnique({ where: { id } })); - 35
Enable Cache Components
Next.js 16's use cache APIs require the cacheComponents setting. Migrate intentionally because this changes rendering and caching behavior.
const nextConfig = { cacheComponents: true }; export default nextConfig; - 36
Cache an async function
With Cache Components enabled, place use cache at the top of an async function or component body. Serializable arguments become part of the cache key.
export async function getProducts() { "use cache"; return db.product.findMany(); } - 37
Set a cache lifetime
cacheLife accepts a named profile or custom timing values. Pick a lifetime from the content's acceptable staleness, not from convenience.
"use cache"; cacheLife("hours"); - 38
Tag related cached work
cacheTag gives several entries a shared invalidation label, such as products or post-42.
"use cache"; cacheTag("products"); - 39
Expire a path
Call revalidatePath from a Server Function or Route Handler after a change makes a page or layout stale.
revalidatePath("/blog"); - 40
Revalidate by tag
Use revalidateTag when tagged content may use stale-while-revalidate behavior. Select the cache profile that matches the freshness requirement.
- 41
Read your own write with updateTag
Call updateTag inside a Server Action when the action's user must see fresh tagged data immediately after the mutation.
await savePost(input); updateTag("posts");
Forms, mutations, and Route Handlers
Writes need validation, authorization, predictable errors, and an explicit cache update. HTTP interfaces also need normal endpoint security.
- 42
Declare a Server Action
Put use server at the top of an async function or action module. Exported Server Actions must be treated as remotely callable mutation endpoints.
"use server"; export async function createPost(formData: FormData) { // validate, authorize, mutate, invalidate } - 43
Bind an action to a form
A form can call a Server Action without a custom client submit handler. The browser's form behavior also gives you a progressive-enhancement path.
<form action={createPost}>...</form> - 44
Validate FormData on the server
Treat names, IDs, files, hidden inputs, and client-side validation as untrusted. Parse into an explicit schema before writing.
- 45
Return expected form errors
Use a serializable result and useActionState for validation or business errors the user can correct. Do not throw for every expected outcome.
- 46
Show pending form state
useFormStatus reads the status of the parent form submission. Disable repeat submits and give the button an honest pending label.
- 47
Create a Route Handler
route.ts exports HTTP verb functions and uses the Web Request and Response APIs. A route.ts file cannot share the same segment as page.tsx.
export async function GET() { return Response.json({ status: "ok" }); } - 48
Read a dynamic Route Handler parameter
Route context params are asynchronous in current Next.js. Await them before querying the resource.
export async function GET(request, { params }) { const { id } = await params; return Response.json(await getItem(id)); } - 49
Redirect after a mutation
Use redirect for a temporary navigation after a successful create or update, and permanentRedirect only when the resource has a lasting new canonical URL.
redirect("/dashboard"); - 50
Keep retries safe
Webhooks and network requests can arrive more than once. Store provider event IDs or idempotency keys before repeating payments, emails, or other side effects.
SEO, images, fonts, and scripts
Next.js can generate head tags and crawler files from route code. The visible page still needs specific, useful content and semantic structure.
- 58
Set static metadata
Export metadata from a Server Component layout or page when the values do not depend on route data.
export const metadata = { title: "Pricing", description: "Simple plans for growing teams.", }; - 59
Generate dynamic metadata
Use generateMetadata for entity-specific titles, descriptions, canonical URLs, and social images. Reuse the route's data access function where possible.
- 60
Set metadataBase once
A root metadataBase lets canonical links and image fields use relative paths while Next.js resolves absolute URLs.
metadataBase: new URL("https://example.com") - 61
Generate a sitemap
app/sitemap.ts returns public URLs and optional lastModified, changeFrequency, and priority fields. Build it from the real content catalog; Google ignores changeFrequency and priority, so keep lastModified accurate instead of manufacturing freshness.
- 62
Publish robots rules
app/robots.ts returns crawl rules and the sitemap location. Robots rules are crawl hints, not access control for private routes.
- 63
Use optimized images
next/image needs intrinsic dimensions or a fill container. Supply accurate alt text and responsive sizes; use priority only for genuinely critical above-the-fold images.
- 64
Load fonts with next/font
next/font self-hosts the selected font files and reduces external requests. Limit weights and subsets to the styles the interface actually uses.
- 65
Schedule third-party scripts
next/script controls when external JavaScript loads. Choose afterInteractive or lazyOnload unless the integration truly belongs on the critical path.
Security and configuration
Framework boundaries reduce accidental exposure only when application code keeps authorization and secret handling explicit.
- 66
Keep secrets server-side
Environment values are server-only unless their name starts with NEXT_PUBLIC_. Anything public-prefixed should be treated as browser-visible and fixed according to the build environment.
- 67
Authorize every server entry point
Check the current identity and its permission in Server Actions, Route Handlers, and protected data access. A hidden button or Proxy redirect is not a security boundary.
- 68
Use Proxy for request-bound routing logic
Next.js 16 uses proxy.ts for rewrites, redirects, and optimistic checks before a request reaches a route. Keep secure authorization near the data too.
- 69
Use secure session cookies
Set HTTP-only, secure, same-site, path, and expiry attributes to match the session design. Rotate and invalidate sessions through the authentication system.
- 70
Send narrow data to the browser
Convert records into DTOs that contain only fields the interface needs. Taint APIs can add defense in depth, but do not replace careful output shaping.
Production, debugging, and deployment
The last five checks turn framework code into an operable website with repeatable builds and observable releases.
- 71
Run checks separately
Next.js 16 does not rely on next build to run linting. Make lint, TypeScript, tests, and the production build separate CI steps.
npm run lint npx tsc --noEmit npm test npm run build - 72
Test the production server
Run next build and next start locally or in a preview. Development behavior can hide production-only import, cache, environment, and rendering problems.
- 73
Read the route build summary
The next build output identifies prerendered and request-rendered routes. Investigate a route that becomes dynamic or grows unexpectedly instead of treating the build as a pass/fail box.
- 74
Expose a truthful health route
Return a small successful response only when the process is ready for traffic. Keep secrets and detailed dependency information out of the public body.
export function GET() { return Response.json({ status: "ok" }); } - 75
Deploy the normal production process
A standard server deployment installs from the lockfile, runs next build, starts with next start, injects runtime secrets, checks health, and promotes only a healthy release.
build_cmd: npm ci && npm run build start_cmd: npm start runtime: name: node@24 port: 3000 health_path: /api/health