nextjs-app-router-expert
4
总安装量
2
周安装量
#53423
全站排名
安装命令
npx skills add https://github.com/curiositech/some_claude_skills --skill nextjs-app-router-expert
Agent 安装分布
mcpjam
2
mistral-vibe
2
claude-code
2
junie
2
windsurf
2
zencoder
2
Skill 文档
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.
When to Use
- Starting a new Next.js project with App Router
- Migrating from Pages Router to App Router
- Implementing complex routing patterns (parallel, intercepting routes)
- Optimizing data fetching with RSC and caching
- Setting up Server Actions for mutations
- Configuring middleware for auth/redirects
- Debugging hydration errors or RSC issues
- Deploying to Vercel, Cloudflare, or self-hosted
Capabilities
Routing Architecture
- File-based routing with
app/directory - Dynamic routes (
[slug],[...catchAll],[[...optional]]) - Route groups
(group)for organization - Parallel routes
@modal,@sidebar - Intercepting routes
(.),(..),(..)(..) - Loading and error boundaries per route segment
React Server Components
- Server vs Client component boundaries
'use client'directive placement- Composition patterns (server wrapping client)
- Streaming with Suspense boundaries
- Progressive rendering strategies
Data Fetching
fetch()with automatic deduplication- Caching strategies (
force-cache,no-store,revalidate) generateStaticParams()for static generation- Incremental Static Regeneration (ISR)
- On-demand revalidation with
revalidatePath()/revalidateTag()
Server Actions
- Form mutations with
'use server' - Optimistic updates with
useOptimistic - Progressive enhancement (works without JS)
- Error handling and validation
- Redirect after mutation
Middleware & Edge
middleware.tsfor auth, redirects, rewrites- Edge Runtime vs Node.js Runtime
- Geolocation and conditional routing
- A/B testing and feature flags
Performance Optimization
- Image optimization with
next/image - Font optimization with
next/font - Script loading strategies
- Bundle analysis and code splitting
- Partial prerendering (PPR)
Dependencies
Works well with:
react-performance-optimizer– React-specific performance patternsvercel-deployment– Vercel deployment configurationcloudflare-worker-dev– Edge deployment patternspostgresql-optimization– Database queries for RSC
Examples
Basic Route Structure
app/
âââ layout.tsx # Root layout (required)
âââ page.tsx # Home page (/)
âââ loading.tsx # Loading UI
âââ error.tsx # Error boundary
âââ not-found.tsx # 404 page
âââ blog/
â âââ page.tsx # /blog
â âââ [slug]/
â âââ page.tsx # /blog/:slug
â âââ loading.tsx # Per-route loading
âââ (auth)/ # Route group (no URL impact)
âââ login/
â âââ page.tsx # /login
âââ register/
âââ page.tsx # /register
Server Component with Data Fetching
// app/posts/page.tsx
import { Suspense } from 'react';
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }, // ISR: revalidate every hour
});
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<main>
<h1>Blog Posts</h1>
<Suspense fallback={<PostsSkeleton />}>
<PostList posts={posts} />
</Suspense>
</main>
);
}
Server Action Form
// app/contact/page.tsx
import { redirect } from 'next/navigation';
import { revalidatePath } from 'next/cache';
async function submitContact(formData: FormData) {
'use server';
const email = formData.get('email') as string;
const message = formData.get('message') as string;
// Validate
if (!email || !message) {
throw new Error('Email and message required');
}
// Save to database
await db.contacts.create({ email, message });
// Revalidate and redirect
revalidatePath('/contact');
redirect('/contact/success');
}
export default function ContactPage() {
return (
<form action={submitContact}>
<input name="email" type="email" required />
<textarea name="message" required />
<button type="submit">Send</button>
</form>
);
}
Parallel Routes (Modal Pattern)
app/
âââ layout.tsx
âââ page.tsx
âââ @modal/
â âââ default.tsx # Empty state when no modal
â âââ (.)photo/[id]/
â âââ page.tsx # Intercept /photo/[id] as modal
âââ photo/[id]/
âââ page.tsx # Full page when direct navigation
// app/layout.tsx
export default function Layout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<>
{children}
{modal}
</>
);
}
Middleware for Auth
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token');
const isAuthPage = request.nextUrl.pathname.startsWith('/login');
const isProtectedPage = request.nextUrl.pathname.startsWith('/dashboard');
// Redirect authenticated users away from login
if (isAuthPage && token) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
// Redirect unauthenticated users to login
if (isProtectedPage && !token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/login'],
};
Static Generation with Dynamic Params
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return posts.map((post: { slug: string }) => ({
slug: post.slug,
}));
}
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
return {
title: post?.title ?? 'Post Not Found',
description: post?.excerpt,
};
}
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
if (!post) {
notFound();
}
return (
<article>
<h1>{post.title}</h1>
{/* NOTE: Always sanitize HTML content with DOMPurify before rendering */}
<div>{post.content}</div>
</article>
);
}
Streaming with Suspense
// app/dashboard/page.tsx
import { Suspense } from 'react';
export default function DashboardPage() {
return (
<div className="grid grid-cols-2 gap-4">
{/* These load in parallel and stream in as ready */}
<Suspense fallback={<CardSkeleton />}>
<RevenueCard />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<UsersCard />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<RecentOrders />
</Suspense>
</div>
);
}
// Each component fetches its own data
async function RevenueCard() {
const revenue = await getRevenue(); // Server-side fetch
return <Card title="Revenue" value={revenue} />;
}
Best Practices
- Server Components by default – Only add
'use client'when needed for interactivity - Colocate data fetching – Fetch data in the component that needs it
- Use Suspense boundaries – Wrap async components for streaming
- Leverage caching – Use
revalidateand tags for efficient caching - Progressive enhancement – Server Actions work without JavaScript
- Route groups for organization – Use
(folder)to organize without affecting URLs - Error boundaries per segment – Add
error.tsxto critical routes - Metadata API – Use
generateMetadatafor dynamic SEO - Sanitize user content – Always use DOMPurify or similar when rendering HTML
Common Pitfalls
- Hydration mismatches – Server/client rendering differences (dates, random values)
- Over-using ‘use client’ – Pushing client boundary too high in the tree
- Waterfall fetching – Not parallelizing independent data fetches
- Missing loading states – Forgetting
loading.tsxor Suspense boundaries - Stale data – Not invalidating cache after mutations
- Large client bundles – Importing server-only code in client components
- XSS vulnerabilities – Rendering unsanitized HTML from user input