TheSkillz

Next.js App Router Patterns

Server Components, Server Actions, caching and streaming done right

TheSkillz Team TheSkillz Team No reviews yet0 installsv1.0.0
Scan passed · 100/100Human reviewedOfficial · TheSkillz
☆ Star 0

Applies version-aware App Router conventions for routing, Server vs Client Components, data fetching and cache policies, Server Actions with validation, metadata and error boundaries. Includes a pre-merge checklist and common migration traps.

SKILL.md

Next.js App Router Patterns

Before you write code

  1. Read package.json for the next version. In 14, fetch is cached by default; in 15+ it is not, and params, searchParams, cookies() and headers() are async and must be awaited.
  2. Read next.config.* for experimental flags (ppr, dynamicIO, useCache, typedRoutes) — they change which caching APIs are correct.
  3. Look at one existing route segment to copy the file conventions already in use (loading.tsx, error.tsx, colocated _components/).

Route structure

  • One folder per URL segment; use route groups (marketing) / (app) to separate layouts without affecting URLs.
  • Put private helpers in _lib/ or _components/ inside the segment so they are not treated as routes.
  • Prefer layout.tsx for shared chrome and template.tsx only when you need a fresh instance per navigation.
  • Add loading.tsx for any segment that awaits data and error.tsx (a Client Component) for any segment that can fail. Add not-found.tsx at the root and call notFound() for missing entities.
  • Dynamic segments: [slug]; catch-all [...parts]; parallel routes @modal for modals that must survive refresh; intercepting routes (.)photo/[id] for the "open in modal, deep link to page" pattern.

Server vs Client Components

Default to Server Components. Add 'use client' only at the leaf that needs state, effects, browser APIs or event handlers. Rules:

  • Never import a Server Component into a Client Component; pass it as children or a prop instead.
  • Keep secrets and database clients in server-only modules and guard them with import 'server-only'.
  • Props crossing the boundary must be serialisable (no functions, Dates become strings, no class instances).
  • Read request data with await headers() / await cookies() in Server Components or actions, not in client code.

Data fetching and caching

  • Fetch in the component that needs the data; React deduplicates identical requests in one render. For non-fetch sources (ORMs), wrap the function with cache() from React.
  • Parallelise independent fetches with Promise.all; use <Suspense> boundaries to stream slow parts.
  • Explicitly choose a cache policy per request: fetch(url, { next: { revalidate: 3600, tags: ['posts'] } }) or cache: 'no-store'. Do not rely on defaults across versions.
  • Export export const revalidate = 60 or export const dynamic = 'force-dynamic' at the segment level only when the whole segment needs it.
  • Invalidate with revalidateTag('posts') or revalidatePath('/posts') inside the Server Action that mutated the data.
  • With dynamicIO/use cache enabled (Next 15.1+/16), use the 'use cache' directive and cacheTag/cacheLife instead of the next fetch options.

Mutations with Server Actions

'use server'
import { z } from 'zod'
const Schema = z.object({ title: z.string().min(1) })
export async function createPost(prev: State, formData: FormData): Promise<State> {
  const parsed = Schema.safeParse(Object.fromEntries(formData))
  if (!parsed.success) return { errors: parsed.error.flatten().fieldErrors }
  await db.post.create({ data: parsed.data })
  revalidateTag('posts')
  redirect('/posts')
}
  • Validate every input server-side; treat actions as public HTTP endpoints.
  • Check authentication inside the action, not only in middleware.
  • Use useActionState for form state and useOptimistic for instant feedback.
  • redirect() throws; never wrap it in try/catch.

Metadata, images, fonts

  • Export metadata or generateMetadata from pages; include title.template in the root layout, openGraph, and alternates.canonical.
  • Generate OG images with opengraph-image.tsx and ImageResponse.
  • Load fonts with next/font in the root layout; never via <link>.
  • Use next/image with sizes for responsive images and priority for the LCP image only.

Middleware and auth

Keep middleware.ts (called proxy.ts in Next 16) thin: redirects, locale detection, coarse auth checks using a lightweight session cookie. Do full authorisation in the data layer.

Checklist before finishing

  • No 'use client' in layouts or pages unless unavoidable.
  • Every awaited data call has a cache decision written next to it.
  • loading.tsx and error.tsx exist for the new segment.
  • Server Actions validate input and check auth.
  • next build passes with no "dynamic server usage" errors and no type errors.
  • npx next lint clean.

Pitfalls

  • Reading searchParams makes the route dynamic; isolate that in a small Suspense-wrapped child.
  • useRouter from next/router is the Pages Router; use next/navigation.
  • Route Handlers (route.ts) should not be used for same-app mutations when a Server Action fits; use them for webhooks and third-party callers.
  • Environment variables reach the client only with the NEXT_PUBLIC_ prefix; everything else is undefined in the browser.
  • generateStaticParams returning nothing plus dynamicParams = false yields 404 for every path.

Reviews

Sign in to leave a review.

  • Be the first to review this skill.

More in coding