Capítulo 6 de 456

Server and Client Components

Core Idea

Decidir onde cada parte da UI roda: Server Components (padrão) buscam dados e reduzem JS enviado ao cliente; Client Components entram só onde há interatividade, estado ou APIs de browser.

Key Concepts

  • Server Component (padrão): busca dados perto da fonte, usa secrets sem expor ao cliente, reduz JS no bundle, melhora FCP via streaming.
  • Client Component ("use client"): necessário para estado, event handlers, lifecycle (useEffect), APIs de browser (localStorage, window) e custom hooks.
  • RSC Payload: formato serializado compacto da árvore de Server Components renderizada; contém o resultado renderizado, placeholders para Client Components e props passadas a eles.
  • Hydration: processo do React de anexar event handlers ao DOM estático já enviado como HTML, tornando a página interativa.
  • "use client" como boundary: marca a fronteira entre o module graph do servidor e do cliente; uma vez marcado, todos os imports e componentes renderizados diretamente por esse arquivo entram no bundle do cliente (não se aplica a Server Components passados como children/props).
  • Interleaving: Server Components podem ser passados como children/prop para Client Components (ex.: <Modal><Cart /></Modal>), permitindo nesting visual sem incluir o Server Component no bundle do cliente.
  • Context providers: React Context não funciona em Server Components; crie um Client Component wrapper (ThemeProvider) e importe-o num Server Component como layout, mantendo o provider o mais fundo possível na árvore.
  • server-only / client-only: pacotes npm opcionais que forçam erro de build se um módulo server-only for importado no cliente (ou vice-versa); variáveis de ambiente sem prefixo NEXT_PUBLIC_ já são substituídas por string vazia no bundle do cliente por padrão.

Code Examples

// app/ui/counter.tsx
'use client'
import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <p>{count} likes</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  )
}
  • O que demonstra: declaração mínima de Client Component via diretiva no topo do arquivo.
// app/ui/modal.tsx
'use client'
export default function Modal({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>
}
// app/page.tsx
import Modal from './ui/modal'
import Cart from './ui/cart'

export default function Page() {
  return (
    <Modal>
      <Cart />
    </Modal>
  )
}
  • O que demonstra: Server Component (Cart) renderizado dentro de Client Component (Modal) via slot de children, sem entrar no bundle do cliente.
// lib/data.js
import 'server-only'

export async function getData() {
  const res = await fetch('https://external-service.com/data', {
    headers: { authorization: process.env.API_KEY },
  })
  return res.json()
}
  • O que demonstra: prevenir import acidental de código com secret em Client Component, forçando erro de build.

Anti-patterns

  • Marcar layout.tsx inteiro como "use client" por causa de um componente interativo: aumenta o bundle desnecessariamente; extraia só o componente interativo (ex.: <Search />) como Client Component e mantenha o resto Server.
  • Usar componente de terceiro com useState diretamente em Server Component: gera erro porque o Next.js não sabe que ele usa client-only features; envolva-o num wrapper próprio com "use client".
  • Renderizar provider de contexto envolvendo <html> inteiro: dificulta a otimização de partes estáticas; envolva só {children}.
  • Assumir que variável de ambiente sem prefixo é "safe by omission" no cliente: ela não vaza, mas também não funciona (vira string vazia); a proteção real e explícita é o pacote server-only.

Key Takeaways

  1. Server Components são o padrão; Client Components só entram onde há necessidade real de interatividade, estado ou API de browser.
  2. "use client" marca uma fronteira de module graph, tudo que esse arquivo importa e renderiza diretamente vai para o bundle do cliente, exceto Server Components passados como children/props.
  3. Interleaving (Server Component como children de Client Component) é o padrão para nestar UI server-rendered dentro de componentes com estado (ex.: modal).
  4. Context Providers precisam ser Client Components, mas devem envolver {children} o mais fundo possível na árvore, não a app inteira.
  5. server-only garante em build time que segredos não vazem para o cliente; variáveis sem NEXT_PUBLIC_ já são zeradas no bundle por padrão, mas isso não substitui a proteção explícita.

Connects To

  • Fetching Data: detalha como Server Components buscam dados (fetch API, ORM) e como passar promises para Client Components via use().
  • Mutating Data: Server Functions ("use server") usam a mesma lógica de module boundary vista aqui.