# React Server Components em produção: padrões e armadilhas
> React Server Components em produção: padrões testados em batalha, anti-padrões comuns e estratégias de depuração para aplicações Next.js 15 robustas.
- Published: 2026-03-29
- Updated: 2026-05-04
- Author: Anthony Fillion-Maillet
- Tags: react server components, next.js 15, rsc patterns, production, react 19
- Reading time: 9 min
---
Os React Server Components (RSC) mudam de forma fundamental como funciona a renderização no servidor no Next.js 15, mas a adoção em produção revela armadilhas que a documentação oficial nem sempre cobre. Este artigo destrincha os padrões que funcionam, os que quebram e como diagnosticar problemas antes que cheguem à produção.
> **Server Components e Client Components**
>
> Um Server Component roda exclusivamente no servidor e envia zero JavaScript ao navegador. Um Client Component (marcado com `"use client"`) roda dos dois lados. A regra: manter os Client Components o menores e mais abaixo possível na árvore.
## A fronteira servidor-cliente: entender o padrão de boundary
A armadilha mais comum com RSC envolve a fronteira entre Server e Client Components. Assim que um componente carrega a diretiva `"use client"`, **todos os filhos importados também viram Client Components**, mesmo sem a diretiva.
```tsx
// ProductPage.tsx (Server Component)
import { ProductDetails } from './ProductDetails'
import { AddToCartButton } from './AddToCartButton'
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const product = await getProduct(id)
return (
{/* Server Component: acesso direto ao banco */}
{/* Client Component: interatividade isolada */}
)
}
```
```tsx
// AddToCartButton.tsx (Client Component)
'use client'
import { useState } from 'react'
export function AddToCartButton({ productId, price }: { productId: string; price: number }) {
const [adding, setAdding] = useState(false)
async function handleAdd() {
setAdding(true)
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId, quantity: 1 }),
})
setAdding(false)
}
return (
)
}
```
O padrão-chave: passar dados como **props serializáveis** do Server Component para o Client Component. Funções, classes e objetos Date não atravessam essa fronteira.
## Anti-padrão: o wrapper Client Component desnecessário
Um erro frequente é criar um Client Component que envolve filhos Server Components, forçando toda a subárvore para o lado cliente.
```tsx
// PageWrapper.tsx — ANTI-PADRÃO
'use client'
import { useState } from 'react'
// Todo o conteúdo filho vai para o lado cliente
export function PageWrapper({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState('light')
return (
{children}
)
}
```
> **children como slot**
>
> A solução: passar Server Components como `children` (padrão slot). Filhos passados como props continuam sendo Server Components mesmo quando o pai é um Client Component. O código acima funciona corretamente desde que `children` venha de um pai Server Component.
```tsx
// layout.tsx (Server Component)
import { PageWrapper } from './PageWrapper'
import { HeavyServerContent } from './HeavyServerContent'
export default function Layout() {
return (
{/* Continua Server Component apesar do wrapper cliente */}
)
}
```
Esse padrão de composição preserva os benefícios da renderização no servidor para conteúdo pesado, ao mesmo tempo em que habilita interatividade no nível do wrapper.
## Manuseio de dados assíncronos: o padrão fetch no componente
React 19 e Next.js 15 suportam `async/await` diretamente em Server Components. Esse padrão simplifica a busca de dados em comparação com a antiga abordagem `getServerSideProps`.
```tsx
// UserProfile.tsx (Server Component)
import { cache } from 'react'
// Deduplica chamadas idênticas dentro do mesmo render
const getUser = cache(async (userId: string) => {
const res = await fetch(`https://api.example.com/users/${userId}`, {
next: { revalidate: 3600 }, // Cache por 1 hora
})
if (!res.ok) throw new Error('User not found')
return res.json()
})
export default async function UserProfile({ userId }: { userId: string }) {
const user = await getUser(userId)
return (
{user.name}
{user.email}
Membro desde {new Date(user.createdAt).toLocaleDateString('pt-BR')}
)
}
```
Três pontos críticos:
- O `cache()` do React deduplica chamadas idênticas durante um único render do servidor
- `next: { revalidate }` controla a duração do cache do lado do Next.js
- Erros num Server Component assíncrono acionam o `error.tsx` mais próximo
## Armadilha de serialização: o que não atravessa a fronteira
Dados trocados entre Server e Client Components precisam ser serializáveis em JSON. Veja o que provoca erros silenciosos ou crashes.
```tsx
// ARMADILHA: passar tipos não serializáveis
// Função — não funciona
{ /* server action */ }} />
// Usar uma Server Action importada no lugar
import { submitForm } from '@/lib/actions/form'
// Objeto Date — não funciona
// String ISO — funciona
// Map, Set, RegExp — não funciona
// Objeto plano ou array — funciona
```
As Server Actions (funções marcadas com `"use server"`) são a exceção: podem ser passadas como props para um Client Component porque o Next.js as transforma em endpoints HTTP.
## Streaming e Suspense: padrões de carregamento progressivo
O streaming SSR com Suspense envia HTML ao navegador de forma progressiva. O padrão ótimo usa boundaries Suspense granulares ao redor de cada seção assíncrona.
```tsx
// DashboardPage.tsx (Server Component)
import { Suspense } from 'react'
import { RevenueChart } from './RevenueChart'
import { RecentOrders } from './RecentOrders'
import { UserStats } from './UserStats'
export default function DashboardPage() {
return (
}>
}>
}>
)
}
```
Cada seção carrega de forma independente. Se `RevenueChart` leva 3 segundos e `UserStats` 200 ms, as estatísticas aparecem instantaneamente sem esperar o gráfico.
> **Suspense e SEO**
>
> O conteúdo dentro de um boundary Suspense é renderizado no servidor e incluído no HTML inicial. Crawlers veem o conteúdo completo. O streaming afeta apenas a velocidade de entrega ao navegador, não a visibilidade SEO.
## Depuração em produção: rastrear problemas RSC
Erros RSC costumam ser crípticos. Três técnicas de diagnóstico funcionam em produção.
**1. Identificar incompatibilidades de hidratação**
```tsx
// debug-hydration.tsx
'use client'
import { useEffect, useState } from 'react'
export function HydrationDebug() {
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
if (process.env.NODE_ENV !== 'development') return null
return (
{isClient ? 'Client' : 'Server'}
)
}
```
**2. Registrar o payload RSC**
No Next.js 15, ativar o logging RSC em `next.config.ts`:
```typescript
// next.config.ts
const nextConfig = {
logging: {
fetches: {
fullUrl: true, // Mostra as URLs completas dos fetch
},
},
}
export default nextConfig
```
**3. Verificar o tamanho do payload**
Um payload RSC superdimensionado (> 128 KB) degrada o desempenho. Monitorar requisições de rede com o content type `text/x-component` no DevTools.
## Padrão avançado: composição com Server Actions
Server Actions combinadas com Server Components criam um padrão CQRS natural: leituras no servidor (RSC), escritas via actions.
```tsx
// TodoList.tsx (Server Component)
import { getTodos } from '@/lib/services/todo'
import { TodoForm } from './TodoForm'
import { deleteTodo } from '@/lib/actions/todo'
export default async function TodoList() {
const todos = await getTodos()
return (
{todos.map(todo => (
{todo.title}
))}
)
}
```
```tsx
// actions/todo.ts
'use server'
import { revalidatePath } from 'next/cache'
import { TodoService } from '@/lib/services/todo'
export async function deleteTodo(formData: FormData) {
const id = formData.get('id') as string
await TodoService.delete(id)
revalidatePath('/todos')
}
```
A chamada a `revalidatePath` dispara um novo render do Server Component com dados atualizados, sem recarregar a página inteira.
Para uma preparação de entrevista mais profunda sobre esses tópicos, confira o módulo [Next.js Server Actions](/technologies/react-next/interview-questions/nextjs-server-actions) e o módulo [Next.js Data Fetching](/technologies/react-next/interview-questions/nextjs-data-fetching) no SharpSkill. A [documentação oficial do React](https://react.dev/reference/rsc/server-components) cobre toda a especificação dos Server Components.
## Conclusão
- Manter Client Components pequenos e isolados na parte de baixo da árvore de componentes
- Usar o padrão slot (`children`) para preservar Server Components dentro de um wrapper cliente
- Sempre verificar a serializabilidade das props que atravessam a fronteira servidor-cliente
- Colocar boundaries Suspense granulares ao redor de cada seção assíncrona independente
- Monitorar o tamanho do payload RSC em produção (alvo < 128 KB)
- Combinar Server Components (leituras) e Server Actions (escritas) para um padrão CQRS natural
- Usar o `cache()` do React para deduplicar requisições dentro de um único render do servidor
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/pt/blog/react-next/react-server-components-patterns-pitfalls