---
title: "Next.js Best Practices"
slug: "nextjs"
category: "frontend"
tags: ["frontend", "nextjs", "react"]
status: "stable"
last_updated: 2026-08-14
summary: "Next.js 16 App Router: server components, server actions, Cache Components, metadata, route segments, and runtime selection."
related: ["[[frontend/react]]", "[[frontend/shadcn]]", "[[frontend/tailwind]]", "[[ops/vercel]]", "[[seo/technical]]"]
---

## Overview

Next.js 16 is [[frontend/nextjs-app-router|App Router]] by default, with a stable Turbopack build and Cache Components as the caching model. Server components do the heavy lifting, [[frontend/nextjs-server-actions|server actions]] replace most ad-hoc API routes, and the [[frontend/nextjs-caching|caching model]] is opt-in instead of opt-out. This page covers the conventions that hold for a Next.js 16 app on React 19.

## Server components by default, client islands at the edges

A file in `app/` is a server component unless its first line is `"use client"`. Server components render on the server, fetch data directly, ship no JS to the browser, and cannot use hooks or event handlers.

Push `"use client"` as far down the tree as possible. A page can be a server component that renders a server-rendered list and a small client island for the filter dropdown. Wrapping the whole page in `"use client"` defeats the model. See [[frontend/react-server-components]] for the serialization rules, [[frontend/react]] for the broader rule on the boundary, and [[comparisons/server-vs-client-components]] for the head-to-head selection rules.

## Mutate with server actions, not ad-hoc routes

Server actions are functions marked `"use server"` that the framework wires up as RPC endpoints. They are the default for any mutation a form submits (see [[frontend/react-forms]] for the FormData and Zod patterns).

```tsx
// app/notes/actions.ts
"use server";
import { revalidatePath } from "next/cache";

export async function createNote(formData: FormData) {
  const title = String(formData.get("title"));
  await db.note.create({ data: { title } });
  revalidatePath("/notes");
}

// app/notes/new/page.tsx
import { createNote } from "../actions";
export default function NewNote() {
  return (
    <form action={createNote}>
      <input name="title" />
      <button type="submit">Save</button>
    </form>
  );
}
```

Reach for a [[frontend/nextjs-route-handlers|`route.ts` handler]] only for true HTTP endpoints (webhooks, public APIs, file uploads with custom headers).

## Cache deliberately with Cache Components

Next.js 16 ships Cache Components (`cacheComponents: true` in `next.config.ts`), which flips the default from cached-unless-opted-out to dynamic-unless-opted-in. Every fetch, page, and function runs on every request unless you mark it cacheable with `"use cache"`. This replaces `unstable_cache` and the old default-cached `fetch` behavior.

```ts
// app/notes/page.tsx
async function getNotes() {
  "use cache";
  cacheLife("minutes");
  cacheTag("notes");
  return db.note.findMany();
}
```

`use cache`, `cacheLife`, and `cacheTag` are stable in Next.js 16 (the `unstable_` prefix is gone). Projects that have not opted into `cacheComponents` keep the Next.js 15 fetch-caching model described in [[frontend/nextjs-caching]]; new projects should adopt Cache Components. Invalidate from server actions with `updateTag` (or the legacy `revalidateTag`/`revalidatePath` on pre-Cache-Components projects):

```ts
updateTag("notes");        // Cache Components: immediate, read-your-writes
revalidateTag("notes", "minutes"); // eventual; needs a cacheLife profile in Next.js 16
revalidatePath("/notes");
```

Tags are usually right for collections; paths are right for one-off pages. Pick one strategy per resource and document it.

## Drive metadata from the route

Export `metadata` or `generateMetadata` from a page or layout. Do not write `<head>` tags by hand in App Router.

```tsx
// app/posts/[slug]/page.tsx
import type { Metadata } from "next";

export async function generateMetadata(
  { params }: { params: Promise<{ slug: string }> },
): Promise<Metadata> {
  const { slug } = await params;       // params is a Promise; sync access was removed in Next.js 16.
  const post = await getPost(slug);
  return {
    title: post.title,
    description: post.summary,
    alternates: { canonical: `/posts/${post.slug}` },
    openGraph: { title: post.title, description: post.summary, images: [post.cover] },
  };
}
```

`params` and `searchParams` are Promises that must be awaited; Next.js 16 removed the synchronous fallback entirely, where Next.js 15 only warned. See [[frontend/nextjs-async-dynamic-apis]] for the full breaking change and the affected APIs. Layouts contribute defaults; child segments override. Open Graph and Twitter Card data are first-class. For structured data, render JSON-LD in the page body. See [[seo/technical]] and [[seo/structured-data]].

## Use route segments and layouts for shared shells

A `layout.tsx` wraps every page below it. Use layouts for the chrome that does not change between routes: the nav, the auth check, the providers. Use `loading.tsx` and `error.tsx` next to a route for the [[frontend/react-suspense|Suspense fallback]] and the [[frontend/react-error-boundaries|error boundary]].

Group routes without changing the URL using `(group)` folders. Use `@slot` parallel routes for sidebars and modals that live next to the main content. Use `[param]` for dynamic segments and `[...slug]` for catch-alls.

## Pick the runtime per route

Each route segment can run on `nodejs` (default) or `edge`.

- Node runtime: full Node APIs, Prisma, native modules, longer cold start.
- Edge runtime: Web APIs only, fast cold start, regional and global execution, no native modules.

```ts
export const runtime = "edge"; // or "nodejs"
```

Pick `edge` for short, network-bound work that benefits from low latency (auth checks, redirects, A/B routing). Pick `nodejs` for anything that touches Prisma, the filesystem, or a Node-only library. Mixing is fine; choose per segment.

## Related

- [[frontend/react]]
- [[frontend/shadcn]]
- [[frontend/tailwind]]
- [[ops/vercel]]
- [[seo/technical]]
