Voltar para o Blog

React Server Components: A Revolução de Performance que Está Dominando 2025

Olá HaWkers, React Server Components (RSC) se tornaram o padrão de facto para aplicações React de alta performance em 2025. Com frameworks como Next.js 15 adotando RSC como padrão, entender essa tecnologia não é mais opcional.

Mas o que exatamente são Server Components? E por que empresas como Vercel, Meta e Shopify estão migrando massivamente para essa arquitetura?

O Problema que RSC Resolve

Bundle Size Crescendo Exponencialmente

// ❌ Componente tradicional (Client Component)
// TUDO vai para o bundle JavaScript do cliente
import { useState } from "react";
import { formatDate } from "date-fns"; // 100KB
import { marked } from "marked"; // 50KB
import { Prism } from "prism-react-renderer"; // 80KB
import _ from "lodash"; // 70KB

export default function BlogPost({ slug }) {
  const [post, setPost] = useState(null);

  useEffect(() => {
    fetch(`/api/posts/${slug}`)
      .then((res) => res.json())
      .then((data) => {
        setPost(data);
      });
  }, [slug]);

  if (!post) return <div>Loading...</div>;

  const html = marked(post.content);
  const formattedDate = formatDate(post.date, "PPP");

  return (
    <article>
      <h1>{post.title}</h1>
      <time>{formattedDate}</time>
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </article>
  );
}

// Bundle JavaScript enviado ao cliente: ~300KB
// Time to Interactive: 3-5 segundos em 3G

A Solução com React Server Components

// ✅ Server Component
// Zero JavaScript enviado ao cliente para este componente!
import { formatDate } from "date-fns";
import { marked } from "marked";
import db from "@/lib/database";

// Este componente roda APENAS no servidor
export default async function BlogPost({ slug }) {
  // Fetch direto do banco de dados (não precisa de API route)
  const post = await db.post.findUnique({
    where: { slug },
  });

  if (!post) return <div>Post not found</div>;

  // Processamento pesado no servidor
  const html = marked(post.content);
  const formattedDate = formatDate(post.date, "PPP");

  return (
    <article>
      <h1>{post.title}</h1>
      <time>{formattedDate}</time>
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </article>
  );
}

// Bundle JavaScript enviado ao cliente: ~0KB (só HTML)
// Time to Interactive: instantâneo

Como React Server Components Funcionam

Arquitetura de Renderização

// 1. Server Component (padrão em Next.js 13+)
// Arquivo: app/dashboard/page.tsx

import { Suspense } from "react";
import { Analytics } from "./analytics"; // Server Component
import { UserProfile } from "./user-profile"; // Server Component
import { ChatWidget } from "./chat-widget"; // Client Component

export default async function DashboardPage() {
  // Fetch paralelo de dados no servidor
  const [user, stats] = await Promise.all([
    fetchUser(),
    fetchAnalytics(),
  ]);

  return (
    <div>
      <h1>Dashboard</h1>

      {/* Server Component - renderiza no servidor */}
      <UserProfile user={user} />

      {/* Streaming com Suspense */}
      <Suspense fallback={<AnalyticsSkeleton />}>
        <Analytics data={stats} />
      </Suspense>

      {/* Client Component - interatividade no cliente */}
      <ChatWidget userId={user.id} />
    </div>
  );
}

// Funções assíncronas diretas (sem useEffect!)
async function fetchUser() {
  const res = await fetch("https://api.example.com/user", {
    cache: "force-cache", // Cache automático
  });
  return res.json();
}

async function fetchAnalytics() {
  const res = await fetch("https://api.example.com/analytics", {
    next: { revalidate: 60 }, // Revalidar a cada 60s
  });
  return res.json();
}

Server vs Client Components: Quando Usar?

// ✅ Use Server Components (padrão) para:
// - Fetch de dados
// - Acesso direto ao backend (DB, filesystem, APIs internas)
// - Renderização de conteúdo pesado
// - Operações que requerem secrets/tokens
// - Reduzir bundle JavaScript

