Next.js & Web
Next.js App Router Conventions
Use when creating or restructuring anything under a Next.js app/ directory: adding a route, page, or layout, deciding whether a component is a Server or Client Component, adding "use client", writing a route handler, setting metadata, or moving data fetching. Also use when a build complains that a hook or event handler cannot be used in a Server Component, or when data is fetched in the wrong place. If a file lands under app/, use this skill.
1 file
Description
Use when creating or restructuring anything under a Next.js app/ directory: adding a route, page, or layout, deciding whether a component is a Server or Client Component, adding "use client", writing a route handler, setting metadata, or moving data fetching. Also use when a build complains that a hook or event handler cannot be used in a Server Component, or when data is fetched in the wrong place. If a file lands under app/, use this skill.
name: nextjs-app-router-conventions description: Use when building or editing pages in a Next.js App Router project (app/ directory). Covers server vs client components, metadata, generateStaticParams, and data fetching so routes render correctly and stay indexable.
Next.js App Router Conventions
When to trigger
Any work under an app/ directory: adding a route, splitting a component into client and server halves, wiring metadata, or debugging why a page is a client bundle when it should be static.
Core rules
- Every file in
app/is a Server Component by default. Add"use client"only on the leaf that needs state, effects, or browser APIs. Pushing"use client"up to a page turns the whole subtree into a client bundle. - Keep data fetching in the server component and pass plain data down as props. Do not fetch in a client component when a parent server component can do it.
- A route is
page.tsx. Shared shell islayout.tsx. Loading UI isloading.tsx. Error UI iserror.tsxand must be a client component.
Steps to add an indexable dynamic route
-
Create
app/thing/[slug]/page.tsxas a server component. -
Export
generateStaticParamsreturning every slug so the route is statically generated:export async function generateStaticParams() { const items = await getAllItems(); return items.map((i) => ({ slug: i.slug })); } -
Export
generateMetadatafor a per-page title, description, and canonical:export async function generateMetadata({ params }): Promise<Metadata> { const { slug } = await params; const item = await getItem(slug); return { title: item.title, description: item.excerpt, alternates: { canonical: \`/thing/\${slug}\` }, }; } -
Return
notFound()fromnext/navigationfor unknown slugs so you get a real 404, not a soft empty page.
Pitfalls
- In Next 15+,
paramsandsearchParamsare Promises. Await them. A synchronousparams.slugread compiles but is undefined at runtime. export const metadataandgenerateMetadatacannot both live in the same file. Pick one.- Importing a server-only module (a DB client, a secret) into a
"use client"file leaks it into the browser bundle. Guard server modules withimport "server-only". - A new route often 404s until you restart the dev server. The file watcher misses new directories more often than edits.
Added 2026-07-01. Back to the Skill Library.

Get Smarter About AI Dev
New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.