Master Next.js 14+ App Router with Server Components, streaming, parallel routes, and advanced data fetching. Use when building Next.js applications, implementing SSR/SSG, or optimizing React Server Components.
Provides patterns for Next.js 14+ App Router, Server Components, streaming, and Server Actions.
/plugin marketplace add kinneyyan/prompts/plugin install frontend-big-brother@kinney-yan-toolsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Comprehensive patterns for Next.js 14+ App Router architecture, Server Components, and modern full-stack React development.
| Mode | Where | When to Use |
|---|---|---|
| Server Components | Server only | Data fetching, heavy computation, secrets |
| Client Components | Browser | Interactivity, hooks, browser APIs |
| Static | Build time | Content that rarely changes |
| Dynamic | Request time | Personalized or real-time data |
| Streaming | Progressive | Large pages, slow data sources |
app/
├── layout.tsx # Shared UI wrapper
├── page.tsx # Route UI
├── loading.tsx # Loading UI (Suspense)
├── error.tsx # Error boundary
├── not-found.tsx # 404 UI
├── route.ts # API endpoint
├── template.tsx # Re-mounted layout
├── default.tsx # Parallel route fallback
└── opengraph-image.tsx # OG image generation
// app/layout.tsx
import { Inter } from 'next/font/google';
import { Providers } from './providers';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: { default: 'My App', template: '%s | My App' },
description: 'Built with Next.js App Router',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<Providers>{children}</Providers>
</body>
</html>
);
}
// app/page.tsx - Server Component by default
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }, // ISR: revalidate every hour
});
return res.json();
}
export default async function HomePage() {
const products = await getProducts();
return (
<main>
<h1>Products</h1>
<ProductGrid products={products} />
</main>
);
}
// app/products/page.tsx
import { Suspense } from 'react';
import { ProductList, ProductListSkeleton } from '@/components/products';
import { FilterSidebar } from '@/components/filters';
interface SearchParams {
category?: string;
sort?: 'price' | 'name' | 'date';
page?: string;
}
export default async function ProductsPage({ searchParams }: { searchParams: Promise<SearchParams> }) {
const params = await searchParams;
return (
<div className="flex gap-8">
<FilterSidebar />
<Suspense key={JSON.stringify(params)} fallback={<ProductListSkeleton />}>
<ProductList category={params.category} sort={params.sort} page={Number(params.page) || 1} />
</Suspense>
</div>
);
}
// components/products/ProductList.tsx - Server Component
async function getProducts(filters: ProductFilters) {
const res = await fetch(`${process.env.API_URL}/products?${new URLSearchParams(filters)}`, {
next: { tags: ['products'] },
});
if (!res.ok) throw new Error('Failed to fetch products');
return res.json();
}
export async function ProductList({ category, sort, page }: ProductFilters) {
const { products, totalPages } = await getProducts({ category, sort, page });
return (
<div>
<div className="grid grid-cols-3 gap-4">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
<Pagination currentPage={page} totalPages={totalPages} />
</div>
);
}
// components/products/AddToCartButton.tsx
'use client';
import { useState, useTransition } from 'react';
import { addToCart } from '@/app/actions/cart';
export function AddToCartButton({ productId }: { productId: string }) {
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const handleClick = () => {
setError(null);
startTransition(async () => {
const result = await addToCart(productId);
if (result.error) {
setError(result.error);
}
});
};
return (
<div>
<button onClick={handleClick} disabled={isPending} className="btn-primary">
{isPending ? 'Adding...' : 'Add to Cart'}
</button>
{error && <p className="text-red-500 text-sm">{error}</p>}
</div>
);
}
// app/actions/cart.ts
'use server';
import { revalidateTag } from 'next/cache';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export async function addToCart(productId: string) {
const cookieStore = await cookies();
const sessionId = cookieStore.get('session')?.value;
if (!sessionId) {
redirect('/login');
}
try {
await db.cart.upsert({
where: { sessionId_productId: { sessionId, productId } },
update: { quantity: { increment: 1 } },
create: { sessionId, productId, quantity: 1 },
});
revalidateTag('cart');
return { success: true };
} catch (error) {
return { error: 'Failed to add item to cart' };
}
}
export async function checkout(formData: FormData) {
const address = formData.get('address') as string;
const payment = formData.get('payment') as string;
// Validate
if (!address || !payment) {
return { error: 'Missing required fields' };
}
// Process order
const order = await processOrder({ address, payment });
// Redirect to confirmation
redirect(`/orders/${order.id}/confirmation`);
}
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
}) {
return (
<div className="dashboard-grid">
<main>{children}</main>
<aside className="analytics-panel">{analytics}</aside>
<aside className="team-panel">{team}</aside>
</div>
);
}
// app/dashboard/@analytics/page.tsx
export default async function AnalyticsSlot() {
const stats = await getAnalytics();
return <AnalyticsChart data={stats} />;
}
// app/dashboard/@analytics/loading.tsx
export default function AnalyticsLoading() {
return <ChartSkeleton />;
}
// app/dashboard/@team/page.tsx
export default async function TeamSlot() {
const members = await getTeamMembers();
return <TeamList members={members} />;
}
// File structure for photo modal
// app/
// ├── @modal/
// │ ├── (.)photos/[id]/page.tsx # Intercept
// │ └── default.tsx
// ├── photos/
// │ └── [id]/page.tsx # Full page
// └── layout.tsx
// app/@modal/(.)photos/[id]/page.tsx
import { Modal } from '@/components/Modal';
import { PhotoDetail } from '@/components/PhotoDetail';
export default async function PhotoModal({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const photo = await getPhoto(id);
return (
<Modal>
<PhotoDetail photo={photo} />
</Modal>
);
}
// app/photos/[id]/page.tsx - Full page version
export default async function PhotoPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const photo = await getPhoto(id);
return (
<div className="photo-page">
<PhotoDetail photo={photo} />
<RelatedPhotos photoId={id} />
</div>
);
}
// app/layout.tsx
export default function RootLayout({ children, modal }: { children: React.ReactNode; modal: React.ReactNode }) {
return (
<html>
<body>
{children}
{modal}
</body>
</html>
);
}
// app/product/[id]/page.tsx
import { Suspense } from 'react';
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
// This data loads first (blocking)
const product = await getProduct(id);
return (
<div>
{/* Immediate render */}
<ProductHeader product={product} />
{/* Stream in reviews */}
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews productId={id} />
</Suspense>
{/* Stream in recommendations */}
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations productId={id} />
</Suspense>
</div>
);
}
// These components fetch their own data
async function Reviews({ productId }: { productId: string }) {
const reviews = await getReviews(productId); // Slow API
return <ReviewList reviews={reviews} />;
}
async function Recommendations({ productId }: { productId: string }) {
const products = await getRecommendations(productId); // ML-based, slow
return <ProductCarousel products={products} />;
}
// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const category = searchParams.get('category');
const products = await db.product.findMany({
where: category ? { category } : undefined,
take: 20,
});
return NextResponse.json(products);
}
export async function POST(request: NextRequest) {
const body = await request.json();
const product = await db.product.create({
data: body,
});
return NextResponse.json(product, { status: 201 });
}
// app/api/products/[id]/route.ts
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = await db.product.findUnique({ where: { id } });
if (!product) {
return NextResponse.json({ error: 'Product not found' }, { status: 404 });
}
return NextResponse.json(product);
}
// app/products/[slug]/page.tsx
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
type Props = {
params: Promise<{ slug: string }>;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const product = await getProduct(slug);
if (!product) return {};
return {
title: product.name,
description: product.description,
openGraph: {
title: product.name,
description: product.description,
images: [{ url: product.image, width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
title: product.name,
description: product.description,
images: [product.image],
},
};
}
export async function generateStaticParams() {
const products = await db.product.findMany({ select: { slug: true } });
return products.map((p) => ({ slug: p.slug }));
}
export default async function ProductPage({ params }: Props) {
const { slug } = await params;
const product = await getProduct(slug);
if (!product) notFound();
return <ProductDetail product={product} />;
}
// No cache (always fresh)
fetch(url, { cache: 'no-store' });
// Cache forever (static)
fetch(url, { cache: 'force-cache' });
// ISR - revalidate after 60 seconds
fetch(url, { next: { revalidate: 60 } });
// Tag-based invalidation
fetch(url, { next: { tags: ['products'] } });
// Invalidate via Server Action
('use server');
import { revalidateTag, revalidatePath } from 'next/cache';
export async function updateProduct(id: string, data: ProductData) {
await db.product.update({ where: { id }, data });
revalidateTag('products');
revalidatePath('/products');
}
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.