Next.js: Modelo Mental dos Server Components

Última atualização: 2026-08-26

O modelo mental RSC é a mudança de paradigma mais importante no Next.js 16 — somente ao entendê-lo você poderá realmente compreender a filosofia de design por trás do App Router.

1. O Que Você Vai Aprender



2. Uma História Real de uma Desenvolvedora Full-Stack

(1) Ponto de Dor: O Tamanho do Bundle Está Fora de Controle

Alice é uma desenvolvedora full-stack na equipe TaskFlow. A página Dashboard que ela construiu inclui um componente de tabela de dados que referencia três bibliotecas — date-fns, recharts e lodash — simplesmente para formatar datas e desenhar alguns gráficos de barras. O bundle JS inicial da página atingiu 480 KB, e sua pontuação no Lighthouse Performance caiu para 52. Para piorar, esta tabela é meramente um componente renderizado no servidor, puramente apresentacional — os usuários não precisam interagir com ela — mas o código dessas bibliotecas ainda é baixado para o cliente.

(2) Solução com RSC

O RSC garante que os componentes do lado do servidor executem apenas no servidor, produzindo HTML puro e dados serializados, com os bundles JavaScript completamente excluídos.

TSX
// app/dashboard/page.tsx — Server Component (Zero-Client JS)
import { getSalesData } from '@/lib/db'
import { formatDistanceToNow } from 'date-fns'

export default async function DashboardPage() {
  const sales = await getSalesData()  // Acesso Direto ao Banco de Dados
  return (
    <div>
      <h1>Dashboard — {sales.length} registros</h1>
      <SalesTable data={sales} />
    </div>
  )
}

async function SalesTable({ data }: { data: Sale[] }) {
  return (
    <table>
      {data.map(row => (
        <tr key={row.id}>
          <td>{formatDistanceToNow(row.createdAt)}</td>
          <td>{row.amount}</td>
        </tr>
      ))}
    </table>
  )
}

(3) Resultados

Dimensão Client Component Puro RSC
Tamanho do Bundle 480 KB (inclui date-fns + ReCharts + Lodash) 0 KB (bibliotecas do lado do servidor não são baixadas)
Acesso ao Banco de Dados Requer Rota de API Intermediária Acesso Direto (Latência Zero)
Renderização Above-the-fold Requer download + execução de JS HTML Instantâneo
SEO Depende de SSR / renderização no cliente Suporte nativo
Pontuação Lighthouse 52 96


3. Definição Básica do RSC

RSC (React Server Component) é um novo tipo de componente introduzido no React 19. Ele executa apenas no servidor e nunca é enviado para o navegador do cliente. O código RSC (incluindo bibliotecas dependentes) não aparece no bundle JS, então você pode usar com segurança bibliotecas grandes, acessar bancos de dados diretamente e ler o sistema de arquivos.

100%
graph TB
    subgraph "Lado do Servidor (Server)"
        A[Componentes RSC] --> B[Banco de Dados/Sistema de Arquivos/API]
        A --> C[Serializar para RSC Payload<br/>Protocolo React Flight]
    end
    subgraph "Cliente (Navegador)"
        D[RSC Payload] --> E[Client Component<br/>Preserva a lógica de interação]
        D --> F[Renderização HTML Pura<br/>Zero Sobrecarga de JS]
    end
    C --> D
    style A fill:#d4edda
    style D fill:#cce5ff
Característica Server Component Client Component
Ambiente de Execução Lado do servidor (Node.js) Navegador
Bundle JS ❌ Não incluído ✅ Incluído
Banco de Dados/Sistema de Arquivos ✅ Acesso Direto ❌ Não Disponível (Requer API)
React Hooks (useState/useEffect) ❌ Não disponível ✅ Disponível
Manipulação de Eventos (onClick/onSubmit) ❌ Não disponível ✅ Disponível
Async/Await ✅ Suporte nativo ❌ Requer tratamento adicional

(1) O Significado do "Zero-Client JS"

O princípio central do RSC é: Se um componente não tem lógica interativa, seu código não deve ser enviado ao navegador. Isso significa:

(2) RSC vs. SSR Tradicional

O SSR tradicional (Pages Router) também renderiza HTML no servidor, mas ainda envia o código JavaScript do componente para o cliente para hidratação. O RSC, por outro lado, é completamente diferente — o código dos componentes do lado do servidor nunca chega ao cliente.

Dimensão SSR Tradicional (Pages Router) RSC (App Router)
Renderização no servidor ✅ HTML ✅ HTML
Hidratação no Cliente ✅ Completa ❌ Não necessária
Código do componente enviado ao cliente ✅ Todo ❌ Apenas Client Components
Preservação de estado Requer tratamento cuidadoso Naturalmente sem estado
Momento da Obtenção de Dados getServerSideProps Diretamente no componente com await

▶ Exemplo: Verificando Diferenças no Tamanho do Bundle (Dificuldade: ⭐⭐)

Saída:

TEXT 📖 Somente leitura
Diagrama: Componentes RSC; Banco de Dados/Sistema de Arquivos/API; Serializar para RSC Payload Protocolo React Flight; RSC Payload; Client Component Preserva a lógica de interação; Renderização HTML Pura Zero Sobrecarga de JS.
TSX
// app/bundle-demo/page.tsx — Server Component (Puramente no servidor)
import { format, addDays } from 'date-fns'

export default function BundleDemoPage() {
  const today = new Date()
  const dates = Array.from({ length: 7 }, (_, i) => {
    const d = addDays(today, i)
    return { label: format(d, 'EEEE'), date: format(d, 'yyyy-MM-dd') }
  })

  return (
    <div>
      <h1>Esta Semana</h1>
      <ul>{dates.map(d => <li key={d.date}>{d.label}: {d.date}</li>)}</ul>
    </div>
  )
}

Saída:

TEXT 📖 Somente leitura
Verificação Pós-Build em .next/static/chunks — Exclui o código do date-fns

Saída:

TEXT 📖 Somente leitura
Após o build, o diretório .next/static/chunks não inclui o código do date-fns — confirmada zero sobrecarga de JS no cliente. A página renderiza: cabeçalho "Esta Semana" + 7 itens de lista (ex.: "Segunda-feira: 2026-07-06", "Terça-feira: 2026-07-07", ...).


4. A Diretiva 'use client' e as Fronteiras do Cliente

'use client' é uma diretiva em nível de módulo que marca um componente em um arquivo como Client Component. Quando funcionalidade interativa (useState, onClick, useEffect) é necessária na árvore de componentes RSC, esta diretiva deve ser adicionada no topo do arquivo.

100%
graph TB
    A[Root Layout<br/>Server Component] --> B[NavBar<br/>Server Component]
    A --> C[DashboardPage<br/>Server Component]
    C --> D[SalesChart<br/>'use client']
    C --> E[DataTable<br/>Server Component]
    D --> F[Lógica de Interação<br/>useState / useEffect]

    style A fill:#d4edda
    style B fill:#d4edda
    style C fill:#d4edda
    style D fill:#cce5ff
    style E fill:#d4edda
Comando Função Exemplo
'use client' Marca o módulo como Client Component 'use client'; export default function Btn() { ... }
'use server' Marca a função como Server Action 'use server'; export async function create() { ... }

(1) Regras de Penetração da Fronteira

Existem duas regras fundamentais na árvore de componentes RSC:

TSX
// ✅ Correto: Server Component Importa Client Component
// app/page.tsx (Server)
import ClientCounter from './ClientCounter'
export default function Page() {
  return <ClientCounter />
}

// app/ClientCounter.tsx (Client)
'use client'
import { useState } from 'react'
export default function ClientCounter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
TSX
// ❌ Erro: Client Component Não Pode Importar Diretamente Server Component
// app/ClientList.tsx
'use client'
import ServerItem from './ServerItem'  // ❌ Erro de Compilação: Server Component não pode ser importado no lado do cliente

export default function ClientList() {
  return <ServerItem />  // Esta linha causará um erro.
}

(2) Como Incorporar um Server Component em um Client Component

Dica: Passe dados via children props — o slot children no client component pode receber o resultado da renderização do server component.

TSX
// app/layout.tsx (Server)
import ClientShell from './ClientShell'
import ServerSidebar from './ServerSidebar'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <ClientShell sidebar={<ServerSidebar />}>
      {children}
    </ClientShell>
  )
}

▶ Exemplo: A Maneira Correta de Propagar Props — Children Props (Dificuldade ⭐⭐)

Saída:

TEXT 📖 Somente leitura
Inclui uma barra lateral.
Texto visível: }>
      {children}
TSX
// app/interleaving/ClientWrapper.tsx
'use client'
import { useState } from 'react'

export default function ClientWrapper({ sidebar, children }: {
  sidebar: React.ReactNode
  children: React.ReactNode
}) {
  const [isOpen, setIsOpen] = useState(true)
  return (
    <div style={{ display: 'flex' }}>
      {isOpen && <aside>{sidebar}</aside>}
      <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
      <main>{children}</main>
    </div>
  )
}

Saída:

