home / skills / gpolanco / skills-as-context / nextjs
/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 nextjsReview the files below or copy the command above to add this skill to your agents.
---
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)
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.
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.
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.