Server Components
Render React components on the server with zero client-side JavaScript -- o padrão no App Router do Next.js.
Busque em todas as páginas da documentação
Render React components on the server with zero client-side JavaScript -- o padrão no App Router do Next.js.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida -- pronto para copiar e colar.
// app/page.tsx -- Server Component por padrão (nenhuma diretiva necessária)
import { db } from "@/lib/db";
export default async function HomePage() {
const posts = await db.post.findMany({ take: 10 });
return (
<main>
<h1>Últimas Postagens</h1>
<ul>
{posts.map((p) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
</main>
);
}Quando usar isso: Qualquer componente que apenas lê dados e renderiza marcação -- sem useState, sem useEffect, sem manipuladores de eventos, sem APIs do navegador. Este é o padrão; você opta por sair com "use client", não por entrar.
// app/blog/page.tsx (Server Component)
import { Suspense } from "react";
import { formatDistanceToNow } from "date-fns";
type Post = {
id: string;
title: string;
excerpt: string;
publishedAt: string;
author: { name: string; avatar: string };
};
async function fetchPosts(): Promise<Post[]> {
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 300 },
});
if (!res.ok) throw new Error("Falha ao buscar posts");
return res.json();
}
export default async function BlogPage() {
return (
<main className="max-w-3xl mx-auto p-6">
<h1 className="text-3xl font-bold mb-8">Blog</h1>
<Suspense fallback={<PostsSkeleton />}>
<PostList />
</Suspense>
</main>
);
}
async function PostList() {
const posts = await fetchPosts();
return (
<div className="space-y-8">
{posts.map((post) => (
<article key={post.id} className="border-b pb-6">
<h2 className="text-xl font-semibold mb-2">{post.title}</h2>
<p className="text-gray-600 mb-3">{post.excerpt}</p>
<div className="flex items-center gap-3 text-sm text-gray-500">
<img
src={post.author.avatar}
alt={post.author.name}
className="w-6 h-6 rounded-full"
/>
<span>{post.author.name}</span>
<span>
{formatDistanceToNow(new Date(post.publishedAt), {
addSuffix: true,
})}
</span>
</div>
</article>
))}
</div>
);
}
function PostsSkeleton() {
return (
<div className="space-y-8">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="border-b pb-6">
<div className="h-6 bg-gray-200 rounded w-3/4 mb-2 animate-pulse" />
<div className="h-4 bg-gray-100 rounded w-full mb-3 animate-pulse" />
<div className="h-4 bg-gray-100 rounded w-1/2 animate-pulse" />
</div>
))}
</div>
);
}O que isso demonstra:
await diretamente no corpo da funçãodate-fns) que envia zero JavaScript para o cliente<Suspense> para streaming"use client" em lugar nenhum -- a página inteira é renderizada no servidorasync e usar await diretamente. Isso não é permitido em Client Components.date-fns) não são incluídas no bundle do cliente.revalidatePath/revalidateTag é chamado.Busca de dados paralela:
async function Dashboard() {
const [users, revenue, orders] = await Promise.all([
fetchUsers(),
fetchRevenue(),
fetchOrders(),
]);
return (
<>
<UserTable users={users} />
<RevenueChart revenue={revenue} />
<OrderList orders={orders} />
</>
);
}Passando dados do servidor para Client Components:
// Server Component
import { ClientMap } from "./client-map";
export default async function LocationPage() {
const locations = await db.location.findMany();
// Apenas dados serializáveis podem cruzar a fronteira
return <ClientMap locations={locations} />;
}Utilitários exclusivos do servidor:
// lib/server-only-utils.ts
import "server-only"; // Lança um erro de build se importado em um Client Component
export function getSecretConfig() {
return {
apiKey: process.env.SECRET_API_KEY!,
dbUrl: process.env.DATABASE_URL!,
};
}// Async Server Components retornam Promise<JSX.Element>
// TypeScript lida com isso com os tipos do React 19+
async function MyComponent(): Promise<JSX.Element> {
const data = await fetchData();
return <div>{data.name}</div>;
}
// Props devem ser serializáveis quando passadas para Client Components
type SerializableProps = {
name: string;
count: number;
items: { id: string; label: string }[];
// NÃO permitido: onClick: () => void
// NÃO permitido: ref: React.Ref<HTMLDivElement>
};
// Use o pacote `server-only` para proteção em tempo de compilação
import "server-only";Não é possível usar hooks -- useState, useEffect, useRef e todos os outros hooks são exclusivos do cliente. Correção: Extraia partes interativas para um componente "use client".
Não é possível usar manipuladores de eventos -- onClick, onChange, onSubmit, etc. exigem JavaScript do lado do cliente. Correção: Mova a lógica de manipulação de eventos para um Client Component.
Não é possível acessar APIs do navegador -- window, document, localStorage, navigator não estão disponíveis no servidor. Correção: Use-os apenas em componentes "use client" ou atrás de verificações typeof window !== "undefined".
Props para Client Components devem ser serializáveis -- Funções (exceto Server Actions), instâncias de classe, Symbols e nós DOM não podem ser passados como props através da fronteira servidor-cliente. Correção: Passe apenas dados simples; use Server Actions para callbacks.
Grandes payloads do servidor -- Buscar muitos dados em um Server Component e passá-los todos como props incha o payload RSC. Correção: Busque apenas o que o Client Component precisa; pague na paginação no servidor.
Bibliotecas de terceiros podem não ser compatíveis com RSC -- Bibliotecas que importam useState, useEffect ou APIs do navegador falham em Server Components. Correção: Importe-as apenas dentro de arquivos "use client", ou use um componente wrapper.
| Abordagem | Use Quando | Não Use Quando |
|---|---|---|
| Server Components | UI somente leitura, busca de dados, renderização zero-JS | UI interativa com estado ou efeitos |
| Client Components | UI interativa com hooks, eventos, APIs do navegador | Exibição pura de dados sem interatividade |
| Server-side rendering (SSR) | Aplicativos legados pré-RSC que precisam de HTML do servidor | Você tem acesso ao App Router |
| Static Site Generation | Conteúdo raramente muda e pode ser construído no momento da implantação | Dados são específicos do usuário ou altamente dinâmicos |
| API routes + client fetch | Consumidores externos precisam de um endpoint REST | Dados são consumidos apenas por suas próprias páginas |
De uma aplicação SaaS Next.js 15 / React 19 em produção (SystemsArchitect.io).
// Exemplo de produção: página de categoria de FAQ com acesso direto a dados
// Arquivo: src/app/faqs/[slug]/page.tsx
export default async function FaqCategoryPage({ params }: FaqCategoryPageProps) {
const { slug } = await params;
const category = await getFaqCategory(slug);
if (!category) {
notFound();
}
const iconConfig = getFaqIcon(category.slug);
const IconComponent = iconConfig.icon;
return (
<div className="min-h-screen bg-zinc-50 dark:bg-black">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<Link href="/faqs" className="inline-flex items-center gap-2 text-sm cursor-pointer">
<ChevronLeft className="h-4 w-4" />
<span>Voltar para FAQs</span>
</Link>
<div className="flex items-center gap-3 mb-4">
<div className={iconConfig.color}>
<IconComponent className="h-8 w-8" />
</div>
<h1 className="text-3xl font-bold">{category.title}</h1>
</div>
<FaqList faqs={category.faqs} categorySlug={category.slug} />
</div>
</div>
);
}O que isso demonstra em produção:
async e diretamente awaits dados de getFaqCategory() que chama Prisma internamenteawait params é o padrão do Next.js 15+ onde params agora é uma Promise em rotas dinâmicasnotFound() de next/navigation aciona o limite mais próximo de not-found.tsxFaqList (um Client Component) recebe dados pré-buscados como props. A fronteira servidor/cliente está no nível da propComponentes são Server Components por padrão no App Router. Nenhuma diretiva é necessária. Eles se tornam Client Components apenas quando você adiciona "use client" ao arquivo.
Sim. Server Components podem ser funções async e usar await diretamente no corpo da função. Isso não é permitido em Client Components.
Não. Server Components produzem um payload RSC (árvore React serializada) que é transmitida para o cliente. Dependências usadas apenas em Server Components (por exemplo, date-fns, parsers de markdown) não são incluídas no bundle do cliente.
Server Components não podem usar hooks do React (useState, useEffect, useRef, etc.) porque eles rodam no servidor e não re-renderizam no cliente. Extraia partes interativas para um componente "use client".
A biblioteca provavelmente importa useState, useEffect ou APIs do navegador internamente. Importe-a apenas dentro de um arquivo "use client", ou crie um wrapper fino de Client Component ao redor dela.
Apenas dados serializáveis: strings, números, booleanos, arrays, objetos simples e Server Actions. Você não pode passar funções regulares, instâncias de classe, Symbols ou nós DOM.
Use o pacote server-only:
import "server-only";
export function getSecretConfig() {
return { apiKey: process.env.SECRET_API_KEY! };
}Isso lança um erro de build se o arquivo for importado em um arquivo "use client".
Use Promise.all para executar múltiplos fetches concorrentemente:
const [users, revenue, orders] = await Promise.all([
fetchUsers(),
fetchRevenue(),
fetchOrders(),
]);Server Components não re-renderizam em resposta a mudanças de estado do lado do cliente. Eles re-executam apenas quando a rota muda ou quando revalidatePath/revalidateTag é chamado.
async function MyComponent(): Promise<JSX.Element> {
const data = await fetchData();
return <div>{data.name}</div>;
}Os tipos do React 19+ lidam com Promise<JSX.Element> para componentes async.
Defina um tipo com apenas campos serializáveis:
type SerializableProps = {
name: string;
count: number;
items: { id: string; label: string }[];
// NÃO permitido: onClick: () => void
};"use client"Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