Next.js Best Practices for Production Applications
The defaults get you running. These practices keep you sane as the application grows.
Next.js ships with sensible defaults, but production applications quickly run into decisions the framework leaves to you. Here are the ones worth getting right early.
On rendering strategy: use Server Components by default and add "use client" only when you need interactivity, browser APIs, or event handlers. The common mistake is adding "use client" at the top of a component tree, which opts the entire subtree out of server rendering. Push client boundaries as far down the component tree as possible.
On data fetching: colocate data fetching with the component that needs the data using async Server Components. Avoid prop-drilling data down from a root layout — this creates coupling and forces re-renders when unrelated state changes. Use React's cache() for request deduplication when multiple components need the same data.
On routing: use the App Router's layout nesting to share UI across routes without re-mounting — the layout component persists between navigations within its segment. Route Groups (folders with parentheses) let you organize routes without affecting the URL structure.
On image optimization: always use Next.js's Image component rather than native img. It handles lazy loading, format selection (WebP/AVIF), and responsive sizing automatically. Set explicit width and height to prevent layout shift, or use fill with a positioned container.
On environment variables: prefix browser-visible variables with NEXT_PUBLIC_. Never expose secrets in NEXT_PUBLIC_ variables — they're embedded in the JavaScript bundle and visible to anyone who inspects it.
On error handling: add error.tsx files to route segments to catch rendering errors gracefully. Add loading.tsx for streaming skeletons. These two files together give users a coherent experience when things go wrong or are slow.