TEXT 📖 Somente leitura
Um componente interativo com gerenciamento de estado.
Texto visível: }
TSX
// app/interleaving/page.tsx (Server)
import ClientWrapper from './ClientWrapper'
import { getSidebarData } from '@/lib/db'

export default function InterleavingPage() {
  const items = getSidebarData() // Obtendo Dados no Lado do Servidor
  return (
    <ClientWrapper sidebar={<ServerItemList items={items} />}>
      <h1>Conteúdo Principal</h1>
    </ClientWrapper>
  )
}

async function ServerItemList({ items }: { items: string[] }) {
  return <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>
}

▶ Exemplo: Restrições de Props Serializáveis (Dificuldade: ⭐⭐⭐)

Saída:

TEXT 📖 Somente leitura
Renderiza uma lista de itens usando .map().
Texto visível: }> | Conteúdo Principal
TSX
// app/serializable/page.tsx (Server)
function greet() { return 'hello' }  // ❌ Funções não são serializáveis
const date = new Date()              // ⚠️ Date Não Permitido como Prop RSC

// app/serializable/ClientComponent.tsx
'use client'
export default function ClientComponent(props: {
  fn: () => string       // ❌ Funções como props → Erro em Tempo de Execução
  date: Date             // ⚠️ Date → Convertido para string, fuso horário pode ser perdido
  data: { name: string } // ✅ Objetos comuns são permitidos
}) {
  return <div>{props.data.name}</div>
}

Saída:

TEXT 📖 Somente leitura
Renderiza a interface do componente ClientComponent.


5. Unindo o React Flight Payload com a Árvore de Componentes

Após a renderização do lado do servidor RSC ser concluída, ela produz um formato de dados especial chamado RSC Payload (Protocolo React Flight), que contém a árvore HTML serializada, referências de componentes e dados de props. Quando o cliente recebe o payload, ele o mescla com o client component local para formar a árvore de componentes final.

(1) Estrutura do RSC Payload

100%
sequenceDiagram
    participant Server as Servidor Next.js
    participant Client as Navegador

    Server->>Server: Executar Árvore de Componentes RSC
    Server->>Server: Serializar para React Flight Payload
    Server->>Client: Enviar RSC Payload + HTML
    Client->>Client: Analisar Flight Payload
    Client->>Client: Mesclar Client Component (Hidratar)
    Client->>Client: Renderizar a interface final
Componente Descrição Exemplo
Saída do Server Component Fragmento HTML serializado <div><h1>Dashboard</h1></div>
Referência do Client Component ID do Módulo + Props {id: "./chart.js", props: {data: [...]}}
Referência de Dados Resultados da Consulta ao Banco de Dados {sales: [{id:1, amount: 100}]}
Fluxo (Stream) Divisão por Suspense Boundary Envio de Múltiplos Chunks Incrementalmente

▶ Exemplo: Visualizando o RSC Payload (Dificuldade: ⭐⭐)

Visualize a resposta RSC no painel Network das ferramentas de desenvolvedor do navegador:

BASH
# Abra o Painel Network, Atualize a página, Filtre por Fetch/XHR
# Encontre a requisição da página atual, Veja a Resposta
# Content-Type: text/x-component significa RSC Payload

Saída:

TEXT 📖 Somente leitura
# Trecho do RSC Payload (Simplificado):
M1:{"id":"./app/page.tsx","chunks":["app/page-abc123.js"]}
J0:["$","div",null,{"children":["$","h1",null,{"children":"Dashboard"}]}]
S1:"react.suspense"

▶ Exemplo: Como um Client Component Referencia um Server Component (Dificuldade: ⭐⭐⭐)

TSX
// app/flight-demo/ServerData.tsx — Componente de dados puramente do lado do servidor
export default async function ServerData() {
  const data = await fetch('https://api.example.com/data').then(r => r.json())
  return <pre>{JSON.stringify(data, null, 2)}</pre>
}
TSX
// app/flight-demo/ClientShell.tsx
'use client'
export default function ClientShell({ dataSlot }: { dataSlot: React.ReactNode }) {
  return (
    <div className="card">
      <h2>Client Shell</h2>
      <div className="server-data">{dataSlot}</div>
    </div>
  )
}

Saída:

TEXT 📖 Somente leitura
Renderiza: Client Shell
Texto visível: Client Shell
TSX
// app/flight-demo/page.tsx
import ClientShell from './ClientShell'
import ServerData from './ServerData'

export default function FlightDemoPage() {
  return (
    <ClientShell dataSlot={<ServerData />}>
  )
}


6. Exemplo Completo: Arquitetura da Árvore de Componentes RSC

TSX
// app/rsc-architecture/layout.tsx — Layout RSC
import ClientShell from './ClientShell'
import { getUser } from '@/lib/auth'

