Skip to main content
Watch: Claude Code Just Killed After Effects

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.

nextjsapp-routerreactserver-components

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.

Install
SKILL.md

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 is layout.tsx. Loading UI is loading.tsx. Error UI is error.tsx and must be a client component.

Steps to add an indexable dynamic route

  1. Create app/thing/[slug]/page.tsx as a server component.

  2. Export generateStaticParams returning every slug so the route is statically generated:

    export async function generateStaticParams() {
      const items = await getAllItems();
      return items.map((i) => ({ slug: i.slug }));
    }
    
  3. Export generateMetadata for 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}\` },
      };
    }
    
  4. Return notFound() from next/navigation for unknown slugs so you get a real 404, not a soft empty page.

Pitfalls

  • In Next 15+, params and searchParams are Promises. Await them. A synchronous params.slug read compiles but is undefined at runtime.
  • export const metadata and generateMetadata cannot 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 with import "server-only".
  • A new route often 404s until you restart the dev server. The file watcher misses new directories more often than edits.

Related

Added 2026-07-01. Back to the Skill Library.

AI Development Stack

Get Smarter About AI Dev

New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.

One email per weekReal code, not theoryFree forever