384
.claude/skills/nextjs-developer/references/server-components.md
Normal file
384
.claude/skills/nextjs-developer/references/server-components.md
Normal file
@@ -0,0 +1,384 @@
|
||||
# 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 (
|
||||
<div>
|
||||
<h1>Users</h1>
|
||||
<ul>
|
||||
{users.map(user => (
|
||||
<li key={user.id}>{user.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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 (
|
||||
<button onClick={() => setCount(count + 1)}>
|
||||
Count: {count}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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 (
|
||||
<div>
|
||||
{/* Server Component content */}
|
||||
<h1>Server Content</h1>
|
||||
|
||||
{/* Pass data to Client Component */}
|
||||
<ClientWrapper initialData={data}>
|
||||
{/* Server Component as children */}
|
||||
<ServerSidebar />
|
||||
</ClientWrapper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// components/client-wrapper.tsx
|
||||
'use client'
|
||||
|
||||
export function ClientWrapper({
|
||||
children,
|
||||
initialData,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
initialData: Data
|
||||
}) {
|
||||
const [data, setData] = useState(initialData)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Client Component UI */}
|
||||
<button onClick={() => refresh()}>Refresh</button>
|
||||
{/* Server Component children */}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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 (
|
||||
<div>
|
||||
{/* Renders immediately */}
|
||||
<FastComponent />
|
||||
|
||||
{/* Shows fallback while loading */}
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<SlowComponent />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 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 <div>{data.data}</div>
|
||||
}
|
||||
```
|
||||
|
||||
## 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 (
|
||||
<div>
|
||||
<UserProfile user={user} />
|
||||
<PostsList posts={posts} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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 (
|
||||
<div>
|
||||
<h1>{artist.name}</h1>
|
||||
<Albums albums={albums} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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 <div>{user.name}</div>
|
||||
}
|
||||
|
||||
// app/page.tsx
|
||||
import { getUser } from '@/lib/data'
|
||||
import { UserProfile } from '@/components/user-profile'
|
||||
|
||||
export default async function Page() {
|
||||
// Preload
|
||||
getUser('123')
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* This will use cached result */}
|
||||
<UserProfile userId="123" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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 (
|
||||
<div>
|
||||
<Sidebar user={user} />
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 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 <ClientComponent data={data} />
|
||||
}
|
||||
|
||||
return <div>{data.content}</div>
|
||||
}
|
||||
```
|
||||
|
||||
### 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 (
|
||||
<article>
|
||||
{/* Server-rendered content */}
|
||||
<h1>{post.title}</h1>
|
||||
<div dangerouslySetInnerHTML={{ __html: post.content }} />
|
||||
|
||||
{/* Client island for interactivity */}
|
||||
<LikeButton postId={post.id} initialLikes={post.likes} />
|
||||
</article>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Context in Server/Client Components
|
||||
|
||||
```tsx
|
||||
// app/providers.tsx
|
||||
'use client'
|
||||
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return <ThemeProvider>{children}</ThemeProvider>
|
||||
}
|
||||
|
||||
// app/layout.tsx
|
||||
import { Providers } from './providers'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Third-Party Components
|
||||
|
||||
```tsx
|
||||
// components/carousel-wrapper.tsx
|
||||
'use client'
|
||||
|
||||
import { Carousel } from 'third-party-carousel'
|
||||
|
||||
export function CarouselWrapper({ items }: { items: Item[] }) {
|
||||
return <Carousel items={items} />
|
||||
}
|
||||
|
||||
// app/page.tsx
|
||||
import { CarouselWrapper } from '@/components/carousel-wrapper'
|
||||
|
||||
export default async function Page() {
|
||||
const items = await fetchItems()
|
||||
return <CarouselWrapper items={items} />
|
||||
}
|
||||
```
|
||||
|
||||
## 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 <div>Edge-rendered page</div>
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user