nextjs-app-router-mastery
Installation
SKILL.md
Next.js App Router Mastery
Core Concepts
- Server Components by Default - Components are server-rendered unless marked
'use client' - Streaming & Suspense - Progressive rendering with loading states
- Parallel Routes - Simultaneous route rendering
- Intercepting Routes - Modal patterns without navigation
File Conventions
app/
layout.tsx # Root layout (required)
page.tsx # Route UI
loading.tsx # Loading UI (Suspense boundary)
error.tsx # Error boundary
not-found.tsx # 404 UI
route.ts # API route handler
template.tsx # Re-renders on navigation
default.tsx # Parallel route fallback
Data Fetching Patterns
Server Component Fetching
// app/posts/page.tsx - Server Component
async function PostsPage() {
const posts = await fetchPosts(); // Direct fetch, no useEffect
return <PostList posts={posts} />;
}
Parallel Data Fetching
async function Dashboard() {
// Parallel fetches - don't await sequentially
const [user, posts, analytics] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchAnalytics(),
]);
return <DashboardView user={user} posts={posts} analytics={analytics} />;
}
Streaming with Suspense
export default function Page() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<StatsSkeleton />}>
<Stats /> {/* Async component */}
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<Chart /> {/* Streams in when ready */}
</Suspense>
</div>
);
}
Server Actions
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
await db.posts.create({ data: { title } });
revalidatePath('/posts');
}
Route Handlers
// app/api/posts/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const posts = await fetchPosts();
return NextResponse.json(posts);
}
export async function POST(request: Request) {
const body = await request.json();
const post = await createPost(body);
return NextResponse.json(post, { status: 201 });
}
Caching Strategies
// Force dynamic rendering
export const dynamic = 'force-dynamic';
// Revalidate every 60 seconds
export const revalidate = 60;
// Static generation
export const dynamic = 'force-static';
// Per-fetch revalidation
fetch(url, { next: { revalidate: 3600 } });
// On-demand revalidation
revalidatePath('/posts');
revalidateTag('posts');
Metadata
// Static metadata
export const metadata: Metadata = {
title: 'My App',
description: 'App description',
};
// Dynamic metadata
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await fetchPost(params.id);
return { title: post.title };
}
Best Practices
- Keep client components at the leaves of the tree
- Pass serializable props from server to client components
- Use
loading.tsxfor route-level loading states - Colocate data fetching with the component that uses it
- Use route groups
(group)for organization without affecting URL
Related skills
More from autohandai/community-skills
api-design-restful
RESTful API design patterns, error handling, and documentation
31cli-tool-development
Build professional CLI tools with Node.js, commander, and Ink
16testing-strategies
Comprehensive testing strategies with Vitest, Jest, and Testing Library
12react-component-architecture
Modern React component patterns with hooks, composition, and TypeScript
12error-handling-patterns
Robust error handling patterns for TypeScript applications
10git-workflow-mastery
Advanced Git workflows, branching strategies, and commit conventions
10