home / skills / gpolanco / skills-as-context / nextjs

nextjs skill

/skills/nextjs

This skill enforces Next.js App Router patterns, routing, caching, middleware, and metadata to accelerate secure, scalable app development.

npx playbooks add skill gpolanco/skills-as-context --skill nextjs

Review the files below or copy the command above to add this skill to your agents.

Files (4)
SKILL.md
9.3 KB
---
name: nextjs
description: >
  Enforces Next.js App Router patterns: routing, caching, middleware, and metadata.
  Trigger: Use when creating routes, configuring caching, setting up middleware, or handling Next.js-specific APIs.
license: Apache-2.0
metadata:
  author: devcontext
  version: "1.0.0"
  scope: [root]
  auto_invoke: "Writing Next.js code"
allowed-tools: Read
---

# Developing with Next.js (App Router)

## 🚨 CRITICAL: Reference Files are MANDATORY

**This SKILL.md provides OVERVIEW only. For EXACT patterns:**

| Task | MANDATORY Reading |
|------|-------------------|
| **Creating Server Actions & data flow** | āš ļø [reference/data-fetching.md](reference/data-fetching.md) |
| **Routing & file structure** | āš ļø [reference/routing-system.md](reference/routing-system.md) |
| **Architecture decisions** | āš ļø [reference/architecture.md](reference/architecture.md) |

**āš ļø DO NOT implement Server Actions without reading [data-fetching.md](reference/data-fetching.md) FIRST.**


---

## When to Use

Use this skill for **Next.js-specific** patterns:

- App Router routing conventions
- **Server Actions & data fetching flow** (Actions → Services → Repositories)
- Caching and revalidation
- Middleware configuration
- Metadata API
- Route Handlers (webhooks, public APIs)
- Loading and error boundaries

**Cross-references:**

- **For Server Actions data flow & error handling** → See [reference/data-fetching.md](reference/data-fetching.md)
- For React patterns (hooks, components) → See `react-19` skill
- For project structure (features/, imports) → See `structuring-projects` skill

---

## Critical Patterns

### ALWAYS

- **Use App Router** - Never use Pages Router for new projects
- **Read [data-fetching.md](reference/data-fetching.md) before creating Server Actions**
- **Follow Action → Service → Repository flow** (see data-fetching.md)
- **Return `ApiResponse<T>` from all Actions** (never throw exceptions)
- **Validate ALL inputs with Zod** in Actions (including IDs)
- **Implement `loading.tsx`** for every major route segment
- **Implement `error.tsx`** for graceful error handling
- **Use `generateMetadata()`** for dynamic SEO
- **Use `next/image`** with width/height to prevent CLS
- **Use `next/font`** for optimized font loading
- **Validate env vars at build time** with Zod schema
- **Use Route Handlers only for** webhooks and public APIs

### NEVER

- **Never use Pages Router patterns** (`getServerSideProps`, `getStaticProps`)
- **Never create Route Handlers for internal mutations** → Use Server Actions (react-19)
- **Never use `router.push()` for form submissions** → Use Server Actions with `redirect()`
- **Never import `next/router`** → Use `next/navigation` in App Router

### DEFAULTS

- `loading.tsx` and `error.tsx` at route segment level
- Metadata via `generateMetadata()` function
- Static generation by default, opt-in to dynamic with `cache: 'no-store'`

---

## 🚫 Critical Anti-Patterns

- **DO NOT** use `router.push` for navigation in Server Actions → use `redirect()`
- **DO NOT** create a Route Handler (`api/route.ts`) for internal data mutations → use Server Actions
- **DO NOT** use `usePathname` or `useSearchParams` if you can get the data from props in a Server Component
- **DO NOT** ignore the Action → Service → Repository flow → See [data-fetching.md](reference/data-fetching.md)

---

---

## Decision Tree

