Next.js best practices, server components, app router patterns, caching strategies, and full-stack architecture
npx skills add https://github.com/cosmicstack-labs/mercury-agent-skills --skill nextjs-patterns
Comprehensive guide to building production-grade Next.js applications with the App Router, Server Components, and modern React patterns.
| Aspect | App Router | Pages Router |
|--------|-----------|--------------|
| Components | Server Components by default | Client Components only |
| Routing | File-system based with layout nesting | File-system based, flat |
| Data Fetching | Server-side fetch, use, cache primitives | getServerSideProps, getStaticProps |
| Loading States | loading.js files | Manual implementation |
| Error Handling | error.js files | Manual implementation |
| Streaming | Built-in with Suspense boundaries | Not supported |
my-app/
├── app/
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page
│ ├── loading.tsx # Root loading state
│ ├── error.tsx # Root error boundary
│ ├── not-found.tsx # 404 page
│ ├── (auth)/ # Route group
│ │ ├── login/page.tsx
│ │ └── register/page.tsx
│ ├── dashboard/
│ │ ├── layout.tsx # Dashboard layout
│ │ ├── page.tsx # Dashboard home
│ │ ├── loading.tsx
│ │ └── settings/
│ │ └── page.tsx
│ └── api/
│ └── [...route]/route.ts # API handlers
├── components/
│ ├── ui/ # Shared UI components
│ └── features/ # Feature-specific components
├── lib/
│ ├── db.ts # Database client
│ ├── auth.ts # Auth utilities
│ └── utils.ts # Shared utilities
└── public/
└── images/
// ✅ SERVER COMPONENT (default) — Use for:
// - Data fetching from databases/APIs
// - Accessing backend resources directly
// - Keeping sensitive logic on server
// - Reducing client bundle size
// - SEO-critical content
async function ProductPage({ params }: { params: { id: string } }) {
const product = await db.product.findUnique({ where: { id: params.id } });
return <ProductDetail product={product} />;
}
// ✅ CLIENT COMPONENT — Use for:
// - Interactivity (onClick, onChange, etc.)
// - useState, useReducer, useEffect
// - Browser-only APIs
// - Custom hooks
// - Event listeners
'use client';
function AddToCartButton({ productId }: { productId: string }) {
const [added, setAdded] = useState(false);
return (
<button onClick={() => { addToCart(productId); setAdded(true); }}>
{added ? 'Added!' : 'Add to Cart'}
</button>
);
}
// ✅ CORRECT: Server Component wrapping Client Component
async function ProductPage({ id }: { id: string }) {
const product = await getProduct(id);
// Server component passes data as props to client component
return (
<div>
<ProductDetails product={product} />
<AddToCartButton productId={id} />
</div>
);
}
// ❌ WRONG: Client Component importing Server Component
'use client';
import ServerComponent from './ServerComponent'; // ❌ Won't work
function ClientPage() {
return <ServerComponent />; // Error: Cannot import server component into client
}
// ✅ CORRECT: Pass Server Component as children
'use client';
function ClientLayout({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
{isOpen && children} {/* Server component rendered here */}
</div>
);
}
// app/products/page.tsx
async function ProductsPage() {
// Direct database access — no API route needed
const products = await db.product.findMany({
take: 20,
orderBy: { createdAt: 'desc' },
});
return <ProductGrid products={products} />;
}
// With parallel fetching for multiple data sources
async function DashboardPage() {
const [user, posts, analytics] = await Promise.all([
getCurrentUser(),
getRecentPosts(),
getAnalytics(),
]);
return (
<DashboardShell user={user}>
<PostList posts={posts} />
<AnalyticsChart data={analytics} />
</DashboardShell>
);
}
// Time-based revalidation (ISR)
export const revalidate = 3600; // Revalidate every hour
// On-demand revalidation (revalidateTag / revalidatePath)
'use server';
export async function publishPost(formData: FormData) {
const post = await createPost(formData);
revalidateTag('posts');
revalidatePath('/blog');
redirect(`/blog/${post.slug}`);
}
// Dynamic data — no caching
export const dynamic = 'force-dynamic';
// Static data — cache forever
export const dynamic = 'force-static';
// app/dashboard/page.tsx
import { Suspense } from 'react';
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<DashboardSkeleton />}>
<DashboardContent />
</Suspense>
<Suspense fallback={<ActivitySkeleton />}>
<RecentActivity />
</Suspense>
</div>
);
}
async function DashboardContent() {
const data = await fetchDashboardData(); // This suspends
return <DashboardCharts data={data} />;
}
async function RecentActivity() {
const activity = await fetchRecentActivity(); // This loads independently
return <ActivityFeed items={activity} />;
}
// app/api/products/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '20');
const products = await db.product.findMany({
skip: (page - 1) * limit,
take: limit,
});
return Response.json({ products, page, limit });
}
export async function POST(request: Request) {
const body = await request.json();
const product = await db.product.create({ data: body });
return Response.json(product, { status: 201 });
}
// app/(marketing)/page.tsx — Public marketing pages
// app/(dashboard)/page.tsx — Authenticated dashboard
// app/(auth)/login/page.tsx — Auth pages with different layout
// middleware.ts
export function middleware(request: NextRequest) {
const token = request.cookies.get('session');
const { pathname } = request.nextUrl;
// Protected routes
if (pathname.startsWith('/dashboard') && !token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
// app/dashboard/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
'use client' when you need interactivity.Promise.all() for independent data fetches.no-store or revalidate explicitly.Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling
Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification
Take cosmicstack-labs/nextjs-patterns from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.