Next.js Best Practices
Knowing every feature does not guarantee a healthy project. Teams ship faster and sleep better when they follow habits that keep code simple, pages quick, and data safe. The practices below come from patterns that appear again and again in well-built Next.js applications. Treat them as a checklist for reviews and new features.
The Everyday View
A tidy kitchen helps a cook work faster. Knives hang in one place, spices sit in labeled jars, and the stove stays clean. The cook could still make dinner in a messy kitchen, but every task takes longer and mistakes creep in. Best practices keep your code kitchen tidy.
Practice One: Keep Components on the Server by Default
Server Components send no JavaScript to the browser for their own logic. Pages load faster and phones with slow connections cope better. Add 'use client' only when a component needs state, effects, event handlers, or browser-only features.
Push Client Boundaries Down
Mark the smallest possible piece as a Client Component. A product page does not need to become a client page because it holds one like button.
BETTER WORSE ProductPage (server) ProductPage (client) |- ProductInfo (server) |- ProductInfo (client) |- Reviews (server) |- Reviews (client) |- LikeButton (client) |- LikeButton (client)
The left tree ships JavaScript for one button. The right tree ships JavaScript for everything.
Practice Two: Fetch Data Where You Use It
Async Server Components can request their own data. Placing the fetch beside the markup removes the need to pass data through many layers. Next.js removes duplicate identical requests during a single render, so repeated calls in sibling components do not multiply the load.
Start independent requests together with Promise.all so they run in parallel.
const [user, orders] = await Promise.all([
getUser(id),
getOrders(id),
]);Practice Three: Read From the Source Directly
A Server Component can query the database or call a shared helper function. Calling your own Route Handler over the network adds delay and extra code. Reserve Route Handlers for outside callers.
Practice Four: Validate All Input
Treat every value from a form, query string, cookie, or request body as untrusted. Check type, length, and format on the server. Return clear error messages for visitors and log details for developers.
Practice Five: Protect Secrets
- Store keys in environment variables, never in source files.
- Use the
NEXT_PUBLIC_prefix only for values safe to expose. - Keep database calls in server-only files.
- Add
.envfiles to.gitignore.
The server-only package makes the build fail when a client file imports a server-only module. That safety net catches accidents early.
import 'server-only';
export async function getSecretReport() {
// database access here
}Practice Six: Use the Built-In Components
| Need | Built-In Tool | Benefit |
|---|---|---|
| Pictures | next/image | Resizing, modern formats, lazy loading |
| Fonts | next/font | No layout shift, self-hosted files |
| Links | next/link | Prefetching and fast navigation |
| Page details | Metadata API | Clean titles and descriptions |
Practice Seven: Plan for Loading and Failure
Slow networks and broken services happen in real life. Add loading.js or Suspense fallbacks so visitors see progress. Add error.js files so a failure in one section does not blank the whole site. Add a not-found.js file with helpful links so lost visitors find their way.
Practice Eight: Mind Caching Rules
Caching defaults have changed between Next.js versions. Read the documentation for the version you run. Mark pages that show personal data as dynamic, and set sensible revalidation times for content that changes on a schedule. Test with a production build, because development mode behaves differently.
Practice Nine: Write Accessible Pages
- Use real headings in logical order.
- Add alt text to meaningful images.
- Label every form field.
- Check color contrast.
- Make every action reachable by keyboard.
Accessible pages serve more visitors and often rank better in search results.
Practice Ten: Keep the Project Organized
src/ app/ routes and layouts components/ reusable interface pieces lib/ helpers, database code, validators types/ shared type definitions public/ images and static files
Pick a structure early and share it with the team. Consistent folders reduce time spent hunting for files.
Practice Eleven: Measure Before Optimizing
Run Lighthouse or the browser performance tools on a production build. Fix the largest problems first. A bundle analyzer shows which packages weigh the most. Guessing wastes time, and numbers point to real bottlenecks.
Review Checklist
| Question | Healthy Answer |
|---|---|
| Does the component need to be a Client Component? | Only when it uses state, effects, or events |
| Does the server validate input? | Yes, on every entry point |
| Are secrets kept out of the browser? | Yes |
| Do slow sections show a fallback? | Yes |
| Do images use the Image component? | Yes |
| Do tests cover the key journeys? | Yes |
Common Mistakes to Avoid
Marking the Root Layout as Client
This move turns the whole tree into client code and removes most Server Component benefits.
Fetching in useEffect for Initial Data
Initial page data loads faster on the server. Reserve effects for browser-only needs.
Ignoring Build Warnings
Warnings often point to future failures. Fix them while they are small.
Quick Summary
Healthy Next.js projects keep components on the server, fetch data close to use, validate every input, guard secrets, use built-in tools, and plan for slow or broken moments. Review these habits regularly, and the codebase stays easy to change.