```
Creating a new page?        → page.tsx (default export async function)
Need to navigate/redirect?  → āš ļø STOP → Read reference/routing-system.md FIRST
Need loading state?         → Add loading.tsx in same folder
Need error handling?        → Add error.tsx ('use client') in same folder
Sharing layout?             → Use layout.tsx
Need metadata?              → Export generateMetadata()
Public API / webhook?       → Use route.ts (Route Handler)
Internal mutation?          → āš ļø STOP → Read reference/data-fetching.md FIRST
                              Then create Server Action with Action → Service → Repository flow
Need middleware?            → middleware.ts at project root
```

---

## App Router File Conventions

```text
app/
ā”œā”€ā”€ layout.tsx              # Root layout (required)
ā”œā”€ā”€ page.tsx                # Home page (/)
ā”œā”€ā”€ loading.tsx             # Loading UI for /
ā”œā”€ā”€ error.tsx               # Error UI for / ('use client')
ā”œā”€ā”€ not-found.tsx           # 404 page
ā”œā”€ā”€ (auth)/                 # Route group (no URL segment)
│   ā”œā”€ā”€ login/page.tsx      # /login
│   └── register/page.tsx   # /register
ā”œā”€ā”€ dashboard/
│   ā”œā”€ā”€ layout.tsx          # Nested layout
│   ā”œā”€ā”€ page.tsx            # /dashboard
│   ā”œā”€ā”€ loading.tsx
│   ā”œā”€ā”€ error.tsx
│   └── [id]/               # Dynamic segment
│       └── page.tsx        # /dashboard/123
ā”œā”€ā”€ api/                    # Route Handlers (public APIs only)
│   └── webhooks/
│       └── stripe/route.ts
└── [...slug]/              # Catch-all segment
    └── page.tsx
```

---

## Metadata API

```typescript
// Static metadata
export const metadata = {
  title: "Dashboard",
  description: "User dashboard",
};

// Dynamic metadata
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const product = await getProduct(params.id);

  return {
    title: product.name,
    description: product.description,
    openGraph: {
      title: product.name,
      images: [product.image],
    },
  };
}
```

---

## Caching & Revalidation

```typescript
// Static (cached indefinitely) - DEFAULT
const data = await fetch("https://api.example.com/data");

// Time-based revalidation (ISR)
const data = await fetch("https://api.example.com/data", {
  next: { revalidate: 60 }, // Revalidate every 60s
});

// No cache (always fresh)
const data = await fetch("https://api.example.com/data", {
  cache: "no-store",
});

// On-demand revalidation (in Server Action)
import { revalidatePath, revalidateTag } from "next/cache";

revalidatePath("/products"); // Revalidate path
revalidateTag("products"); // Revalidate by tag
```

---

## Route Handlers (Webhooks/Public APIs)

**Only use for:**

- External webhooks (Stripe, GitHub)
- Public APIs consumed by third parties

```typescript
// app/api/webhooks/stripe/route.ts
import { headers } from "next/headers";

export async function POST(request: Request) {
  const body = await request.text();
  const signature = headers().get("stripe-signature")!;

  // Verify and process webhook
  const event = await verifyStripeWebhook(body, signature);

  switch (event.type) {
    case "checkout.session.completed":
      await handleCheckoutComplete(event.data.object);
      break;
  }

  return Response.json({ received: true });
}
```

---

## Middleware

```typescript
// middleware.ts (project root)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  // Auth check example
  const token = request.cookies.get("session");

  if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  return NextResponse.next();
}

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

---

## Required Files

Every Next.js App Router project should have:

| File                | Purpose                         | Required          |
| ------------------- | ------------------------------- | ----------------- |
| `app/layout.tsx`    | Root layout with html/body      | āœ… Yes            |
| `app/page.tsx`      | Home page (/)                   | āœ… Yes            |
| `app/loading.tsx`   | Loading UI (Suspense boundary)  | Per route segment |
| `app/error.tsx`     | Error boundary (`'use client'`) | Per route segment |
| `app/not-found.tsx` | 404 page                        | Recommended       |
| `middleware.ts`     | Auth/session handling           | If auth needed    |

---

## Loading & Error Signatures

```typescript
// loading.tsx - No props, returns loading UI
export default function Loading() {
  return <Skeleton />;
}

