Voltar para o Blog

React 19 Server Components: Guia Pratico Para Desenvolvedores

Ola HaWkers, React 19 trouxe Server Components como uma feature estavel, mudando fundamentalmente como pensamos sobre renderizacao em React. Se voce ainda nao entendeu bem como funcionam ou quando usar, este guia pratico vai esclarecer tudo.

Voce ja se perguntou por que seus bundles JavaScript estao tao grandes ou por que a performance inicial da sua aplicacao e lenta? Server Components podem ser a resposta.

O Que Sao Server Components

Server Components sao componentes React que executam exclusivamente no servidor. Diferente de SSR tradicional, eles nunca enviam JavaScript para o cliente.

Diferenca Fundamental

Comparativo de abordagens:

Aspecto Client Components Server Components
Onde executa Browser Servidor
JavaScript no cliente Sim Nao
Acesso a banco de dados Via API Direto
Estado e eventos useState, onClick Nao disponivel
Performance inicial Mais lento Mais rapido

💡 Contexto: Server Components nao substituem Client Components. Eles trabalham juntos, cada um no que faz melhor.

Como Funcionam na Pratica

Vamos ver exemplos concretos de Server e Client Components:

Server Component Basico

// app/posts/page.tsx (Server Component por padrao)
import { db } from '@/lib/database';

// Este componente NUNCA vai para o browser
async function PostsPage() {
  // Acesso direto ao banco de dados
  const posts = await db.posts.findMany({
    orderBy: { createdAt: 'desc' },
    take: 10,
  });

  return (
    <div className="posts-container">
      <h1>Ultimos Posts</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
          <time>{new Date(post.createdAt).toLocaleDateString()}</time>
        </article>
      ))}
    </div>
  );
}

export default PostsPage;

Client Component

// components/LikeButton.tsx
'use client'; // Marcador obrigatorio

import { useState, useTransition } from 'react';
import { likePost } from '@/actions/posts';

interface LikeButtonProps {
  postId: string;
  initialLikes: number;
}

export function LikeButton({ postId, initialLikes }: LikeButtonProps) {
  const [likes, setLikes] = useState(initialLikes);
  const [isPending, startTransition] = useTransition();

  const handleLike = () => {
    startTransition(async () => {
      const newLikes = await likePost(postId);
      setLikes(newLikes);
    });
  };

  return (
    <button
      onClick={handleLike}
      disabled={isPending}
      className="like-button"
    >
      {isPending ? '...' : `❤️ ${likes}`}
    </button>
  );
}

Combinando Server e Client

// app/posts/[id]/page.tsx
import { db } from '@/lib/database';
import { LikeButton } from '@/components/LikeButton';
import { CommentSection } from '@/components/CommentSection';

async function PostPage({ params }: { params: { id: string } }) {
  const post = await db.posts.findUnique({
    where: { id: params.id },
    include: { author: true, comments: true },
  });

  if (!post) {
    return <div>Post nao encontrado</div>;
  }

  return (
    <article>
      <header>
        <h1>{post.title}</h1>
        <span>Por {post.author.name}</span>
      </header>

      <div className="content">
        {post.content}
      </div>

      {/* Client Component com interatividade */}
      <LikeButton postId={post.id} initialLikes={post.likes} />

      {/* Outro Client Component */}
      <CommentSection
        postId={post.id}
        initialComments={post.comments}
      />
    </article>
  );
}

export default PostPage;

Padroes de Composicao

A chave para usar Server Components efetivamente e entender padroes de composicao:

1. Passando Server Components como Children

// components/ClientWrapper.tsx
'use client';

import { useState } from 'react';

interface ClientWrapperProps {
  children: React.ReactNode;
}

export function ClientWrapper({ children }: ClientWrapperProps) {
  const [isExpanded, setIsExpanded] = useState(false);

  return (
    <div className={isExpanded ? 'expanded' : 'collapsed'}>
      <button onClick={() => setIsExpanded(!isExpanded)}>
        {isExpanded ? 'Recolher' : 'Expandir'}
      </button>
      {isExpanded && children}
    </div>
  );
}

// app/page.tsx (Server Component)
import { ClientWrapper } from '@/components/ClientWrapper';
import { ExpensiveServerComponent } from '@/components/ExpensiveServerComponent';

export default function Page() {
  return (
    <ClientWrapper>
      {/* Este Server Component e passado como children */}
      <ExpensiveServerComponent />
    </ClientWrapper>
  );
}

2. Slots Pattern

// components/Layout.tsx
'use client';

import { useState } from 'react';

interface LayoutProps {
  header: React.ReactNode;
  sidebar: React.ReactNode;
  content: React.ReactNode;
}

export function Layout({ header, sidebar, content }: LayoutProps) {
  const [sidebarOpen, setSidebarOpen] = useState(true);

  return (
    <div className="layout">
      <header>{header}</header>
      <button onClick={() => setSidebarOpen(!sidebarOpen)}>
        Toggle Sidebar
      </button>
      {sidebarOpen && <aside>{sidebar}</aside>}
      <main>{content}</main>
    </div>
  );
}