// app/products/[id]/page.tsx (Server Component)
import db from "@/lib/db";

export default async function ProductPage({ params }) {
  const product = await db.product.findUnique({
    where: { id: params.id },
    include: { reviews: true, category: true },
  });

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

// ✅ Use Client Components APENAS para:
// - Interatividade (onClick, onChange, etc)
// - Hooks (useState, useEffect, useContext, etc)
// - Browser APIs (localStorage, window, document)
// - Event listeners
// - React lifecycle

// app/components/add-to-cart-button.tsx (Client Component)
"use client"; // Diretiva obrigatória para Client Components

import { useState } from "react";
import { useCart } from "@/hooks/use-cart";

export function AddToCartButton({ productId }: { productId: string }) {
  const [isAdding, setIsAdding] = useState(false);
  const { addItem } = useCart();

  const handleClick = async () => {
    setIsAdding(true);
    await addItem(productId);
    setIsAdding(false);
  };

  return (
    <button onClick={handleClick} disabled={isAdding}>
      {isAdding ? "Adding..." : "Add to Cart"}
    </button>
  );
}

Padrões Avançados com RSC

1. Streaming com Suspense

// app/dashboard/page.tsx
import { Suspense } from "react";

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>

      {/* Componente leve renderiza imediatamente */}
      <UserGreeting />

      {/* Componente pesado faz streaming quando pronto */}
      <Suspense fallback={<ChartsSkeleton />}>
        <Charts />
      </Suspense>

      <Suspense fallback={<TableSkeleton />}>
        <DataTable />
      </Suspense>
    </div>
  );
}

// Server Component com fetch lento
async function Charts() {
  // Simula query complexa de analytics
  const data = await fetchAnalyticsData(); // 2-3 segundos

  return <ComplexChart data={data} />;
}

async function DataTable() {
  const data = await fetchTableData(); // 1-2 segundos

  return <Table data={data} />;
}

// Resultado: HTML inicial enviado instantaneamente
// Charts e DataTable fazem streaming quando prontos
// UX muito melhor que loading spinner tradicional!

2. Composição de Server + Client Components

// app/posts/[slug]/page.tsx (Server Component)
import { CommentSection } from "./comment-section"; // Client Component
import { ShareButtons } from "./share-buttons"; // Client Component
import { RelatedPosts } from "./related-posts"; // Server Component

export default async function PostPage({ params }) {
  // Fetch no servidor
  const post = await getPost(params.slug);
  const relatedPosts = await getRelatedPosts(post.tags);

  return (
    <article>
      {/* Conteúdo estático renderizado no servidor */}
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.html }} />

      {/* Componentes interativos (Client Components) */}
      <ShareButtons url={post.url} title={post.title} />
      <CommentSection postId={post.id} />

      {/* Mais conteúdo estático (Server Component) */}
      <RelatedPosts posts={relatedPosts} />
    </article>
  );
}

// comment-section.tsx (Client Component)
"use client";

import { useState } from "react";

export function CommentSection({ postId }: { postId: string }) {
  const [comments, setComments] = useState([]);
  const [newComment, setNewComment] = useState("");

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    await fetch("/api/comments", {
      method: "POST",
      body: JSON.stringify({ postId, text: newComment }),
    });

    setNewComment("");
    // Refresh comments
  };

  return (
    <div>
      <h2>Comments</h2>
      <form onSubmit={handleSubmit}>
        <textarea
          value={newComment}
          onChange={(e) => setNewComment(e.target.value)}
          placeholder="Write a comment..."
        />
        <button type="submit">Post Comment</button>
      </form>

      <ul>
        {comments.map((comment) => (
          <li key={comment.id}>{comment.text}</li>
        ))}
      </ul>
    </div>
  );
}

3. Data Fetching com Cache Inteligente

// lib/data.ts
import { cache } from "react";

// React cache() deduplica requests automáticamente
export const getUser = cache(async (id: string) => {
  console.log("Fetching user", id); // Log só aparece 1 vez!

  const user = await db.user.findUnique({ where: { id } });
  return user;
});

// app/profile/page.tsx
export default async function ProfilePage() {
  const user = await getUser("123"); // Primeira chamada

  return (
    <div>
      <UserHeader user={user} />
      <UserPosts userId={user.id} />
      <UserActivity userId={user.id} />
    </div>
  );
}

// components/user-posts.tsx
async function UserPosts({ userId }: { userId: string }) {
  const user = await getUser(userId); // Cache hit! Não faz fetch novamente

  const posts = await db.post.findMany({
    where: { authorId: user.id },
  });

  return <PostList posts={posts} />;
}

// components/user-activity.tsx
async function UserActivity({ userId }: { userId: string }) {
  const user = await getUser(userId); // Cache hit novamente!

  const activity = await db.activity.findMany({
    where: { userId: user.id },
  });

  return <ActivityFeed activity={activity} />;
}

// Resultado: getUser() chamado 3 vezes no código
// Mas executa apenas 1 query no banco!

Performance: Números Reais

Benchmark: App Tradicional vs RSC

// Caso de estudo: Dashboard de e-commerce
// Métricas coletadas em conexão 3G simulada

// ❌ Abordagem tradicional (Client-Side Rendering)
const traditionalMetrics = {
  bundleSize: "450 KB", // JavaScript comprimido
  firstContentfulPaint: "2.8s",
  timeToInteractive: "4.2s",
  totalBlockingTime: "890ms",
  cumulativeLayoutShift: 0.15,
  lighthouseScore: 68,
};

// ✅ Com React Server Components
const rscMetrics = {
  bundleSize: "85 KB", // 81% menor!
  firstContentfulPaint: "0.9s", // 68% mais rápido
  timeToInteractive: "1.3s", // 69% mais rápido
  totalBlockingTime: "120ms", // 86% melhor
  cumulativeLayoutShift: 0.02, // 87% melhor
  lighthouseScore: 96, // +28 pontos
};

// Impacto no negócio:
// - Bounce rate: -35%
// - Conversion rate: +22%
// - SEO ranking: +15 posições
// - Server costs: -40% (menos API calls)

Otimizações Práticas

// 1. Prefetching automático com Next.js
import Link from "next/link";

export function Navigation() {
  return (
    <nav>
      {/* Prefetch automático em hover */}
      <Link href="/dashboard" prefetch={true}>
        Dashboard
      </Link>

      {/* Prefetch apenas quando visível */}
      <Link href="/reports" prefetch={false}>
        Reports
      </Link>
    </nav>
  );
}

// 2. Parallel Data Fetching
export default async function ProductPage({ params }) {
  // ❌ Sequencial (lento)
  // const product = await getProduct(params.id);
  // const reviews = await getReviews(params.id);
  // const related = await getRelated(params.id);
  // Total: 600ms + 400ms + 300ms = 1300ms

  // ✅ Paralelo (rápido)
  const [product, reviews, related] = await Promise.all([
    getProduct(params.id), // 600ms
    getReviews(params.id), // 400ms
    getRelated(params.id), // 300ms
  ]);
  // Total: max(600, 400, 300) = 600ms

  return <ProductDetails product={product} reviews={reviews} related={related} />;
}

// 3. Partial Prerendering (Next.js 14+)
// next.config.js
module.exports = {
  experimental: {
    ppr: true, // Partial Prerendering
  },
};

// app/dashboard/page.tsx
export default async function Dashboard() {
  return (
    <div>
      {/* Parte estática pré-renderizada */}
      <StaticHeader />
      <StaticSidebar />

      {/* Parte dinâmica com streaming */}
      <Suspense fallback={<Skeleton />}>
        <DynamicContent />
      </Suspense>
    </div>
  );
}

