Skip to content
Back to skills

Dev Nextjs

ASecurity

Next.js development (App Router, Server Components, caching, streaming). Trigger when the user works with Next.js, modifies app/, pages/, next.config, or talks about RSC, Server Actions, Route Handlers, middleware.

  • 5 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added May 28, 2026
ai-agentsgoshellbashsqlreactnextjsnodegitapisecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 4, 2026

npx -y skills add christopherlouet/claude-base --skill dev-nextjs --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dev Nextjs?

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

Security grade badge for Dev Nextjs
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/christopherlouet-dev-nextjs/badge)](https://www.skillsdirectory.com/skills/christopherlouet-dev-nextjs)

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
---
name: dev-nextjs
description: Next.js development (App Router, Server Components, caching, streaming). Trigger when the user works with Next.js, modifies app/, pages/, next.config, or talks about RSC, Server Actions, Route Handlers, middleware.
---

# Next.js App Router

## App Router vs Pages Router

**App Router** (default since Next 13, stable): `app/` folder, Server Components by default, Server Actions, streaming. **Prefer** for any new project.

**Pages Router**: `pages/` folder, getServerSideProps/getStaticProps. **Legacy**, do not add new routes there.

If the project has both, coexist — both can live together, but do not duplicate a route.

## Server Components by default

Any component in `app/` is a **Server Component** by default. It runs on the server, zero client JS.

```tsx
// app/posts/page.tsx — Server Component (default)
export default async function PostsPage() {
  const posts = await db.posts.findMany();  // Direct SQL OK
  return <PostList posts={posts} />;
}
```

### Switch to Client Component with `"use client"`

```tsx
// app/components/SearchBox.tsx
"use client";  // Directive at the top of the file

import { useState } from "react";

export function SearchBox() {
  const [query, setQuery] = useState("");
  return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}
```

**Rule**: "use client" only if you need hooks (useState, useEffect) or browser events (onClick, onChange). Otherwise, stay Server Component.

### Server/Client composition

Server Components can import Client Components, but **the reverse is not allowed** (except via `children` props).

```tsx
// OK: Server Component uses a Client Component
export default async function Page() {
  const data = await fetch(...);
  return <ClientChart data={data} />;  // ClientChart is "use client"
}

// OK: Client Component receives a Server Component via children
"use client";
export function Layout({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>;  // children can be a RSC
}
```

## Data Fetching

### Native fetch() with Next.js cache

```tsx
// Forced cache (SSG-like, manual revalidation)
const data = await fetch(url, { cache: "force-cache" });

// No cache (SSR on every request)
const data = await fetch(url, { cache: "no-store" });

// Time-based revalidation (ISR)
const data = await fetch(url, { next: { revalidate: 60 } });

// Tag-based revalidation
const data = await fetch(url, { next: { tags: ["posts"] } });
// Then in a Server Action: revalidateTag("posts")
```

IMPORTANT (Next 15+): `fetch` is no longer cached by default. You must explicitly set `force-cache` or `next: { revalidate }`.

### Parallel data fetching

```tsx
// BAD — waterfall
const user = await getUser();
const posts = await getPosts();

// GOOD — parallel
const [user, posts] = await Promise.all([getUser(), getPosts()]);
```

## Server Actions

Functions executed on the server, invoked from the client without a manual API route.

```tsx
// app/actions.ts
"use server";

export async function createPost(formData: FormData) {
  const title = formData.get("title") as string;
  await db.posts.create({ data: { title } });
  revalidatePath("/posts");
}

// app/posts/new/page.tsx
import { createPost } from "../actions";

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" />
      <button type="submit">Create</button>
    </form>
  );
}
```

**Pitfalls**:
- Always validate inputs with Zod (Server Actions receive unvalidated data)
- Always `revalidatePath` or `revalidateTag` after a mutation
- Do NOT expose business logic without auth (check the session inside the action)

## Route Handlers (API)

`app/api/*/route.ts` replaces `pages/api/`.

```tsx
// app/api/posts/route.ts
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const posts = await db.posts.findMany();
  return NextResponse.json(posts);
}

export async function POST(request: Request) {
  const body = await request.json();
  const post = await db.posts.create({ data: body });
  return NextResponse.json(post, { status: 201 });
}
```

## Streaming and Suspense

Show the page shell immediately, stream the slow content:

```tsx
import { Suspense } from "react";

export default function Page() {
  return (
    <div>
      <Header />  {/* Render immediately */}
      <Suspense fallback={<PostsSkeleton />}>
        <SlowPosts />  {/* Stream when ready */}
      </Suspense>
    </div>
  );
}
```

### loading.tsx

```tsx
// app/posts/loading.tsx — Automatic loading UI
export default function Loading() {
  return <PostsSkeleton />;
}
```

## Middleware

```tsx
// middleware.ts (at the root)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const token = request.cookies.get("token");
  if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
}

export const config = {
  matcher: ["/dashboard/:path*", "/api/protected/:path*"],
};
```

**Pitfall**: middleware runs on the Edge Runtime. No Node APIs (fs, crypto.createHash...) without a polyfill.

## Metadata API

Replaces manual `<head>`.

```tsx
// Static metadata
export const metadata: Metadata = {
  title: "My App",
  description: "...",
};

// Dynamic metadata (async)
export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.id);
  return { title: post.title };
}
```

## Images and Fonts

```tsx
import Image from "next/image";
import { Geist } from "next/font/google";

const geist = Geist({ subsets: ["latin"] });

<Image src="/hero.jpg" alt="" width={1200} height={600} priority />
```

Next loads and hosts fonts locally (no Google request), avoiding FOIT/FOUT.

## Configuration

```ts
// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  experimental: {
    reactCompiler: true,       // Auto-memoization optimization
    ppr: "incremental",        // Partial Prerendering
    dynamicIO: true,           // Next 15+ granular cache
  },
  images: {
    remotePatterns: [{ protocol: "https", hostname: "cdn.example.com" }],
  },
};

export default nextConfig;
```

## Vercel deployment

- `vercel` — preview deploy
- `vercel --prod` — production deploy
- Build settings auto-detected (npm, pnpm, bun)
- Env variables in the dashboard

Alternative: self-host with `next build && next start` (Node 18+).

## Common pitfalls

| Problem | Solution |
|---------|----------|
| "use client" everywhere | Only add it to components that use hooks/events |
| Unwanted data refetch | Check `cache` and `next.revalidate` on the fetch |
| Build errors ERR_DYNAMIC | Mark the page `export const dynamic = "force-dynamic"` or fix dynamic calls |
| Slow middleware | Reduce the matcher, avoid fetches inside middleware |
| Hydration mismatch | No random/Date.now() in SSR without suppressHydrationWarning |

## Verification

```bash
npm run build              # Verify the build + bundle size
npm run build -- --debug   # Detailed log
npx @next/bundle-analyzer  # Visualize the chunks
```

## Complements with the foundation

- Rule `.claude/rules/nextjs.md`: path-specific rules (auto-activation on `app/**`)
- Rule `.claude/rules/performance.md`: Core Web Vitals
- Skill `dev-react-perf`: memoization, React lazy loading
- Skill `qa-chrome`: visual audit of Next pages

## Expected output

1. **App Router** by default (not Pages Router unless partial migration)
2. **Server Components** by default, "use client" only if necessary
3. **Explicit caching** on every fetch (force-cache, no-store, or revalidate)
4. **Zod validation** on Server Actions and Route Handlers
5. **Metadata API** for SEO (never manual `<head>` in App Router)

## Rules

IMPORTANT: "use client" is the exception, not the rule. By default, everything is a Server Component.

IMPORTANT: Next 15+: fetch is no longer cached by default. Always specify the cache behavior.

IMPORTANT: Validate Server Action inputs with Zod before mutation.

YOU MUST use `revalidatePath` or `revalidateTag` after every mutation to invalidate the cache.

NEVER fetch inside middleware (Edge, slow).

NEVER expose business logic in a Route Handler without checking auth.

## See also

Vercel Labs publishes official agent skills at [`vercel-labs/agent-skills`](https://github.com/vercel-labs/agent-skills) (maintained by Vercel Engineering). Note the repo ships **React + Vercel** skills, *not* a dedicated Next.js one:

- **`react-best-practices`** — 40+ rules across 8 categories from Vercel Engineering.
- Companion skills: React Composition Patterns, React View Transitions, `deploy-to-vercel`, `vercel-optimize`, Web Design Guidelines.

The Next.js team ships its own skills **inside the framework repo**, at [`vercel/next.js/skills`](https://github.com/vercel/next.js/tree/canary/skills) (MIT; pin a stable release tag, v16.3.8 at the 2026-10-03 audit). They are adoption and verification procedures for recent features, not a general reference:

- **`next-cache-components-adoption`** / **`-optimizer`** — enable Cache Components and resolve the blocking routes it surfaces.
- **`next-partial-prefetching-adoption`** (+ an optimizer on `canary`) — enable Partial Prefetching and work through its insights.
- **`next-dev-loop`** — verify runtime behaviour in a running `next dev` (needs a browser-driving tool).

Since 16.2 the `next` package bundles version-matched docs at `node_modules/next/dist/docs/`; from 16.3, `next dev` upserts a managed block into `AGENTS.md` and `CLAUDE.md` (when it detects an agent) telling agents to read them first. Prefer those docs over this skill for API details on 16.2+: they match the installed version.

Install the vendor skill alongside this one on a Next.js project: the vendor sharpens the **React layer and Vercel deploy/optimize**, while **this foundation skill stays the primary Next.js reference** — App Router, Server Components, caching/streaming, Server Actions, middleware, route handlers — plus the opinionated workflow the foundation imposes (TDD-first, security defaults, deploy-safety, anti-patterns).

Install command and full list of validated vendor skills: `docs/recipes/recommended-vendor-skills.md`. Audit pilot trace: `specs/marketplace-audit/dev-skills-pilot-2026-05-05.md`.

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.