// app/dashboard/page.tsx
import { Layout } from '@/components/Layout';
import { UserStats } from '@/components/UserStats';
import { Navigation } from '@/components/Navigation';
import { DashboardContent } from '@/components/DashboardContent';

export default function DashboardPage() {
  return (
    <Layout
      header={<Navigation />}
      sidebar={<UserStats />}
      content={<DashboardContent />}
    />
  );
}

Server Actions

React 19 introduziu Server Actions para mutations de forma integrada:

Criando Server Actions

// actions/posts.ts
'use server';

import { db } from '@/lib/database';
import { revalidatePath } from 'next/cache';
import { z } from 'zod';

const PostSchema = z.object({
  title: z.string().min(1).max(200),
  content: z.string().min(10),
});

export async function createPost(formData: FormData) {
  const rawData = {
    title: formData.get('title'),
    content: formData.get('content'),
  };

  const validatedData = PostSchema.safeParse(rawData);

  if (!validatedData.success) {
    return { error: validatedData.error.flatten().fieldErrors };
  }

  try {
    const post = await db.posts.create({
      data: validatedData.data,
    });

    revalidatePath('/posts');
    return { success: true, postId: post.id };
  } catch (error) {
    return { error: 'Falha ao criar post' };
  }
}

export async function likePost(postId: string) {
  const post = await db.posts.update({
    where: { id: postId },
    data: { likes: { increment: 1 } },
  });

  revalidatePath(`/posts/${postId}`);
  return post.likes;
}

export async function deletePost(postId: string) {
  await db.posts.delete({ where: { id: postId } });
  revalidatePath('/posts');
}

Usando Server Actions em Forms

// components/CreatePostForm.tsx
'use client';

import { useActionState } from 'react';
import { createPost } from '@/actions/posts';

export function CreatePostForm() {
  const [state, formAction, isPending] = useActionState(createPost, null);

  return (
    <form action={formAction}>
      <div>
        <label htmlFor="title">Titulo</label>
        <input
          id="title"
          name="title"
          type="text"
          required
          disabled={isPending}
        />
        {state?.error?.title && (
          <span className="error">{state.error.title}</span>
        )}
      </div>

      <div>
        <label htmlFor="content">Conteudo</label>
        <textarea
          id="content"
          name="content"
          rows={10}
          required
          disabled={isPending}
        />
        {state?.error?.content && (
          <span className="error">{state.error.content}</span>
        )}
      </div>

      <button type="submit" disabled={isPending}>
        {isPending ? 'Criando...' : 'Criar Post'}
      </button>

      {state?.success && (
        <div className="success">Post criado com sucesso!</div>
      )}
    </form>
  );
}

Otimizacao e Performance

Server Components trazem beneficios significativos de performance:

1. Streaming com Suspense

// app/dashboard/page.tsx
import { Suspense } from 'react';
import { SlowDataComponent } from '@/components/SlowDataComponent';
import { FastDataComponent } from '@/components/FastDataComponent';

export default function DashboardPage() {
  return (
    <div className="dashboard">
      <h1>Dashboard</h1>

      {/* Componente rapido renderiza imediatamente */}
      <FastDataComponent />

      {/* Componente lento carrega em streaming */}
      <Suspense fallback={<div>Carregando dados...</div>}>
        <SlowDataComponent />
      </Suspense>

      {/* Varios Suspense para streaming paralelo */}
      <div className="widgets">
        <Suspense fallback={<WidgetSkeleton />}>
          <AnalyticsWidget />
        </Suspense>

        <Suspense fallback={<WidgetSkeleton />}>
          <RevenueWidget />
        </Suspense>

        <Suspense fallback={<WidgetSkeleton />}>
          <UsersWidget />
        </Suspense>
      </div>
    </div>
  );
}

2. Preloading de Dados

// lib/preload.ts
import { cache } from 'react';

export const getUser = cache(async (userId: string) => {
  const user = await db.users.findUnique({ where: { id: userId } });
  return user;
});

export const preloadUser = (userId: string) => {
  void getUser(userId);
};

// components/UserProfile.tsx
import { getUser, preloadUser } from '@/lib/preload';

// Em um componente pai
function ParentComponent({ userId }: { userId: string }) {
  // Inicia o fetch antecipadamente
  preloadUser(userId);

  return (
    <Suspense fallback={<ProfileSkeleton />}>
      <UserProfile userId={userId} />
    </Suspense>
  );
}

// O componente filho usa os dados pre-carregados
async function UserProfile({ userId }: { userId: string }) {
  const user = await getUser(userId); // Ja esta em cache!

  return (
    <div className="profile">
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
    </div>
  );
}

Quando Usar Cada Tipo

Guia pratico para decidir entre Server e Client Components:

Use Server Components Para

Casos ideais:

  • Fetch de dados do banco
  • Acesso a APIs privadas
  • Componentes sem interatividade
  • Conteudo estatico ou semi-estatico
  • Reducao de bundle size
