Skip to main content

nextjs-server-components

Use when next.js Server Components for optimal performance. Use when building data-intensive Next.js applications.

Source facts

Repository
TheBushidoCollective/han
Last source activity
February 11, 2026 at 17:47
Detected SKILL.md language
English
Stars
198
Forks
22

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
nextjs-server-components
user-invocable
false
description
Use when next.js Server Components for optimal performance. Use when building data-intensive Next.js applications.
allowed-tools
["Bash","Read"]
# Next.js Server Components Master Server Components in Next.js to build high-performance applications with server-side rendering and data fetching. ## Server Components Basics In Next.js App Router, all components are Server Components by default: ```typescript // app/posts/page.tsx (Server Component by default) async function getPosts() { const res = await fetch('https://api.example.com/posts', { next: { revalidate: 3600 } // Cache for 1 hour }); if (!res.ok) throw new Error('Failed to fetch posts'); return res.json(); } export default async function Posts() { const posts = await getPosts(); return ( <div> <h1>Blog Posts</h1> {posts.map((post: Post) => ( <article key={post.id}> <h2>{post.title}</h2> <p>{post.content}</p> <span>{new Date(post.date).toLocaleDateString()}</span> </article> ))} </div> ); } // Direct database access (server-only) import { db } from '@/lib/db'; export default async function Users() { const users = await db.user.findMany({ select: { id: true, name: true, email: true } }); return ( <div> {users.map(user => ( <div key={user.id}> {user.name} - {user.email} </div> ))} </div> ); } ``` ## Server vs Client Components Decision Tree ```typescript // Use Server Components when: // - Fetching data // - Accessing backend resources directly // - Keeping sensitive information on server // - Keeping large dependencies on server // Server Component (default) export default async function ServerComp() { const data = await fetchData(); return <div>{data}</div>; } // Use Client Components when: // - Using interactivity (onClick, onChange, etc.) // - Using state or lifecycle hooks (useState, useEffect) // - Using browser-only APIs (localStorage, window, etc.) // - Using custom hooks that depend on state/effects // - Using React Context // Client Component 'use client'; import { useState } from 'react'; export default function ClientComp() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); } // Composition: Server Component with Client Component export default async function Page() { const data = await fetchData(); // Server-side return ( <div> <ServerContent data={data} /> <InteractiveButton /> {/* Client Component */} </div> ); } ``` ## Server Actions for Mutations ```typescript // app/actions.ts - Server Actions 'use server'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; export async function createPost(formData: FormData) { const title = formData.get('title') as string; const content = formData.get('content') as string; const post = await db.post.create({ data: { title, content } }); revalidatePath('/posts'); redirect(`/posts/${post.id}`); } export async function updatePost(id: string, formData: FormData) { const title = formData.get('title') as string; const content = formData.get('content') as string; await db.post.update({ where: { id }, data: { title, content } }); revalidatePath(`/posts/${id}`); return { success: true }; } export async function deletePost(id: string) { await db.post.delete({ where: { id } }); revalidatePath('/posts'); } // app/posts/new/page.tsx - Using Server Actions export default function NewPost() { return ( <form action={createPost}> <input name="title" placeholder="Title" required /> <textarea name="content" placeholder="Content" required /> <button type="submit">Create Post</button> </form> ); } // With client-side enhancement 'use client'; import { useFormStatus, useFormState } from 'react-dom'; import { createPost } from './actions'; function SubmitButton() { const { pending } = useFormStatus(); return ( <button type="submit" disabled={pending}> {pending ? 'Creating...' : 'Create Post'} </button> ); } export default function NewPostForm() { const [state, formAction] = useFormState(createPost, null); return ( <form action={formAction}> <input name="title" required /> <textarea name="content" required /> {state?.error && <p className="error">{state.error}</p>} <SubmitButton /> </form> ); } ``` ## Data Fetching Patterns ```typescript // Parallel data fetching async function getUser(id: string) { const res = await fetch(`/api/users/${id}`); return res.json(); } async function getUserPosts(id: string) { const res = await fetch(`/api/users/${id}/posts`); return res.json(); } export default async function UserProfile({ params }: { params: { id: string } }) { // Fetch in parallel const [user, posts] = await Promise.all([ getUser(params.id), getUserPosts(params.id) ]); return ( <div> <UserInfo user={user} /> <UserPosts posts={posts} /> </div> ); } // Sequential data fetching (when needed) export default async function Dashboard() { // Fetch user first const user = await getUser(); // Then fetch user-specific data const settings = await getUserSettings(user.id); const notifications = await getUserNotifications(user.id); return ( <div> <UserHeader user={user} settings={settings} /> <Notifications items={notifications} /> </div> ); } // Waterfall optimization with Suspense import { Suspense } from 'react'; export default function Page() { return ( <div> {/* User loads first */} <Suspense fallback={<UserSkeleton />}> <User /> </Suspense> {/* These load in parallel */} <div className="grid"> <Suspense fallback={<SettingsSkeleton />}> <Settings /> </Suspense> <Suspense fallback={<NotificationsSkeleton />}> <Notifications /> </Suspense> </div> </div> ); } ``` ## Streaming with Suspense ```typescript // app/dashboard/page.tsx import { Suspense } from 'react'; export default function Dashboard() { return ( <div> <h1>Dashboard</h1> {/* Fast component renders immediately */} <QuickStats /> {/* Slow components stream in */} <Suspense fallback={<ChartSkeleton />}> <RevenueChart /> </Suspense> <Suspense fallback={<TableSkeleton />}> <RecentOrders /> </Suspense> <Suspense fallback={<ActivitySkeleton />}> <ActivityFeed /> </Suspense> </div> ); } async function RevenueChart() { // Slow data fetch const data = await fetchRevenueData(); return <Chart data={data} />; } async function RecentOrders() { const orders = await fetchOrders({ limit: 10 }); return ( <table> {orders.map(order => ( <tr key={order.id}> <td>{order.id}</td> <td>{order.total}</td> </tr> ))} </table> ); } // Nested Suspense for granular streaming async function ActivityFeed() { return ( <div> <h2>Activity</h2> <Suspense fallback={<div>Loading comments...</div>}> <Comments /> </Suspense> <Suspense fallback={<div>Loading likes...</div>}> <Likes /> </Suspense> </div> ); } ``` ## Server Component Patterns ```typescript // Pattern 1: Data fetching in Server Component async function fetchProduct(id: string) { const product = await db.product.findUnique({ where: { id } }); return product; } export default async function ProductPage({ params }: { params: { id: string } }) { const product = await fetchProduct(params.id); return ( <div> <ProductDetails product={product} /> <AddToCartButton productId={product.id} /> {/* Client Component */} </div> ); } // Pattern 2: Server Component as data provider async function ProductWithReviews({ id }: { id: string }) { const [product, reviews] = await Promise.all([ fetchProduct(id), fetchReviews(id) ]); return ( <> <ProductInfo product={product} /> <ReviewList reviews={reviews} /> <ReviewForm productId={id} /> {/* Client Component */} </> ); } // Pattern 3: Composing Server and Client Components export default async function Page() { const data = await fetchData(); return ( <ClientWrapper> {/* Server Component children work inside Client Components */} <ServerContent data={data} /> </ClientWrapper> ); } // Pattern 4: Props passing from Server to Client export default async function Layout({ children }: { children: React.ReactNode }) { const user = await getUser(); return ( <div> <ClientHeader user={user} /> {/* Pass server data to client */} <main>{children}</main> </div> ); } ``` ## Client Component Patterns ```typescript // Pattern 1: Minimal Client Component 'use client'; import { useState } from 'react'; export function Counter({ initialCount = 0 }: { initialCount?: number }) { const [count, setCount] = useState(initialCount); return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); } // Pattern 2: Client Component with Server Component children 'use client'; import { useState } from 'react'; export function Tabs({ children }: { children: React.ReactNode }) { const [activeTab, setActiveTab] = useState(0); return ( <div> <div className="tabs"> <button onClick={() => setActiveTab(0)}>Tab 1</button> <button onClick={() => setActiveTab(1)}>Tab 2</button> </div> <div>{children}</div> </div> ); } // Usage: Server Component children work export default async function Page() { const data = await fetchData(); return ( <Tabs> <ServerContent data={data} /> </Tabs> ); } // Pattern 3: Client wrapper with context 'use client'; import { createContext, useContext, useState } from 'react'; const ThemeContext = createContext<{ theme: string; setTheme: (theme: string) => void; } | null>(null); export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={{ theme, setTheme }}>
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub