Parte da nossa série Performance & Scalability
Leia o guia completoComponentes do servidor React 19: o que mudou e por quê
React 19 é o lançamento mais significativo desde os ganchos. Os componentes do servidor, que começaram como um recurso experimental no React 18, agora estão estáveis e totalmente integrados ao modelo de renderização simultânea. Mas as mudanças vão muito além de apenas estabilizar o RSC – o React 19 apresenta Actions, um novo gancho use(), integração de formulários, atualizações otimistas e gerenciamento de metadados de documentos que mudam coletivamente a forma como você pensa sobre o fluxo de dados em aplicativos React.
Este guia foca no que realmente mudou entre o React 18 e 19, explica o raciocínio arquitetônico por trás de cada mudança e mostra os padrões de produção que substituem as abordagens antigas.
Principais conclusões
- Os componentes do servidor são executados apenas no servidor — eles não têm ciclo de vida, estado e APIs de navegador
- O gancho
use()substituiawaitnos componentes do cliente para consumir promessas e contexto- As ações do React 19 substituem os padrões manuais
useState+fetchpara envios de formuláriosuseOptimisticpermite atualizações instantâneas da UI antes da confirmação do servidoruseFormStatusfornece estado pendente sem detalhamento de componentes do formulário- APIs de pré-carregamento de ativos (
preload,preinit) permitem controlar o carregamento de recursos de componentes- As tags
<title>,<meta>e<link>em componentes são elevadas automaticamente para<head>- Componentes de servidor e componentes de cliente formam uma árvore — os componentes de cliente não podem importar componentes de servidor
O que realmente são os componentes do servidor
Antes de discutir o que mudou, esclareça o que são os componentes do servidor: Componentes React que são renderizados exclusivamente no servidor e produzem carga útil HTML + RSC que o cliente hidrata. Eles não são iguais aos componentes do cliente renderizados no lado do servidor.
As principais diferenças:
| Componentes do servidor | Componentes do cliente | |
|---|---|---|
| Continua | Somente servidor | Servidor (renderização inicial) + Cliente |
| Pode usar ganchos | Não | Sim |
| Pode usar APIs de navegador | Não | Sim |
| Pode acessar banco de dados | Sim (diretamente) | Não (via API) |
| Impacto no tamanho do pacote | Zero | Sim |
| Pode ser assíncrono | Sim | Não (sem Suspense) |
// Server Component — async, no hooks, direct DB access
async function UserProfile({ userId }: { userId: string }) {
// Direct database query — no API needed
const user = await db.query.users.findFirst({
where: eq(users.id, userId),
});
return (
<div>
<h1>{user.name}</h1>
{/* Client Component gets data as props */}
<UserActions userId={user.id} role={user.role} />
</div>
);
}
// Client Component — can use hooks, handles interactions
'use client';
function UserActions({ userId, role }: { userId: string; role: string }) {
const [isEditing, setIsEditing] = useState(false);
return (
<div>
<button onClick={() => setIsEditing(true)}>Edit</button>
{isEditing && <EditUserForm userId={userId} />}
</div>
);
}
Reaja 19 ações
A maior melhoria ergonômica no React 19 são as Ações — uma forma padronizada de lidar com operações assíncronas acionadas por interações do usuário, principalmente envios de formulários.
Antes do React 19, o tratamento de formulários era um padrão repetitivo:
// React 18 — manual state management
function ContactForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSubmitting(true);
setError(null);
try {
await createContact({ name, email });
} catch (err) {
setError(err.message);
} finally {
setSubmitting(false);
}
}
return (
<form onSubmit={handleSubmit}>
{/* ... */}
</form>
);
}
As ações do React 19 simplificam isso significativamente:
// React 19 — useActionState
'use client';
import { useActionState } from 'react';
async function createContactAction(
prevState: { error?: string },
formData: FormData
) {
'use server'; // Server Action — runs on the server
const name = formData.get('name') as string;
const email = formData.get('email') as string;
try {
await createContact({ name, email });
return { success: true };
} catch (err) {
return { error: err.message };
}
}
function ContactForm() {
const [state, formAction, isPending] = useActionState(
createContactAction,
{}
);
return (
<form action={formAction}>
<input name="name" required />
<input name="email" type="email" required />
{state.error && <p className="text-red-500">{state.error}</p>}
<button type="submit" disabled={isPending}>
{isPending ? 'Submitting...' : 'Submit'}
</button>
</form>
);
}
O gancho useActionState gerencia estado pendente, dados de formulário e tratamento de erros em um só lugar. A diretiva 'use server' na função de ação a marca como uma Ação do Servidor — ela é executada no servidor, pode acessar o banco de dados diretamente e o cliente nunca vê a implementação.
Ações do servidor
Ações do servidor são funções assíncronas com a diretiva 'use server' que são executadas no servidor quando chamadas pelo cliente. Elas substituem as rotas de API para a maioria dos casos de uso de mutação de dados:
// app/actions/contacts.ts
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { db } from '@ecosire/db';
import { contacts } from '@ecosire/db/schema';
export async function createContact(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
if (!name || !email) {
throw new Error('Name and email are required');
}
await db.insert(contacts).values({
name,
email,
organizationId: await getOrganizationId(), // From session
});
revalidatePath('/dashboard/contacts'); // Invalidate cached page
redirect('/dashboard/contacts'); // Redirect after success
}
As ações do servidor podem ser usadas diretamente nos elementos form:
import { createContact } from '@/app/actions/contacts';
export default function NewContactPage() {
return (
<form action={createContact}>
<input name="name" placeholder="Name" required />
<input name="email" placeholder="Email" type="email" required />
<button type="submit">Create Contact</button>
</form>
);
}
Sem manipulador onSubmit, sem preventDefault(), sem fetch() manual - o formulário simplesmente funciona.
O gancho use()
O React 19 introduz o gancho use() para consumir recursos (promessas e contexto) dentro da renderização. Ao contrário de await, funciona com o sistema Suspense do React:
// Before React 19 — had to resolve at the top level
export default async function Page() {
const posts = await getPosts(); // Blocks entire page
return <PostList posts={posts} />;
}
// React 19 — pass a promise, resolve with use()
export default function Page() {
const postsPromise = getPosts(); // Start fetch immediately
return (
<Suspense fallback={<PostsSkeleton />}>
<PostList postsPromise={postsPromise} />
</Suspense>
);
}
// Client Component using use() to consume the promise
'use client';
function PostList({ postsPromise }: { postsPromise: Promise<Post[]> }) {
const posts = use(postsPromise); // Suspends until resolved
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
O gancho use() também substitui useContext() — ele pode consumir contexto condicionalmente (ao contrário dos ganchos, use() pode ser chamado dentro de condições e loops):
'use client';
import { use } from 'react';
import { ThemeContext } from '@/contexts/theme';
function Button({ children }: { children: React.ReactNode }) {
const theme = use(ThemeContext); // Can be inside conditions
return (
<button className={theme === 'dark' ? 'bg-gray-800' : 'bg-white'}>
{children}
</button>
);
}
Atualizações otimistas com useOptimistic
useOptimistic fornece feedback instantâneo da interface do usuário antes da conclusão de uma operação do servidor, revertendo automaticamente se a operação falhar:
'use client';
import { useOptimistic, useTransition } from 'react';
interface Like {
id: string;
count: number;
userLiked: boolean;
}
function LikeButton({ postId, initialLikes }: { postId: string; initialLikes: Like }) {
const [likes, setOptimisticLikes] = useOptimistic(
initialLikes,
(state, action: 'like' | 'unlike') => ({
...state,
count: action === 'like' ? state.count + 1 : state.count - 1,
userLiked: action === 'like',
})
);
const [isPending, startTransition] = useTransition();
async function toggleLike() {
const action = likes.userLiked ? 'unlike' : 'like';
startTransition(async () => {
setOptimisticLikes(action); // Instant UI update
// Server operation — if this fails, optimistic state reverts
await togglePostLike(postId, action);
});
}
return (
<button onClick={toggleLike} disabled={isPending}>
{likes.userLiked ? '♥' : '♡'} {likes.count}
</button>
);
}
A atualização otimista é exibida imediatamente e, em seguida, é confirmada (sucesso do servidor) ou revertida (falha do servidor). Os usuários obtêm feedback instantâneo sem esperar pelas viagens de ida e volta da rede.
Documentar metadados de componentes
O React 19 permite que as tags <title>, <meta> e <link> sejam renderizadas dentro de qualquer componente – elas são automaticamente içadas para o documento <head>:
// Server Component — metadata hoists to <head> automatically
async function BlogPost({ slug }: { slug: string }) {
const post = await getPost(slug);
return (
<article>
<title>{post.title}</title>
<meta name="description" content={post.description} />
<link rel="canonical" href={`https://ecosire.com/blog/${slug}`} />
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
Isso funciona em componentes de servidor e cliente. Em frameworks como Next.js, você ainda usará generateMetadata() para controle total sobre tags OpenGraph e atributos hreflang – o suporte nativo do React 19 é mais básico. Mas para casos simples, elimina a necessidade de next/head ou bibliotecas semelhantes.
APIs de carregamento de ativos
O React 19 fornece APIs explícitas para pré-carregar recursos, proporcionando controle refinado sobre a cascata de carregamento de recursos:
import { preload, preinit, prefetchDNS, preconnect } from 'react-dom';
function BelowFoldSection() {
// When this component renders, preload the hero image for next section
preload('/images/hero-next.webp', { as: 'image' });
// Preinit a script (loads and executes immediately)
preinit('https://cdn.example.com/analytics.js', { as: 'script' });
// DNS prefetch for external resources
prefetchDNS('https://fonts.googleapis.com');
// Establish connection early
preconnect('https://api.ecosire.com');
return <section>{/* content */}</section>;
}
Essas APIs funcionam corretamente com o modelo de renderização do React — elas agrupam dicas de recursos e as emitem no ponto ideal do documento, mesmo nas profundezas da árvore de componentes.
Armadilhas e soluções comuns
Armadilha 1: tentar usar ganchos em componentes de servidor
Os componentes do servidor não podem usar useState, useEffect, useContext ou qualquer outro gancho. Se precisar deles, adicione 'use client' ao componente. O erro geralmente é: Error: Hooks can only be called inside a function component.
Armadilha 2: importar componentes de servidor de componentes de cliente
Os componentes do cliente não podem importar componentes do servidor (o inverso é adequado). Isso ocorre porque os componentes do cliente são executados no navegador onde os componentes do servidor não existem. Se precisar compô-los, passe os componentes do servidor como props children:
// Wrong — Client Component cannot import Server Component
'use client';
import { ServerUserProfile } from './server-user-profile'; // Error
// Correct — pass as children from a Server Component parent
// Parent (Server):
<ClientShell>
<ServerUserProfile userId={userId} />
</ClientShell>
// ClientShell:
'use client';
function ClientShell({ children }: { children: React.ReactNode }) {
return <div className="shell">{children}</div>;
}
Armadilha 3: erros de serialização ao passar props do servidor para o cliente
Somente dados serializáveis em JSON podem cruzar o limite Servidor-Cliente como adereços. Funções, instâncias de classe, Mapa, Conjunto e Datas (como objetos) não podem ser serializadas. Converta datas em strings ISO; substitua funções por identificadores de string.
Armadilha 4: as ações do servidor não validam a entrada
Nunca confie nos dados fornecidos pelo cliente nas Ações do Servidor. Sempre valide com Zod ou similar antes de gravar no banco de dados:
'use server';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(2).max(255),
email: z.string().email(),
});
export async function createContact(formData: FormData) {
const result = schema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
});
if (!result.success) {
return { error: result.error.flatten().fieldErrors };
}
await db.insert(contacts).values(result.data);
}
Perguntas frequentes
Os componentes do servidor estão disponíveis fora do Next.js?
Os componentes do React Server são um recurso do React, mas exigem uma estrutura para lidar com a infraestrutura de renderização do servidor – roteamento, empacotamento, streaming. Next.js App Router é a implementação mais madura. As configurações baseadas em Remix, Astro e Vite estão adicionando suporte RSC. Usar o RSC sem uma estrutura requer um trabalho significativo de infraestrutura personalizada.
Como as ações do servidor se comparam às rotas da API REST?
As ações do servidor são mais simples para mutações localizadas na UI – sem URL de endpoint para gerenciar, sem chamada fetch() para gravar, sem padrão de tratamento de erros. As rotas da API REST são melhores para operações chamadas de fora do aplicativo Web (aplicativos móveis, webhooks, integrações de terceiros), para APIs públicas que precisam de documentação e quando você precisa de códigos de status HTTP explícitos. Use ambos no mesmo aplicativo com base no caso de uso.
Qual é o impacto no desempenho dos componentes do servidor?
Os componentes do servidor reduzem o tamanho do pacote JavaScript (o código do componente nunca é enviado para o navegador), eliminam cascatas de busca de dados do lado do cliente e permitem a entrega de streaming de HTML via Suspense. A desvantagem é o custo de computação do servidor – a renderização acontece nos seus servidores, não no dispositivo do cliente. Para páginas com muitos dados, isso quase sempre é uma vitória líquida.
Posso misturar React 18 e React 19 em uma base de código?
Todo o código React é executado como React 19 — não há controle de versão por arquivo. A questão é se o seu código React 18 existente funciona no React 19. A maior parte do código React 18 funciona inalterado. As principais alterações importantes são em torno de refs (agora um suporte regular), remoção de ReactDOM.render e algumas alterações de digitação em @types/react. Execute os codemods React 19 para migração automatizada.
Como faço para testar os componentes do servidor?
Os componentes do servidor podem ser testados com React Testing Library usando async render. Para ações do servidor, teste-as como funções assíncronas simples com chamadas de banco de dados simuladas. Os testes ponta a ponta com o Playwright cobrem a integração completa do componente servidor + componente cliente sem qualquer configuração especial - eles testam a saída HTML final.
Próximas etapas
Os componentes do servidor React 19 representam uma mudança fundamental na forma como os aplicativos da web modernos são construídos. A equipe de front-end da ECOSIRE construiu aplicativos de produção nesta arquitetura — 249 páginas com componentes de servidor, ações de servidor e atualizações otimistas que alimentam fluxos de trabalho de usuários reais.
Se você precisar de ajuda para migrar do React 18 para o 19, arquitetar um novo aplicativo RSC-first ou apenas quiser engenharia de front-end especializada em sua equipe, explore nossos serviços de desenvolvimento.
Escrito por
ECOSIRE Research and Development Team
Construindo produtos digitais de nível empresarial na ECOSIRE. Compartilhando insights sobre integrações Odoo, automação de e-commerce e soluções de negócios com IA.
Artigos Relacionados
k6 Load Testing: Stress-Test Your APIs Before Launch
Master k6 load testing for Node.js APIs. Covers virtual user ramp-ups, thresholds, scenarios, HTTP/2, WebSocket testing, Grafana dashboards, and CI integration patterns.
Next.js 16 App Router: Production Patterns and Pitfalls
Production-ready Next.js 16 App Router patterns: server components, caching strategies, metadata API, error boundaries, and performance pitfalls to avoid.
Odoo Performance Tuning: PostgreSQL and Server Optimization
Expert guide to Odoo 19 performance tuning. Covers PostgreSQL configuration, indexing, query optimization, Nginx caching, and server sizing for enterprise deployments.
Mais de Performance & Scalability
k6 Load Testing: Stress-Test Your APIs Before Launch
Master k6 load testing for Node.js APIs. Covers virtual user ramp-ups, thresholds, scenarios, HTTP/2, WebSocket testing, Grafana dashboards, and CI integration patterns.
Nginx Production Configuration: SSL, Caching, and Security
Nginx production configuration guide: SSL termination, HTTP/2, caching headers, security headers, rate limiting, reverse proxy setup, and Cloudflare integration patterns.
Odoo Performance Tuning: PostgreSQL and Server Optimization
Expert guide to Odoo 19 performance tuning. Covers PostgreSQL configuration, indexing, query optimization, Nginx caching, and server sizing for enterprise deployments.
Odoo vs Acumatica: Cloud ERP for Growing Businesses
Odoo vs Acumatica compared for 2026: unique pricing models, scalability, manufacturing depth, and which cloud ERP fits your growth trajectory.
Testing and Monitoring AI Agents in Production
A complete guide to testing and monitoring AI agents in production environments. Covers evaluation frameworks, observability, drift detection, and incident response for OpenClaw deployments.
Compliance Monitoring Agents with OpenClaw
Deploy OpenClaw AI agents for continuous compliance monitoring. Automate regulatory checks, policy enforcement, audit trail generation, and compliance reporting.