# React Server Components
## Server Components (Default)
```tsx
// app/page.tsx - Server Component by default
import { db } from '@/lib/db'
export default async function Page() {
// Data fetching in Server Component
const users = await db.user.findMany()
return (
Users
{users.map(user => (
{user.name}
))}
)
}
```
## Benefits of Server Components
- **Zero bundle size** - Server Components don't add JavaScript to client bundle
- **Direct backend access** - Query databases, read files, use secrets
- **Automatic code splitting** - Only Client Components add to bundle
- **Streaming** - Send UI progressively as data loads
- **No client-side waterfalls** - Fetch all data in parallel on server
## Client Components
```tsx
// components/counter.tsx
'use client' // Required directive
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
setCount(count + 1)}>
Count: {count}
)
}
```
## When to Use Client Components
Use `'use client'` when you need:
- **Interactivity** - onClick, onChange, event handlers
- **State** - useState, useReducer
- **Effects** - useEffect, useLayoutEffect
- **Browser APIs** - localStorage, window, document
- **Custom hooks** - Any hook using client-only features
- **Class components** - Component lifecycle methods
## Composition Pattern
```tsx
// app/page.tsx - Server Component
import { ClientWrapper } from './client-wrapper'
import { db } from '@/lib/db'
export default async function Page() {
const data = await db.query()
return (
{/* Server Component content */}
Server Content
{/* Pass data to Client Component */}
{/* Server Component as children */}
)
}
// components/client-wrapper.tsx
'use client'
export function ClientWrapper({
children,
initialData,
}: {
children: React.ReactNode
initialData: Data
}) {
const [data, setData] = useState(initialData)
return (
{/* Client Component UI */}
refresh()}>Refresh
{/* Server Component children */}
{children}
)
}
```
## Streaming with Suspense
```tsx
// app/page.tsx
import { Suspense } from 'react'
import { SlowComponent } from './slow-component'
import { FastComponent } from './fast-component'
export default function Page() {
return (
{/* Renders immediately */}
{/* Shows fallback while loading */}
Loading...
}>
)
}
// components/slow-component.tsx
async function getData() {
await new Promise(resolve => setTimeout(resolve, 3000))
return { data: 'Loaded!' }
}
export async function SlowComponent() {
const data = await getData()
return {data.data}
}
```
## Parallel Data Fetching
```tsx
// app/dashboard/page.tsx
async function getUser() {
return fetch('https://api.example.com/user')
}
async function getPosts() {
return fetch('https://api.example.com/posts')
}
export default async function Dashboard() {
// Fetch in parallel
const [user, posts] = await Promise.all([
getUser(),
getPosts(),
])
return (
)
}
```
## Sequential Data Fetching
```tsx
// app/artist/[id]/page.tsx
async function getArtist(id: string) {
return fetch(`https://api.example.com/artists/${id}`)
}
async function getAlbums(artistId: string) {
return fetch(`https://api.example.com/artists/${artistId}/albums`)
}
export default async function ArtistPage({ params }: { params: { id: string } }) {
// Sequential: albums depends on artist
const artist = await getArtist(params.id)
const albums = await getAlbums(artist.id)
return (
)
}
```
## Preloading Data
```tsx
// lib/data.ts
import { cache } from 'react'
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } })
})
// components/user-profile.tsx
export async function UserProfile({ userId }: { userId: string }) {
const user = await getUser(userId)
return {user.name}
}
// app/page.tsx
import { getUser } from '@/lib/data'
import { UserProfile } from '@/components/user-profile'
export default async function Page() {
// Preload
getUser('123')
return (
{/* This will use cached result */}
)
}
```
## Server Component Patterns
### Pattern: Layout with Data Fetching
```tsx
// app/dashboard/layout.tsx
import { auth } from '@/lib/auth'
import { db } from '@/lib/db'
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const session = await auth()
const user = await db.user.findUnique({ where: { id: session.userId } })
return (
{children}
)
}
```
### Pattern: Conditional Client Components
```tsx
// app/page.tsx
import { ClientComponent } from './client-component'
export default async function Page() {
const data = await fetchData()
// Only render Client Component when needed
if (data.requiresInteractivity) {
return
}
return {data.content}
}
```
### Pattern: Server Component with Client Island
```tsx
// app/blog/[slug]/page.tsx
import { LikeButton } from './like-button'
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return (
{/* Server-rendered content */}
{post.title}
{/* Client island for interactivity */}
)
}
```
## Context in Server/Client Components
```tsx
// app/providers.tsx
'use client'
import { ThemeProvider } from 'next-themes'
export function Providers({ children }: { children: React.ReactNode }) {
return {children}
}
// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
```
## Third-Party Components
```tsx
// components/carousel-wrapper.tsx
'use client'
import { Carousel } from 'third-party-carousel'
export function CarouselWrapper({ items }: { items: Item[] }) {
return
}
// app/page.tsx
import { CarouselWrapper } from '@/components/carousel-wrapper'
export default async function Page() {
const items = await fetchItems()
return
}
```
## Edge Runtime
```tsx
// app/api/route.ts
export const runtime = 'edge'
export async function GET() {
return new Response('Hello from Edge!')
}
// app/page.tsx
export const runtime = 'edge'
export default async function Page() {
return Edge-rendered page
}
```
## Quick Reference
| Capability | Server Component | Client Component |
|------------|------------------|------------------|
| Data fetching | ✅ Yes | ⚠️ Use SWR/React Query |
| Backend access | ✅ Yes (DB, files) | ❌ No |
| Event handlers | ❌ No | ✅ Yes |
| State/Effects | ❌ No | ✅ Yes |
| Browser APIs | ❌ No | ✅ Yes |
| Bundle size | 0 KB | Adds to bundle |
| Streaming | ✅ Yes | ❌ No |
## Best Practices
1. **Default to Server Components** - Only use 'use client' when needed
2. **Move Client Components down** - Push them to leaves of component tree
3. **Pass data down** - Fetch in Server Components, pass to Client Components
4. **Use composition** - Nest Server Components inside Client Components via children
5. **Cache expensive operations** - Use React cache() for deduplication