React 19 Server Components: What Changed and Why

Deep dive into React 19 Server Components: Actions, use() hook, asset loading, optimistic updates, and the architectural patterns that make RSC production-ready.

E
ECOSIRE Research and Development Team
|19 de março de 202611 min de leitura2.4k Palavras|

Parte da nossa série Performance & Scalability

Leia o guia completo

Componentes 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() substitui await nos componentes do cliente para consumir promessas e contexto
  • As ações do React 19 substituem os padrões manuais useState + fetch para envios de formulários
  • useOptimistic permite atualizações instantâneas da UI antes da confirmação do servidor
  • useFormStatus fornece 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 servidorComponentes do cliente
ContinuaSomente servidorServidor (renderização inicial) + Cliente
Pode usar ganchosNãoSim
Pode usar APIs de navegadorNãoSim
Pode acessar banco de dadosSim (diretamente)Não (via API)
Impacto no tamanho do pacoteZeroSim
Pode ser assíncronoSimNã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.

E

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.

Converse no WhatsApp