Next.js Server Components — How They Work
Advertisement
Next.js Server Components — How They Work
Server Components represent a paradigm shift in React development. They allow components to run exclusively on the server, sending only HTML to the browser.
- Next.js Server Components — How They Work
- What Are Server Components?
- Client Components vs Server Components
- When to Use Server Components
- Rendering Server Components
- Mixing Server and Client Components
- Data Fetching in Server Components
- Performance Benefits
- Error Handling
- Server Component Patterns
- Common Mistakes
- FAQ
What Are Server Components?
Server Components execute entirely on the server. They can:
- Access databases directly
- Keep sensitive API keys server-side
- Fetch data without creating API layers
- Render and send HTML to the client
// app/posts/page.tsx (Server Component by default)
async function getPosts() {
const posts = await db.posts.findMany()
return posts
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</article>
))}
</div>
)
}
Client Components vs Server Components
Server Components:
- Async/await support
- Database access
- No browser APIs
- Reduced bundle size
- Better security
Client Components (marked with 'use client'):
- Interactive features
- Browser APIs
- State and hooks
- Event listeners
When to Use Server Components
Use Server Components for:
- Pages that fetch data
- Reading from databases
- Protecting sensitive information
- Large dependencies you don't want in the client bundle
// app/dashboard/page.tsx
import { auth } from '@/lib/auth'
export default async function DashboardPage() {
const session = await auth()
if (!session) {
redirect('/login')
}
const userData = await db.users.findUnique({
where: { id: session.userId }
})
return (
<div>
<h1>Welcome, {userData.name}</h1>
<Dashboard user={userData} />
</div>
)
}
Rendering Server Components
Server Components render on the server, generating HTML sent to the browser:
// Pure Server Component - no JS on client
export default async function ServerCounter() {
const count = await getCountFromDatabase()
return <div>Count: {count}</div>
}
The browser receives pre-rendered HTML without JavaScript.
Mixing Server and Client Components
Nest Client Components within Server Components:
// app/dashboard/page.tsx (Server Component)
import { ClientChart } from '@/components/chart'
export default async function DashboardPage() {
const data = await fetchAnalytics()
return (
<div>
<h1>Dashboard</h1>
<ClientChart data={data} />
</div>
)
}
// components/chart.tsx
'use client'
import { useState } from 'react'
export function ClientChart({ data }) {
const [selectedMetric, setSelectedMetric] = useState('users')
return (
<div>
<select onChange={(e) => setSelectedMetric(e.target.value)}>
<option>users</option>
<option>revenue</option>
</select>
<Chart data={data} metric={selectedMetric} />
</div>
)
}
Data Fetching in Server Components
// Fetch at the component level
export default async function UserProfile({ userId }) {
const user = await fetch(`/api/users/${userId}`, {
next: { revalidate: 60 }
}).then(r => r.json())
return <div>{user.name}</div>
}
The revalidate option controls cache duration.
Performance Benefits
Server Components provide significant advantages:
- No unnecessary JavaScript shipped to clients
- Database queries run server-side, faster
- Sensitive data stays on the server
- Smaller initial page load
Error Handling
Server Components can throw errors that Next.js catches:
export default async function DataDisplay() {
try {
const data = await riskyDataFetch()
return <div>{data}</div>
} catch (error) {
return <div>Error loading data</div>
}
}
For unhandled errors, Next.js renders the nearest error.tsx boundary.
Server Component Patterns
Pattern 1: Loading Data
const data = await fetchData()
return <Component data={data} />
Pattern 2: Conditional Rendering
const user = await getUser()
if (!user) return <NotFound />
return <UserPage user={user} />
Pattern 3: Server-side Search
async function SearchResults({ query }) {
const results = await db.posts.findMany({
where: { title: { contains: query } }
})
return <div>{results.map(r => <Item key={r.id} item={r} />)}</div>
}
Common Mistakes
Mistake 1: Using hooks in Server Components
// WRONG
export default async function Page() {
const [count, setCount] = useState(0) // ERROR
return <div>{count}</div>
}
Mistake 2: Passing non-serializable data to Client Components
// WRONG
export default async function Page() {
const date = new Date() // Not serializable
return <ClientComponent date={date} />
}
// CORRECT
export default async function Page() {
const date = new Date().toISOString() // Serializable string
return <ClientComponent date={date} />
}
FAQ
Q: Can Server Components use context? A: No, but you can use context in Client Components nested within Server Components.
Q: Should all components be Server Components? A: By default, yes. Only use 'use client' when you need interactivity or hooks.
Q: How do I pass functions to Client Components from Server Components? A: You can't pass functions directly. Use Server Actions instead, which are callable server functions.
Understanding Server Components is crucial for building efficient Next.js applications.
Advertisement