skills/nextjs-app-router-expert/SKILL.md
--- license: Apache-2.0 name: nextjs-app-router-expert version: 1.0.0 category: Frontend & UI tags: - nextjs - react - app-router - rsc - server-components - full-stack --- # Next.js App Router Expert ## Overview Expert in Next.js 14/15 App Router architecture, React Server Components (RSC), Server Actions, and modern full-stack React development. Specializes in routing patterns, data fetching strategies, caching, streaming, and deployment optimization. ## Decision Points ### Ro
npx skillsauth add curiositech/windags-skills nextjs-app-router-expertInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert in Next.js 14/15 App Router architecture, React Server Components (RSC), Server Actions, and modern full-stack React development. Specializes in routing patterns, data fetching strategies, caching, streaming, and deployment optimization.
Is the UI requirement:
├── Simple nested layouts?
│ └── Use standard file-based routing (page.tsx, layout.tsx)
│ └── Check: Does each segment need its own loading/error states?
│ ├── Yes → Add loading.tsx/error.tsx per segment
│ └── No → Use parent boundaries only
├── Content that appears alongside main page?
│ └── Use parallel routes (@slot)
│ └── Check: Should content persist across navigation?
│ ├── Yes → Use default.tsx to maintain state
│ └── No → Let it unmount naturally
├── Modal/overlay that intercepts navigation?
│ └── Use intercepting routes (.)
│ └── Check: What's the fallback for direct access?
│ ├── Same content → Create both routes with shared component
│ └── Different UX → Create separate page implementations
└── Dynamic segments with complex patterns?
└── Use catch-all routes ([...slug])
└── Check: Are some segments optional?
├── Yes → Use [[...optional]] syntax
└── No → Use [...required] syntax
What's the data freshness requirement:
├── Static at build time?
│ └── Use generateStaticParams() + fetch with force-cache
├── Fresh on every request?
│ └── Use fetch with no-store or dynamic functions
├── Cached with periodic updates?
│ └── Use fetch with revalidate: seconds
└── User-specific but cacheable?
└── Use fetch with cache + cookies() to make dynamic
Does the component need:
├── Browser APIs (localStorage, window)?
│ └── Use 'use client' at component level
├── Event handlers (onClick, onSubmit)?
│ └── Use 'use client' at component level
├── React hooks (useState, useEffect)?
│ └── Use 'use client' at component level
└── Only data fetching and rendering?
└── Keep as Server Component
└── Check: Do children need client features?
├── Yes → Pass server data as props to client children
└── No → Keep entire tree as server components
Symptoms: Console errors "Text content did not match", layout shifts on page load Diagnosis: Server-rendered HTML differs from client-rendered HTML Fix:
Symptoms: Large bundle sizes, slow initial page loads, excessive JavaScript Diagnosis: 'use client' placed too high in component tree, pulling server logic to client Fix:
Symptoms: Stale data after mutations, users see outdated content Diagnosis: Missing revalidatePath/revalidateTag after Server Actions Fix:
Symptoms: Slow page loads, sequential loading indicators, poor Core Web Vitals Diagnosis: Sequential data fetching instead of parallel, no Suspense boundaries Fix:
Symptoms: Confusing URLs, components in wrong places, hard to navigate codebase Diagnosis: Mixing logical grouping with URL structure, missing route groups Fix:
// Decision: Product data is semi-static, reviews are dynamic
// Solution: ISR for product, streaming for reviews
// app/products/[id]/page.tsx
import { Suspense } from 'react';
// Static product data with 1-hour revalidation
async function getProduct(id: string) {
const res = await fetch(`https://api.shop.com/products/${id}`, {
next: { revalidate: 3600, tags: ['product'] }
});
if (!res.ok) throw new Error('Product not found');
return res.json();
}
// Dynamic reviews, always fresh
async function getReviews(id: string) {
const res = await fetch(`https://api.shop.com/products/${id}/reviews`, {
cache: 'no-store'
});
return res.json();
}
export default async function ProductPage({ params }: { params: { id: string } }) {
// Expert catches: Fetch product immediately, stream reviews
// Novice misses: Would wait for all data before rendering
const product = await getProduct(params.id);
return (
<main>
<ProductDetails product={product} />
<AddToCartForm productId={params.id} />
{/* Stream reviews while showing product immediately */}
<Suspense fallback={<ReviewsSkeleton />}>
<ReviewsSection productId={params.id} />
</Suspense>
</main>
);
}
async function ReviewsSection({ productId }: { productId: string }) {
const reviews = await getReviews(productId);
return <Reviews data={reviews} />;
}
// Server Action for cart - expert includes optimistic update
async function addToCart(formData: FormData) {
'use server';
const productId = formData.get('productId') as string;
// ... save to database
revalidateTag('cart'); // Invalidate cart count
redirect('/cart');
}
Expert Decision Points Navigated:
Don't use this skill for:
react-spa-development insteadnextjs-pages-router insteadzustand-state-management insteadwebsocket-integration insteadnextauth-integration insteadpostgresql-optimization insteadvercel-deployment or docker-deployment insteadDelegate to other skills when:
react-performance-optimizerreact-hook-form-experttypescript-advanced-patternsdata-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.