Next.js Rendering Best Practices
A condensed summary of the 25 most important best practices drawn from every page in this section.
Search across all documentation pages
A condensed summary of the 25 most important best practices drawn from every page in this section.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
"use client", so reach for Server Components first and only add the directive when you actually need hooks, events, or browser APIs.async and call await fetch(...) or await db.query(...) in the function body - skip useEffect loading states, since hooks are not allowed on the server anyway.import "server-only" at the top of modules that read AUTH_SECRET, database URLs, or signing keys makes the build fail the moment a Client Component imports them, preventing leakage into the browser bundle."use client" pulls every child and every imported utility into the client bundle; extract only the interactive leaf (like a LikeButton) into the Client Component and keep its parents server-rendered.window, document, localStorage, and IntersectionObserver do not exist during SSR, so access them inside useEffect (or behind a typeof window !== "undefined" check) and initialize state to a server-safe value first.Date.now(), Math.random(), or read window during render - the server HTML and client re-render will disagree; move varying values into useEffect, or use suppressHydrationWarning for deliberate cases.import a Server Component (the import silently becomes client-only), but it can receive one through children or named JSX props - orchestrate the composition from a Server Component parent.app/providers.tsx with "use client" and render <Providers>{children}</Providers> from the Server Component layout.LikeButton) into its own small "use client" file.cookies(), headers(), searchParams, connection(), any cache: "no-store" fetch, or export const dynamic = "force-dynamic" opt the whole route into dynamic rendering - a single use in any component is enough.generateStaticParams, and leave dynamicParams = true (the default) so new paths are rendered on first request and then cached.export const revalidate = 0 is equivalent to force-dynamic, not "revalidate immediately" - use a positive integer (e.g., revalidate = 60) for ISR and reserve 0 or "no-store" for truly per-request data.export const dynamic = "force-static" makes the build fail if the page calls cookies()/headers()/searchParams; either remove the dynamic call or drop back to dynamic = "auto".experimental: { ppr: "incremental" } in next.config.ts and opt routes in one at a time with export const experimental_ppr = true so you can ship PPR progressively and verify each route behaves.<Suspense> pulls the entire PPR route back into full dynamic rendering.<Image fill> without a sizes prop makes the browser request the largest variant on every device; supply a breakpoint-aware string like "(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" that matches the layout.images.remotePatterns in next.config.ts; without it, remote images throw a build/runtime error and next/image refuses to optimize them.priority only to the one or two above-the-fold LCP images - it disables lazy loading and injects a preload hint, so overusing it slows the page instead of speeding it up.width and height props on next/image lock in an aspect ratio for CLS prevention; use CSS (className, wrapper sizing) to control what the image actually renders at.next/font/google and next/font/local download and serve fonts from your own origin with immutable cache headers, eliminating external requests, FOUT, and CLS via auto-generated fallback metrics.inter.className (or inter.variable) to <html>, <body>, or the relevant container; a forgotten class is the most common "my font isn't loading" bug.weight: ["400", "700"] for a font that has a variable build forces Next.js to download multiple static files instead of one variable file; drop weight when the font supports it to keep the bundle small.display: "swap" (the recommended default) so a fallback font shows immediately and is replaced when the custom font loads; display: "optional" can leave text invisible if the font does not arrive within ~100ms.Reviewed by Chris St. John·Last updated Jul 19, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