nextjs
Build Next.js applications with the App Router, server and client components, route handlers, caching, and deployment configuration.
Files3
View on GitHubSKILL.mdskills/nextjs/SKILL.md
GitHubNext.js App Router AI Skill Guide (Claude)
Overview & Engine Architecture
Next.js App Router (app/) uses file-based routes, React Server Components (RSC) by default, and Client Components only where browser interactivity is required. Data fetching on the server participates in a caching model that changed materially in Next.js 15–16: implicit static-by-default gave way to dynamic-by-default when Cache Components (cacheComponents: true, formerly experimental.dynamicIO) is enabled—opt-in caching via "use cache", cacheLife, and cacheTag.
Claude operates as a Principal Full-Stack Architect: server/client boundary, secrets isolation, Route Handlers vs pages/api, Suspense for dynamic segments, and webhook raw-body routes.
app/
layout.tsx (Server Component default)
page.tsx
loading.tsx / error.tsx
(route)/page.tsx (dynamic segments)
api/.../route.ts (Route Handlers)
components/
*.tsx + "use client" only when needed
Pages Router (pages/) still exists in brownfield apps—do not mix routing models in one tree without a deliberate migration plan.
When to use / when not to
Use when
- Greenfield React apps needing SSR, streaming, and colocated data fetching.
- Refactoring
"use client"sprawl and leaking server imports. - Designing cache/revalidation contracts for product-facing freshness.
Do not use when
- The team requires only static export with zero server runtime (verify
output: 'export'constraints). - A simple SPA behind a separate BFF suffices and RSC complexity adds no value.
Operational Capabilities & Agent Directives
- Default Server Components; add
"use client"only for state, effects, browser APIs, or event handlers. - Secrets: Never in
NEXT_PUBLIC_*or modules imported by client components. Useserver-onlypackage on server modules. - App vs Pages gotchas:
getServerSideProps/getStaticProps→ async Server Components or"use cache"helpers._app.tsxproviders → clientproviders.tsximported from rootlayout.tsx.pages/api/*→app/**/route.tswith different body parsing rules.
- Dynamic APIs (
cookies(),headers(),params,searchParams) make routes dynamic—wrap in<Suspense>when using Cache Components or expect build/runtime validation errors. - Do not call
Date.now(),Math.random(), orcrypto.randomUUID()in prerendered Server Components—move to request-time Suspense or client. - Route Handlers for webhooks: Use raw body (
request.text()/arrayBuffer()) before JSON parse—see@stripe. - Confirm Next version before recommending
dynamic,revalidate,fetchCache—many are removed or error under Cache Components.
Minimal patterns
Server page + client island:
// app/items/page.tsx
import { Counter } from "@/components/counter";
export default async function ItemsPage() {
const res = await fetch(`${process.env.API_URL}/items`, {
next: { revalidate: 60 }, // verify against project's Next major
});
const data = await res.json();
return (
<main>
<h1>Items ({data.length})</h1>
<Counter />
</main>
);
}
Webhook Route Handler (raw body):
// app/api/webhooks/stripe/route.ts
export async function POST(request: Request) {
const rawBody = await request.text();
// verify signature on rawBody, then JSON.parse for logging only
}
App Router vs Pages — migration traps
| Pages pattern | App Router equivalent | Gotcha |
|---|---|---|
pages/index.tsx |
app/page.tsx |
No default _document |
pages/api/x.ts |
app/api/x/route.ts |
Export GET/POST functions |
useRouter() from next/router |
next/navigation |
Different API |
_app global CSS |
app/layout.tsx |
Metadata API replaces Head |
getStaticPaths |
generateStaticParams |
Async in Server Components |
Middleware req.cookies |
cookies() in RSC |
Dynamic boundary |
Technical Troubleshooting Matrix
| Issue | Root cause | Fix |
|---|---|---|
| Secret leaked to client bundle | Server module imported by "use client" |
Split modules; server-only |
| Webhook signature fails | express.json()-style parse before verify |
Raw body route; parse after verify |
| Stale data | Aggressive caching / missing revalidate | Explicit revalidate or "use cache" + cacheLife |
| Build error: missing Suspense | Dynamic API in static shell (Cache Components) | Suspense boundary or "use cache" scope |
cookies() in cached function |
Illegal under Cache Components | Pass values as args into cached helper |
| Hydration mismatch | Server/client render diff | Fix conditional rendering; avoid random in RSC |
Best practices
- Colocate
loading.tsx/error.tsxper route segment. - Metadata API for SEO per route.
next/imagewith explicit dimensions.- Pin Node version to host (Vercel, Docker, etc.).
- For Next 16+: read Cache Components migration before enabling
cacheComponents.
Limitations
- Caching semantics differ by host (serverless vs long-lived Node).
- Server Actions require CSRF/origin awareness and server-side authz.
"use cache"default storage is in-memory per instance—use remote cache for cross-deploy persistence when needed.
Related skills
@react— hooks, effects, composition@typescript— typing server props and loaders@stripe— payment webhooks in Route Handlers@supabase— SSR auth cookie patterns@vercel— deployment-specific caching
Agent Operational Directive
MANDATORY: Keep secrets and DB clients on the server only. For webhooks, verify signatures on the raw body. Before changing caching flags, confirm the project's Next.js major version and whether Cache Components is enabled. Prefer explicit cache boundaries over implicit assumptions from older App Router tutorials.