Migrando para Server Components

Estratégia de Migração Gradual

// Fase 1: Identificar componentes candidatos
const migrationCandidates = {
  highPriority: [
    "Componentes com fetch pesado",
    "Páginas de conteúdo estático",
    "Componentes que usam bibliotecas grandes",
  ],
  mediumPriority: [
    "Componentes sem interatividade",
    "Páginas de listagem/catálogo",
  ],
  lowPriority: ["Componentes com pouca interatividade"],
  neverMigrate: [
    "Componentes com event handlers",
    "Componentes usando hooks",
    "Componentes usando browser APIs",
  ],
};

// Fase 2: Converter componente por componente

// ANTES (Client Component)
// app/products/page.tsx
"use client";

import { useEffect, useState } from "react";

export default function ProductsPage() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("/api/products")
      .then((res) => res.json())
      .then((data) => {
        setProducts(data);
        setLoading(false);
      });
  }, []);

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

  return (
    <div>
      {products.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

// DEPOIS (Server Component)
// app/products/page.tsx
export default async function ProductsPage() {
  // Fetch direto no servidor
  const products = await fetch("https://api.example.com/products", {
    next: { revalidate: 3600 }, // Cache por 1 hora
  }).then((res) => res.json());

  return (
    <div>
      {products.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

// Benefícios imediatos:
// - Sem useState/useEffect
// - Sem loading states
// - Bundle menor
// - SEO melhor (conteúdo no HTML inicial)

Lidando com Interatividade

// Padrão: Server Component wrapper + Client Component para interatividade

// app/products/[id]/page.tsx (Server Component)
import { AddToCartButton } from "./add-to-cart-button"; // Client

export default async function ProductPage({ params }) {
  const product = await getProduct(params.id);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>${product.price}</p>

      {/* Passa apenas dados necessários para Client Component */}
      <AddToCartButton
        productId={product.id}
        price={product.price}
        inStock={product.stock > 0}
      />
    </div>
  );
}

// add-to-cart-button.tsx (Client Component)
"use client";

import { useState } from "react";

export function AddToCartButton({ productId, price, inStock }) {
  const [quantity, setQuantity] = useState(1);

  if (!inStock) return <p>Out of stock</p>;

  return (
    <div>
      <input
        type="number"
        value={quantity}
        onChange={(e) => setQuantity(Number(e.target.value))}
        min="1"
      />
      <button onClick={() => addToCart(productId, quantity)}>
        Add ${price * quantity} to Cart
      </button>
    </div>
  );
}

Conclusão: O Futuro é Server-First

React Server Components representam uma mudança fundamental na arquitetura React:

Benefícios comprovados:

  • Performance: Bundles 70-90% menores
  • UX: Time to Interactive 60-80% mais rápido
  • DX: Código mais simples (sem useEffect complexo)
  • SEO: Conteúdo no HTML inicial
  • Custos: Menos API calls, melhor cache

Adoção em 2025:

  • Next.js: RSC por padrão desde v13
  • Remix: Implementação em andamento
  • Gatsby: Experimentação com Server Components
  • Create React App: Deprecated, migre para frameworks modernos

Se você quer dominar React moderno, recomendo que dê uma olhada em outro artigo: React JS Trends to Look Out for in 2025 onde você vai descobrir outras tendências revolucionárias do ecossistema.

Bora pra cima! 🦅

🎯 Junte-se aos Desenvolvedores que Estão Evoluindo

Milhares de desenvolvedores já usam nosso material para acelerar seus estudos e conquistar melhores posições no mercado.

Por que investir em conhecimento estruturado?

Aprender de forma organizada e com exemplos práticos faz toda diferença na sua jornada como desenvolvedor.

Comece agora:

  • R$9,90 (pagamento único)

🚀 Acessar Guia Completo

"Material excelente para quem quer se aprofundar!" - João, Desenvolvedor

Comentários (0)

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

Adicionar comentário