// error.tsx - Must be 'use client'
'use client';
export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  // Return error UI with reset button
}
```

---

## Environment Variables

```typescript
// lib/env.ts - Validate at build time
import { z } from "zod";

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  NEXTAUTH_SECRET: z.string().min(32),
  NEXT_PUBLIC_APP_URL: z.string().url(),
});

export const env = envSchema.parse(process.env);
```

**Naming:**

- `NEXT_PUBLIC_*` → Available in browser
- Others → Server-only (secrets)

---

## Commands

```bash
# Create new Next.js project
pnpm create next-app@latest ./ --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"

# Development
pnpm dev

# Build & type check
pnpm build
pnpm tsc --noEmit
```

---

## Resources

- **Architecture Deep Dive**: [reference/architecture.md](reference/architecture.md)
- **Typed Routing System**: [reference/routing-system.md](reference/routing-system.md)
- **Server-First Data Fetching**: [reference/data-fetching.md](reference/data-fetching.md)
- **React Patterns**: See `react-19` skill
- **Project Structure**: See `structuring-projects` skill
- **Official Docs**: [Next.js Documentation](https://nextjs.org/docs)

Overview

This skill enforces Next.js App Router patterns and conventions across routing, caching, middleware, Server Actions, and metadata to keep projects consistent and secure. It codifies required files, prevents App Router anti-patterns, and guides data flow (Action → Service → Repository) and caching defaults. Use it as the authoritative checklist when creating or reviewing Next.js routes and server logic.

How this skill works

The skill inspects the app/ directory and project root for required files (layout.tsx, page.tsx, loading.tsx, error.tsx, middleware.ts, etc.), validates routing structure and dynamic segments, and checks use of App Router APIs. It flags forbidden patterns (Pages Router APIs, router.push for mutations, route handlers for internal mutations), enforces input validation with Zod in Server Actions, and verifies metadata, image/font usage, and cache/revalidation settings. It also highlights where to read deeper references for Server Actions, routing, and architecture decisions.

When to use it

  • When adding new routes, dynamic segments, or nested layouts with the App Router
  • When creating Server Actions, modifying data flow, or handling internal mutations
  • When configuring caching, ISR, or on-demand revalidation for fetched data
  • When implementing middleware for auth/session handling or path matching
  • When creating public APIs or webhooks that require Route Handlers
  • When adding dynamic SEO via generateMetadata() or optimizing images/fonts

Best practices

  • Always use the App Router for new pages; never use Pages Router APIs like getServerSideProps
  • Follow Action → Service → Repository flow for Server Actions and return ApiResponse<T> (avoid throwing)
  • Validate every Action input with Zod, including ID and env var validation at build time
  • Provide loading.tsx and error.tsx at each major route segment and export generateMetadata() for dynamic SEO
  • Default to static generation; opt into dynamic with cache: 'no-store' and use next.fetch revalidate options
  • Use Route Handlers only for external webhooks or public third-party APIs; use Server Actions for internal mutations

Example use cases

  • Reviewing a pull request that adds a new nested dashboard route with dynamic [id] segment
  • Implementing a Server Action that updates a record: ensure Zod validation, ApiResponse<T>, and revalidatePath call
  • Adding Stripe webhook endpoint under app/api/webhooks/stripe/route.ts with signature verification
  • Setting up middleware.ts to guard /dashboard paths and redirect unauthenticated users to /login
  • Converting legacy Pages Router code to App Router patterns and replacing getServerSideProps usage

FAQ

Can I use Route Handlers for internal mutations?

No. Use Server Actions for internal data mutations and follow the Action → Service → Repository flow. Route Handlers are reserved for webhooks and public APIs.

What should I do before creating Server Actions?

Read the Server Actions data flow guidance first, validate all inputs with Zod, return ApiResponse<T>, and avoid throwing exceptions from Actions.