Skip to content
Back to skills

Nextjs Patterns

ASecurity

Writes Next.js App Router code — Server Components, Server Actions, RSC boundaries, route handlers, caching, and streaming — matching framework conventions and project architecture.

  • 10 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 5, 2026
ai-agentsrustgoreactnextjsnodeapidatabaseperformance

Works with

  • cli
  • api

Security analysis

A100/100

Scanned June 5, 2026

npx -y skills add event4u-app/agent-config --skill nextjs-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Nextjs Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Nextjs Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/event4u-app-nextjs-patterns/badge)](https://www.skillsdirectory.com/skills/event4u-app-nextjs-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
model_tier: medium
name: nextjs-patterns
description: "Writes Next.js App Router code — Server Components, Server Actions, RSC boundaries, route handlers, caching, and streaming — matching framework conventions and project architecture."
domain: engineering
workspaces:
  - engineering
packs:
  - nextjs
trust:
  level: professional
install:
  default: false
  removable: true
---

# nextjs-patterns

## Compatibility

- **Tested against:** Next.js `14.x` and `15.x` (App Router), React `18+` / `19+`.
- Pages Router is **out of scope**. On a Pages-Router project (no `app/`),
  fall back to plain React skills; ask before introducing App Router.

## When to use

Use this skill for Next.js-specific code generation and editing on the App Router:

- Server Components (RSC) and Client Components (`"use client"`)
- Server Actions (`"use server"`)
- Route Handlers (`app/api/**/route.ts`)
- Layouts, templates, loading / error / not-found boundaries
- Data fetching with `fetch` + cache directives
- `revalidatePath` / `revalidateTag` invalidation
- Streaming and `<Suspense>` boundaries
- Middleware (`middleware.ts`)
- Metadata API (static + dynamic)
- Route Groups, Parallel Routes, Intercepting Routes

## When to use the analysis sibling

When the task is **understanding** how a Next.js app boots, where the
server/client boundary lives, what is cached vs streamed, or why a
hydration mismatch fires — defer to `project-analysis-nextjs` first,
then return here for the edit.

## Procedure: write Next.js code

1. **Confirm App Router** — `app/` directory exists; `next.config.{js,ts,mjs}` present.
2. **Confirm version** — `package.json` major for `next`. 14.x vs 15.x differs in
   default caching (15.x is uncached by default for `fetch`) and in `cookies()` /
   `headers()` being async.
3. **Inspect routing layout** — flat `app/` vs grouped (`app/(marketing)/`,
   `app/(app)/`); identify shared layouts and parallel routes.
4. **Inspect data layer** — Server Actions vs route handlers vs external API;
   ORM (Prisma, Drizzle) or fetch-based.
5. **Inspect styling** — Tailwind, CSS Modules, or styled engine; respect the
   chosen stack, do not mix.
6. **Check test conventions** — Vitest / Jest + RTL for components; Playwright
   for E2E; route-handler tests via `node` runtime.

## Server vs Client boundary

- **Server Components are the default.** Add `"use client"` only when a leaf
  component needs state, effects, browser APIs, or event handlers.
- The `"use client"` boundary is **viral upward at the import graph**, but you
  can keep a Server Component **rendered as a child** of a Client Component
  by passing it as a prop / `children` — that pattern keeps server logic out
  of the client bundle.
- Never put database access, secrets, or server-only modules inside a Client
  Component or a file imported by one. Use `import "server-only"` at the top
  of a server module to make leaks fail at build time.
- Symmetric guard: `import "client-only"` for code that must never run on the
  server (e.g. browser-only SDKs).
- Props passed from Server → Client must be serializable. No functions, no
  Dates with custom prototypes, no `Map` / `Set` — use plain JSON.

## Server Actions

- Declare with `"use server"` at the top of the file or per-function.
- Server Actions are **server-only RPC**; never call them from `useEffect` for
  pure data fetching — that is what Server Components are for.
- Always **validate input** (zod or equivalent) at the entry of the action —
  the caller is the client; treat it as hostile.
- Always **re-authorize** inside the action; do not trust client-side guards.
- After a mutation: call `revalidatePath()` / `revalidateTag()` or `redirect()`.
- Return shape: a `{ ok: true, data }` / `{ ok: false, error }` discriminated
  union — never throw across the action boundary for expected errors.
- Use `useActionState` (15.x) / `useFormState` (14.x) on the client for
  progressive enhancement; the form must work without JS.

## Route Handlers

- `app/api/**/route.ts` exports `GET`, `POST`, etc.
- Handlers run on the **node runtime by default**; opt into `edge` only when
  the dependency set supports it.
- Read request body once: `await req.json()` or `await req.formData()`.
- Return `Response` / `NextResponse`. Use `NextResponse.json` for JSON +
  consistent status codes.
- Authentication and rate limiting happen **inside** the handler or via
  `middleware.ts`; do not assume any framework default.
- Cache behavior: route handlers are dynamic by default in 15.x. Add
  `export const dynamic = 'force-static'` / `revalidate = N` explicitly.

## Caching and revalidation (14.x vs 15.x)

- **14.x**: `fetch` is cached by default (force-cache); opt out with
  `{ cache: 'no-store' }` or segment-level `export const dynamic = 'force-dynamic'`.
- **15.x**: `fetch` is **not cached by default**; opt in with
  `{ cache: 'force-cache' }` or `{ next: { revalidate: N } }`.
- Use **tag-based revalidation** for shared invalidation surface:
  `fetch(url, { next: { tags: ['posts'] } })` then `revalidateTag('posts')`.
- Use `revalidatePath('/blog/[slug]', 'page')` for targeted page invalidation.
- Never call `revalidate*` from a Client Component — wrap it in a Server Action.
- `unstable_cache` / `cache` (React) wrap pure server functions for memoization
  within a request; respect the difference between request-scoped and
  cross-request caching.

## Data fetching patterns

- Prefer fetching in **the Server Component that uses the data**, not in a
  layout, to allow per-route streaming.
- Parallel fetching: declare independent promises and `await` together —
  avoid sequential `await` chains that block streaming.
- `<Suspense>` boundaries make slow data non-blocking; wrap each independent
  data dependency in its own Suspense, not the whole page.
- `loading.tsx` is a default Suspense fallback for the segment; do not use it
  as a global spinner.
- Avoid `useEffect` for initial data load in Client Components — fetch on the
  server and pass props.

## Layouts, templates, error and not-found

- `layout.tsx` is a **persistent** wrapper across navigations; do not put
  per-page logic there.
- `template.tsx` re-renders on every navigation; use only when a layout
  cannot.
- `loading.tsx`, `error.tsx`, `not-found.tsx` are **per-segment**; nest them
  to localize fallbacks.
- `error.tsx` must be a Client Component (`"use client"`); accept `error` and
  `reset` props.

## Middleware

- `middleware.ts` at the project root runs on the **edge runtime** before a
  request hits a route — use for auth redirects, locale negotiation, A/B.
- No database access from middleware; the edge runtime cannot run most node
  drivers. Use a session cookie + lightweight verification.
- `matcher` config narrows which paths invoke it — never let middleware run
  on every asset.

## Metadata, performance, env

- Metadata: static `export const metadata`; dynamic `generateMetadata()`.
  `generateStaticParams` for static dynamic-segment generation.
- `next/image`, `next/font`, `next/dynamic` for images, fonts, code-split
  Client Components — never bare `<img>`, CSS `@import` Google Fonts, or
  unguarded heavy imports.
- A single `"use client"` near the top of a tree pulls the subtree into the
  client bundle — keep the boundary at the leaf.
- Env: server-only `DATABASE_URL` etc.; client-exposed must be prefixed
  `NEXT_PUBLIC_*` and is NOT secret. Validate env at boot (zod) and import
  the validated object — never `process.env` ad hoc.

## Output format

1. Next.js code following App Router conventions and the project's existing layout.
2. All related files (page, layout, server action, route handler, schema, test) as needed.
3. When a cache surface changes — point at the invalidation call (`revalidateTag` / `revalidatePath`).

## Do NOT

- Do NOT add `"use client"` to a file that does not need it — it pulls the
  subtree into the client bundle.
- Do NOT import server modules (db client, secrets) from a Client Component
  file — even transitively. Guard with `import "server-only"`.
- Do NOT call Server Actions from `useEffect` to fetch data — fetch on the server.
- Do NOT throw across a Server Action boundary for expected errors — return a
  result union.
- Do NOT trust client-side authorization in a Server Action or route handler —
  re-check.
- Do NOT mutate without revalidating — stale UI is a class of bug, not a design.
- Do NOT mix Pages Router and App Router patterns in one feature — pick one.
- Do NOT assume 14.x caching defaults on 15.x; verify the major before reasoning about cache.

## Gotcha

- **15.x async APIs**: `cookies()`, `headers()`, `params`, `searchParams` are
  **async** in 15.x. Code that ran on 14.x will silently misbehave (Promise
  rendered as an object) until awaited.
- **Hydration mismatch**: rendering `Date.now()`, `Math.random()`, or
  locale-dependent strings without `suppressHydrationWarning` or a Client
  boundary causes a hydration error.
- **`"use client"` is not opt-in for hooks**: it is required for any file
  using `useState`, `useEffect`, `useRef`, or browser globals.
- **Server Actions and forms**: a Server Action used as a form action must
  accept `FormData`. Mixing `FormData` and typed args breaks progressive
  enhancement.
- **Cache lies during dev**: dev mode ignores most cache directives. Verify
  caching behavior in a production build (`next build && next start`).
- **`revalidatePath` requires the route group syntax** for parallel/dynamic
  segments — bare paths silently miss.
- **Edge runtime ≠ Node**: most npm packages with native deps or Node-only
  APIs (`fs`, `crypto.createHash`) fail at the edge. Pin runtime per route.

## Auto-trigger keywords

- Next.js / App Router · Server Component (RSC) · Client Component (`"use client"`)
- Server Action (`"use server"`) · Route Handler (`app/api`) · `middleware.ts`
- `revalidatePath` / `revalidateTag` / `unstable_cache`
- `next/image` / `next/font` / `next/dynamic`
- `layout.tsx` / `loading.tsx` / `error.tsx` / `not-found.tsx`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.