| 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:
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
});
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>
);
}
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
export default async function ServerComp() {
const data = await fetchData();
return <div>{data}</div>;
}
'use client';
import { useState } from 'react';
export default function ClientComp() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
() {
data = ();
(
);
}
Server Actions for Mutations
'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 ;
db..({
: { id },
: { title, content }
});
();
{ : };
}
() {
db..({ : { id } });
();
}
() {
(
);
}
;
{ useFormStatus, useFormState } ;
{ createPost } ;
() {
{ pending } = ();
(
);
}
() {
[state, formAction] = (createPost, );
(
);
}
Data Fetching Patterns
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 }
}) {
const [user, posts] = await Promise.all([
getUser(params.id),
getUserPosts(params.id)
]);
return (
<div>
<UserInfo user={user} />
<UserPosts posts={posts} />
);
}
() {
user = ();
settings = (user.);
notifications = (user.);
(
);
}
{ } ;
() {
(
);
}
Streaming with Suspense
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() {
const data = await fetchRevenueData();
;
}
() {
orders = ({ : });
(
);
}
() {
(
);
}
Server Component Patterns
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>
);
}
async function ProductWithReviews({ id }: { id: string }) {
const [product, reviews] = await Promise.all([
fetchProduct(id),
fetchReviews(id)
]);
return (
{/* Client Component */}
);
}
() {
data = ();
(
);
}
() {
user = ();
(
);
}
Client Component Patterns
'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>
);
}
'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>
</>
{children}
);
}
() {
data = ();
(
);
}
;
{ createContext, useContext, useState } ;
= createContext<{
: ;
: ;
} | >();
() {
[theme, setTheme] = ();
(
);
}
;
{ useOptimistic } ;
{ addTodo } ;
() {
[optimisticTodos, addOptimisticTodo] = (
todos,
[
...state,
{ : .(), : newTodo, : }
]
);
() {
text = formData.() ;
(text);
(text);
}
(
);
}
Composition Strategies
export default async function Layout({
children
}: {
children: React.ReactNode
}) {
const user = await getUser();
return (
<div>
<ServerNav user={user} />
<Sidebar>
<ClientSidebarContent /> {/* Interactive sidebar */}
</Sidebar>
<main>{children}</main>
</div>
);
}
export default async function Page() {
const posts = await getPosts();
return (
<ClientLayout
sidebar={<ServerSidebar posts={posts} />}
main={<ServerContent posts={posts} />}
/>
);
}
export () {
data = ();
(
);
}
() {
user = ();
(
);
}
Server-Only Code
import 'server-only';
export async function getSecretData() {
const apiKey = process.env.SECRET_API_KEY;
const res = await fetch('https://api.example.com/secret', {
headers: {
Authorization: `Bearer ${apiKey}`
}
});
return res.json();
}
export function decryptData(encrypted: string) {
return decrypt(encrypted, process.env.ENCRYPTION_KEY);
}
import { getSecretData } from '@/lib/server-only-utils';
export default async function Dashboard() {
const data = await getSecretData();
return <div>{data.publicInfo};
}
;
env = {
: process..!,
: process..!,
: process..!
} ;
{ env } ;
() {
data = (, {
: { : env. }
});
;
}
Performance Implications
import { marked } from 'marked';
export default async function MarkdownPage({ content }: {
content: string
}) {
const html = marked(content);
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
import sharp from 'sharp';
export default async function OptimizedImage({ src }: { src: string }) {
const buffer = await fetch(src).then(r => r.arrayBuffer());
const optimized = await sharp(buffer)
.resize(800, 600)
.webp()
.toBuffer();
base64 = optimized.();
;
}
() {
rawData = ();
processed = (rawData);
stats = (processed);
;
}
() {
[analytics, sales, inventory] = .([
(),
(),
()
]);
report = (analytics, sales, inventory);
;
}
Error Handling in Server Components
export default async function PostPage({ params }: {
params: { id: string }
}) {
const post = await db.post.findUnique({
where: { id: params.id }
});
if (!post) {
throw new Error('Post not found');
}
return <Article post={post} />;
}
import { notFound } from 'next/navigation';
export default async function UserPage({ params }: {
params: { id: string }
}) {
const user = await db.user.findUnique({
where: { id: params.id }
});
if (!user) {
notFound();
}
return <UserProfile user={user} />;
}
() {
{
data = ();
;
} (error) {
.(, error);
;
}
}
() {
data = ();
;
}
When to Use This Skill
Use nextjs-server-components when you need to:
- Fetch data on the server
- Access backend resources directly
- Keep sensitive data server-side
- Reduce client bundle size
- Improve initial page load
- Build SEO-optimized pages
- Stream content to the client
- Implement zero-JS pages
- Optimize for performance
- Access databases directly
- Use server-only libraries
- Perform heavy computations
Best Practices
-
Use server components by default - Only add 'use client' when you need
interactivity, state, or browser APIs.
-
Fetch data close to where it's used - Colocate data fetching with the
components that use the data.
-
Leverage parallel data fetching - Use Promise.all to fetch independent
data sources simultaneously.
-
Use streaming for better UX - Wrap slow components in Suspense to show
content as it loads.
-
Keep sensitive logic server-side - Database queries, API keys, and
business logic should stay on server.
-
Minimize client components - Each 'use client' boundary increases
JavaScript bundle size.
-
Cache data appropriately - Use Next.js caching strategies (revalidate,
no-store) based on data freshness needs.
-
Handle errors gracefully - Use error boundaries and not-found pages for
better error handling.
-
Use TypeScript for type safety - Define proper types for props and data
to catch errors early.
-
Test server component behavior - Verify data fetching, error handling,
and rendering in tests.
Common Pitfalls
-
Using client-only APIs in server components - window, localStorage,
document are not available on server.
-
Not handling async errors properly - Unhandled promise rejections crash
the entire page.
-
Mixing server and client state - State management libraries don't work
in server components.
-
Overusing client components - Adding 'use client' unnecessarily increases
bundle size and reduces performance.
-
Not leveraging data streaming - Missing Suspense boundaries cause slow
page loads instead of progressive rendering.
-
Ignoring caching strategies - Not setting revalidate times leads to stale
data or unnecessary requests.
-
Not optimizing data fetching - Sequential fetches cause waterfalls;
use parallel fetching when possible.
-
Exposing sensitive data - Accidentally passing secrets or API keys to
client components.
-
Not handling loading states - Missing loading UI during data fetching
creates poor user experience.
-
Misunderstanding component boundaries - Server components can't be
imported into client components directly.
Resources