Use this skill when the user asks to "create server component", "add React Server Component", "use async component", "setup Next.js 15", "use React 19", "implement PPR", "add Server Actions", mentions "use server", "use client", "Suspense boundaries", or wants to build modern server-rendered components with React 19 and Next.js 15 patterns.
/plugin marketplace add flight505/storybook-assistant-plugin/plugin install flight505-storybook-assistant@flight505/storybook-assistant-pluginThis skill inherits all available tools. When active, it can use any tool Claude has access to.
templates/client-component.template.tsxtemplates/server-component.template.tsxGenerate modern React Server Components with React 19 and Next.js 15 patterns including async/await data fetching, streaming with Suspense, Server Actions, and Partial Prerendering (PPR).
This skill provides templates and best practices for building server-first applications with client-side interactivity only where needed.
Modern server-rendered components with:
Strategic client-side interactivity:
Progressive rendering patterns:
Latest React patterns:
Framework-specific optimizations:
// app/ProductList.tsx (Server Component)
import { fetchProducts } from '@/lib/api';
import { ProductCard } from './ProductCard';
// ✨ Async Server Component
export default async function ProductList({ category }: Props) {
// Direct data fetching - no useEffect!
const products = await fetchProducts(category);
return (
<div className="grid grid-cols-3 gap-4">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// app/ProductCard.client.tsx
'use client'; // ✨ Client boundary
import { useState } from 'react';
import { addToCart } from '@/actions/cart';
export function ProductCard({ product }: Props) {
const [loading, setLoading] = useState(false);
const handleAddToCart = async () => {
setLoading(true);
await addToCart(product.id);
setLoading(false);
};
return (
<article>
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price}</p>
{/* ✨ Client-side interactivity */}
<button onClick={handleAddToCart} disabled={loading}>
{loading ? 'Adding...' : 'Add to Cart'}
</button>
</article>
);
}
// app/Dashboard.tsx
import { Suspense } from 'react';
import { UserStats } from './UserStats';
import { RecentActivity } from './RecentActivity';
import { Skeleton } from './Skeleton';
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
{/* ✨ Stream components independently */}
<Suspense fallback={<Skeleton type="stats" />}>
<UserStats /> {/* Slow fetch */}
</Suspense>
<Suspense fallback={<Skeleton type="activity" />}>
<RecentActivity /> {/* Fast fetch */}
</Suspense>
</div>
);
}
// app/CommentForm.tsx
'use client';
import { useActionState } from 'react';
import { postComment } from '@/actions/comments';
export function CommentForm({ postId }: Props) {
const [state, submitAction, isPending] = useActionState(
async (prevState, formData) => {
const comment = formData.get('comment') as string;
try {
await postComment(postId, comment);
return { success: true, message: 'Comment posted!' };
} catch (error) {
return { success: false, message: 'Failed to post comment' };
}
},
{ success: false, message: '' }
);
return (
<form action={submitAction}>
<textarea name="comment" required />
<button disabled={isPending}>
{isPending ? 'Posting...' : 'Post Comment'}
</button>
{state.message && <p>{state.message}</p>}
</form>
);
}
// actions/comments.ts
'use server';
export async function postComment(postId: string, comment: string) {
const user = await getCurrentUser();
await db.comments.create({
data: { postId, userId: user.id, content: comment }
});
}
// app/page.tsx
export const experimental_ppr = true;
export default function ProductPage() {
return (
<>
{/* ✨ Static: Prerendered at build time */}
<ProductHeader />
<ProductDescription />
{/* ✨ Dynamic: Streamed for each request */}
<Suspense fallback={<ReviewsSkeleton />}>
<UserReviews /> {/* Personalized content */}
</Suspense>
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations /> {/* User-specific */}
</Suspense>
</>
);
}
// templates/server-component.template
import { Suspense } from 'react';
interface {{COMPONENT_NAME}}Props {
{{PROP_NAME}}: {{PROP_TYPE}};
}
export default async function {{COMPONENT_NAME}}({
{{PROP_NAME}}
}: {{COMPONENT_NAME}}Props) {
// ✨ Server-side data fetching
const data = await fetch{{DATA_NAME}}({{PROP_NAME}});
return (
<div>
<h2>{{COMPONENT_NAME}}</h2>
{/* Render data */}
</div>
);
}
// templates/client-component.template
'use client';
import { useState } from 'react';
interface {{COMPONENT_NAME}}Props {
{{PROP_NAME}}: {{PROP_TYPE}};
}
export function {{COMPONENT_NAME}}({
{{PROP_NAME}}
}: {{COMPONENT_NAME}}Props) {
const [state, setState] = useState(initialValue);
const handleAction = () => {
// Client-side logic
};
return (
<div>
{/* Interactive UI */}
</div>
);
}
// ProductList.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import ProductList from './ProductList';
const meta: Meta<typeof ProductList> = {
title: 'Server/ProductList',
component: ProductList,
parameters: {
nextjs: {
appDirectory: true, // Enable App Router
},
},
};
export default meta;
type Story = StoryObj<typeof ProductList>;
// ✨ Mock server data
export const Default: Story = {
parameters: {
async loaders() {
return {
products: [
{ id: 1, name: 'Product 1', price: 29.99 },
{ id: 2, name: 'Product 2', price: 39.99 },
],
};
},
},
};
export const Loading: Story = {
parameters: {
async loaders() {
// Simulate slow load
await new Promise(resolve => setTimeout(resolve, 2000));
return { products: [] };
},
},
};
export const Error: Story = {
parameters: {
async loaders() {
throw new Error('Failed to fetch products');
},
},
};
Does it need interactivity (state, events)?
├─ YES → Client Component ('use client')
└─ NO → Can it fetch data?
├─ YES → Server Component (async)
└─ NO → Server Component (static)
app/
├── ProductList/
│ ├── ProductList.tsx # Server Component
│ ├── ProductCard.client.tsx # Client Component
│ ├── ProductCard.stories.tsx # Storybook
│ └── ProductList.test.tsx # Tests
// ✅ Server Component - Direct fetch
async function ServerComponent() {
const data = await fetchData();
return <div>{data}</div>;
}
// ✅ Client Component - Props from server
function ClientComponent({ data }: { data: Data }) {
const [selected, setSelected] = useState(data[0]);
return <div onClick={() => setSelected(data[1])}>{selected}</div>;
}
// ❌ Client Component - Don't fetch on client if avoidable
function ClientComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData); // Prefer server fetch
}, []);
}
// ❌ Entire component is client
'use client';
export default function Page() {
return (
<div>
<Header /> {/* Could be server */}
<Content /> {/* Could be server */}
<InteractiveButton /> {/* Needs client */}
</div>
);
}
// ✅ Only interactive part is client
export default function Page() {
return (
<div>
<Header /> {/* Server */}
<Content /> {/* Server */}
<InteractiveButton /> {/* Client */}
</div>
);
}
skills/server-components/templates/skills/server-components/examples/server-patterns.mdBuild modern React applications with:
Result: Faster, smaller, more maintainable applications with better UX.