// Exemplos de Server Components
// - Listagens de dados
// - Headers e footers
// - Navegacao estatica
// - Conteudo de paginas
// - Componentes de layout

async function ProductList() {
  const products = await db.products.findMany();

  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>
          <h3>{product.name}</h3>
          <p>{product.price}</p>
        </li>
      ))}
    </ul>
  );
}

Use Client Components Para

Casos ideais:

  • Interatividade (onClick, onChange)
  • Estado local (useState)
  • Efeitos (useEffect)
  • Browser APIs (localStorage, geolocation)
  • Bibliotecas client-only
// Exemplos de Client Components
// - Formularios
// - Modais e dialogs
// - Carroseis e sliders
// - Tooltips e dropdowns
// - Componentes com animacao

'use client';

function SearchInput() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  useEffect(() => {
    const search = async () => {
      if (query.length > 2) {
        const data = await fetch(`/api/search?q=${query}`);
        setResults(await data.json());
      }
    };
    const timer = setTimeout(search, 300);
    return () => clearTimeout(timer);
  }, [query]);

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Buscar..."
      />
      <ul>
        {results.map(r => <li key={r.id}>{r.title}</li>)}
      </ul>
    </div>
  );
}

Erros Comuns e Solucoes

Evite estes problemas frequentes:

1. Importar Client Component sem 'use client'

// ❌ Errado: usando hooks sem 'use client'
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // Erro!
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

// ✅ Correto: adicionar 'use client'
'use client';

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

2. Passar Funcoes Nao Serializaveis

// ❌ Errado: passando funcao de Server para Client
async function ServerComponent() {
  const handleClick = () => console.log('clicked'); // Nao serializavel!

  return <ClientButton onClick={handleClick} />; // Erro!
}

// ✅ Correto: usar Server Action
async function ServerComponent() {
  return <ClientButton postId="123" />;
}

// Client Component chama a action
'use client';
import { likePost } from '@/actions/posts';

function ClientButton({ postId }: { postId: string }) {
  return <button onClick={() => likePost(postId)}>Like</button>;
}

3. Fetch em Client Component Desnecessariamente

// ❌ Errado: fetching no cliente quando poderia ser no servidor
'use client';

function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch('/api/users').then(r => r.json()).then(setUsers);
  }, []);

  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

// ✅ Correto: Server Component com acesso direto
async function UserList() {
  const users = await db.users.findMany();

  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

Migrando Para Server Components

Se voce tem uma aplicacao React existente:

Estrategia de Migracao

Passos recomendados:

  1. Identifique componentes sem interatividade
  2. Mova fetching de dados para Server Components
  3. Adicione 'use client' onde necessario
  4. Refatore para padroes de composicao
  5. Implemente Server Actions para mutations

Exemplo de Migracao

// Antes: tudo no cliente
'use client';

function ProductPage({ productId }) {
  const [product, setProduct] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(`/api/products/${productId}`)
      .then(r => r.json())
      .then(data => {
        setProduct(data);
        setLoading(false);
      });
  }, [productId]);

  if (loading) return <div>Carregando...</div>;

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <AddToCartButton productId={productId} />
    </div>
  );
}

// Depois: Server Component com Client Component para interatividade
// app/products/[id]/page.tsx (Server Component)
async function ProductPage({ params }) {
  const product = await db.products.findUnique({
    where: { id: params.id }
  });

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <AddToCartButton productId={params.id} />
    </div>
  );
}

// components/AddToCartButton.tsx (Client Component)
'use client';

function AddToCartButton({ productId }) {
  const [added, setAdded] = useState(false);

  return (
    <button onClick={() => {
      addToCart(productId);
      setAdded(true);
    }}>
      {added ? 'Adicionado!' : 'Adicionar ao Carrinho'}
    </button>
  );
}

Conclusao

React 19 Server Components representam uma mudanca fundamental em como construimos aplicacoes React. Ao executar componentes no servidor e enviar apenas HTML para o cliente, conseguimos aplicacoes mais rapidas, bundles menores, e acesso direto a recursos do servidor.

A chave e entender que Server e Client Components trabalham juntos. Use Server Components para buscar dados e renderizar conteudo estatico, e Client Components para interatividade. Com essa divisao clara, suas aplicacoes serao mais performaticas e mais faceis de manter.

Se voce quer se aprofundar em React e desenvolvimento frontend moderno, recomendo que de uma olhada em outro artigo: Svelte 5 e Runes: Por Que o Framework Esta Ganhando Terreno onde voce vai descobrir alternativas interessantes ao React.

Bora pra cima! 🦅

💻 Domine JavaScript de Verdade

O conhecimento que voce adquiriu neste artigo e so o comeco. Ha tecnicas, padroes e praticas que transformam desenvolvedores iniciantes em profissionais requisitados.

Invista no Seu Futuro

Preparei um material completo para voce dominar JavaScript:

Formas de pagamento:

  • 1x de R$9,90 sem juros
  • ou R$9,90 a vista

📖 Ver Conteudo Completo

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário