antrophiclove
← Kütüphaneye dön
Kurulum
mkdir -p .claude/skills/server-vs-client && curl -sL https://www.antrophiclove.com/api/kaynak/server-vs-client/raw -o .claude/skills/server-vs-client/SKILL.md

Hedef dosya: .claude/skills/server-vs-client/SKILL.md

Server vs Client Components

Overview

In Next.js App Router, components are Server Components by default. Understanding when to use each type is crucial for optimal performance.

Quick Decision Guide

NeedComponent Type
Fetch dataServer
Access backend resourcesServer
Keep sensitive info on serverServer
Reduce client JavaScriptServer
Use useState/useReducerClient
Use useEffect/lifecycleClient
Use event listeners (onClick)Client
Use browser APIsClient
Use custom hooks with stateClient

Server Components

Characteristics

  • Render on the server only
  • Never shipped to the client
  • Can directly access databases, file systems
  • No JavaScript bundle cost

Example

// app/users/page.tsx - Server Component (default)
import { db } from '@/lib/db'

export default async function UsersPage() {
  // Direct database access - only runs on server
  const users = await db.user.findMany({
    select: { id: true, name: true, email: true }
  })

  // Sensitive operations are safe
  const secret = process.env.API_SECRET

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

What You Can Do

// ✅ Server Component capabilities
async function ServerComponent() {
  // Database queries
  const data = await prisma.post.findMany()

  // File system access
  const file = await fs.readFile('./data.json')

  // Environment variables (secret)
  const apiKey = process.env.SECRET_API_KEY

  // Async operations
  const response = await fetch('https://api.example.com')

  // Heavy computations (not in client bundle)
  const processed = heavyComputation(data)

  return <div>{/* ... */}</div>
}

What You Cannot Do

// ❌ These will NOT work in Server Components
function ServerComponent() {
  // No hooks
  const [state, setState] = useState() // Error!
  useEffect(() => {}) // Error!

  // No event handlers
  <button onClick={() => {}} /> // Error!

  // No browser APIs
  window.localStorage // Error!
  document.querySelector // Error!
}

Client Components

Marking as Client

Add 'use client' directive at the top:

// components/counter.tsx
'use client'

import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)

  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  )
}

What You Can Do

'use client'

function ClientComponent() {
  // Hooks
  const [state, setState] = useState()
  useEffect(() => {}, [])

  // Event handlers
  const handleClick = () => console.log('clicked')

  // Browser APIs
  const width = window.innerWidth

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  )
}

What You Cannot Do

'use client'

// ❌ These will NOT work in Client Components
async function ClientComponent() {
  // No async component
  const data = await fetch() // Error!

  // No direct database access
  const users = await prisma.user.findMany() // Error!

  // No server-only imports
  import { readFile } from 'fs' // Error!
}

The Client Boundary

How It Works

When you add 'use client', you create a boundary:

Server Component (layout.tsx)
├── Server Component (header.tsx)
├── Server Component (sidebar.tsx)
└── Client Component ('use client') ← BOUNDARY
    ├── Client Component (automatically)
    ├── Client Component (automatically)
    └── Server Component via children ← Can still be Server!

Key Rules

  1. Below the boundary is client by default

    'use client'
    
    // This component and all its imports become client
    import { Button } from './button' // Button is now client
    
  2. Props must be serializable

    // ✅ Can pass
    <ClientComponent
      data={{ name: 'John' }}    // Plain objects
      items={['a', 'b']}          // Arrays
      count={42}                  // Numbers
      isActive={true}             // Booleans
    />
    
    // ❌ Cannot pass
    <ClientComponent
      onClick={() => {}}          // Functions
      user={userInstance}         // Class instances
      date={new Date()}           // Date objects
    />
    
  3. Server Components can be children

    // components/client-wrapper.tsx
    'use client'
    
    export function ClientWrapper({ children }) {
      const [open, setOpen] = useState(false)
      return <div>{open && children}</div>
    }
    
    // app/page.tsx (Server)
    export default function Page() {
      return (
        <ClientWrapper>
          <ServerComponent /> {/* Still renders on server! */}
        </ClientWrapper>
      )
    }
    

When to Use Client Directive

Definitely Need 'use client'

'use client'

// 1. Using React hooks
import { useState, useEffect, useContext } from 'react'

// 2. Using event handlers
<button onClick={handleClick}>

// 3. Using browser APIs
useEffect(() => {
  const width = window.innerWidth
}, [])

// 4. Using libraries that need browser
import { motion } from 'framer-motion'

// 5. Using Context
const theme = useContext(ThemeContext)

Don't Need 'use client'

// These work in Server Components

// Static rendering
<div className="container">
  <h1>Hello</h1>
</div>

// Data fetching
const data = await fetch('/api/data')

// Conditional rendering (based on data, not state)
{user.isAdmin && <AdminPanel />}

// Mapping over data
{items.map(item => <Item key={item.id} />)}

Common Patterns

Minimal Client Components

Keep client boundaries as small as possible:

// ❌ Don't make entire page client
'use client'
export default function Page() {
  const [filter, setFilter] = useState('')
  const data = fetchedData // This won't work!
  // ...
}

// ✅ Make only interactive part client
// app/page.tsx (Server)
export default async function Page() {
  const data = await getData()
  return (
    <div>
      <h1>Products</h1>
      <ProductFilter /> {/* Client */}
      <ProductList products={data} />
    </div>
  )
}

// components/product-filter.tsx
'use client'
export function ProductFilter() {
  const [filter, setFilter] = useState('')
  // ...
}

Interleaving Pattern

// Server → Client → Server

// app/page.tsx (Server)
export default function Page() {
  return (
    <ClientTabs>
      <ServerContent /> {/* Passed as children */}
    </ClientTabs>
  )
}

// components/client-tabs.tsx
'use client'
export function ClientTabs({ children }) {
  const [tab, setTab] = useState(0)
  return (
    <div>
      <button onClick={() => setTab(0)}>Tab 1</button>
      <button onClick={() => setTab(1)}>Tab 2</button>
      {children} {/* Server-rendered content */}
    </div>
  )
}

Debugging

Check Component Type

// Add this to see where component runs
export default function MyComponent() {
  console.log('Running on:', typeof window === 'undefined' ? 'server' : 'client')
  return <div>...</div>
}

Common Errors

Error: useState only works in Client Components
→ Add 'use client' directive

Error: Event handlers cannot be passed to Client Component props
→ Move the handler into a Client Component

Error: Functions cannot be passed directly to Client Components
→ Create a Client wrapper or use Server Actions