export default async function RscLayout({ children }: { children: React.ReactNode }) {
  const user = await getUser()                    // ✅ Consultas Diretas ao Banco de Dados
  return (
    <ClientShell username={user?.name ?? 'Convidado'}>
      <nav>
        <a href="/">Início</a>
        <a href="/dashboard">Dashboard</a>
        <a href="/settings">Configurações</a>
      </nav>
      {children}
    </ClientShell>
  )
}

// app/rsc-architecture/ClientShell.tsx
'use client'
import { useState } from 'react'
import type { ReactNode } from 'react'

export default function ClientShell({ username, children }: {
  username: string
  children: ReactNode
}) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light')
  return (
    <div data-theme={theme}>
      <header>
        <span>Bem-vindo, {username}</span>
        <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
          Alternar {theme}
        </button>
      </header>
      {children}
    </div>
  )
}

// app/rsc-architecture/page.tsx
import { getProjects } from '@/lib/db'

export default async function RscArchitecturePage() {
  const projects = await getProjects()
  return (
    <div>
      <h1>Projetos ({projects.length})</h1>
      <table>
        <thead><tr><th>Nome</th><th>Status</th></tr></thead>
        <tbody>
          {projects.map(p => (
            <tr key={p.id}>
              <td>{p.name}</td>
              <td><StatusBadge status={p.status} /></td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

function StatusBadge({ status }: { status: string }) {
  const colors: Record<string, string> = {
    active: '#4caf50', archived: '#9e9e9e', draft: '#ff9800'
  }
  return <span style={{ background: colors[status] ?? '#ccc', padding: '2px 8px', borderRadius: 4 }}>{status}</span>
}

❓ Perguntas Frequentes

P: RSC e SSR são a mesma coisa? R: Não. Com SSR tradicional, o código JavaScript ainda é enviado ao cliente após o HTML ser renderizado no servidor (hidratação). Com RSC, o código do Server Component nunca é enviado ao cliente — zero sobrecarga de JavaScript. SSR e RSC podem coexistir (o modo padrão do App Router é uma combinação de RSC e SSR).

P: Todos os componentes em um arquivo 'use client' são client components? R: Sim. 'use client' é uma diretiva em nível de módulo — todos os componentes exportados de um arquivo são client components. Recomenda-se separar componentes interativos em arquivos próprios para reduzir a quantidade de código no lado do cliente.

P: Por que um Client Component não pode importar diretamente um Server Component? R: Porque Server Components só existem no ambiente de execução do lado do servidor. Quando um Client Component executa no navegador, o código do Server Component simplesmente não existe. A abordagem correta é conectar os dois usando a prop children ou Server Actions.

P: O que acontece se uma função for passada como prop para um client component? R: Um erro será lançado. O RSC payload é baseado em serialização JSON (protocolo React Flight), e funções não podem ser serializadas. Se você precisar passar um callback, deve usar Server Actions ou o padrão de manipulador de eventos.

P: Como determinar se um componente deve ser Server ou Client? R: A regra prática mais simples: se o componente requer interatividade (useState, useEffect, onClick, APIs do navegador), é um Client; caso contrário, use Server Component por padrão. Uma estratégia comum de otimização é extrair as partes interativas para um pequeno wrapper Client, mantendo o corpo principal como Server Component.

P: Qual é a relação entre o RSC payload e o HTML? R: O Next.js 16 envia tanto o HTML (para Carregamento Instantâneo) quanto o RSC payload (para reconstrução da árvore de componentes). O HTML garante que a primeira tela seja exibida imediatamente, enquanto o RSC payload assume as interações após ser analisado no cliente. Juntos, eles proporcionam "primeira tela instantânea + interatividade completa".


📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Crie um Server Component em app/page.tsx que chame diretamente fetch('https://api.github.com/repos/vercel/next.js') para obter dados e renderizar a contagem de estrelas. Verifique se o cliente não baixa bibliotecas como node-fetch.

  2. Exercício Avançado (⭐⭐): Crie uma página contendo um Client Component (botão contador) e um Server Component (lista de usuários), e passe os dados do servidor para o wrapper cliente usando a prop children. Verifique a resposta RSC Payload na aba Network do DevTools do navegador.

  3. Desafio (⭐⭐⭐): Construa uma árvore de componentes de três níveis: Layout (Server) → ClientTabs (Client, contendo useState para gerenciar a aba atual) → ServerTabContent (Server Components passados via children), onde o conteúdo de cada aba consiste em uma consulta assíncrona de dados diferente. Garanta que a obtenção de dados de todos os Server Components não aumente o tamanho do bundle do cliente.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%