12k
All articles

Buscando Dados em React Server Components

Busque dados em React Server Components com funções async, entenda o cache do Next.js 15/16 e evite waterfalls com Promise.all.

OpenReplay Team
OpenReplay Team
Buscando Dados em React Server Components

Em um React Server Component, você busca dados tornando o componente uma função async e utilizando await diretamente no corpo da função — sem useEffect, sem estado de carregamento e sem round-trip de API no lado do cliente. Este é o modelo padrão no Next.js App Router, e ele inverte dois hábitos herdados do React client-side: a busca de dados passa a ocorrer durante a renderização, e o maior risco é o cache, que mudou de comportamento no Next.js 15. Este artigo aborda o padrão atual, a realidade do cache no Next.js 16, por que "use server" não tem nada a ver com Server Components, e como evitar waterfalls de requisições.

Principais Conclusões

  • Em um Server Component, você busca dados tornando o componente async e aguardando a requisição com await; como os Server Components são renderizados no servidor, credenciais e lógica de consulta não são incluídas no bundle do cliente, permitindo que você consulte um banco de dados diretamente com um ORM.
  • Por padrão, requisições fetch não são cacheadas no Next.js atual — o inverso do comportamento do Next.js 13/14 que muitos tutoriais ainda descrevem.
  • Para cachear, você opta explicitamente: { cache: 'force-cache' } ou { next: { revalidate } } no modelo anterior, ou a diretiva use cache quando cacheComponents: true estiver configurado.
  • Não existe diretiva para Server Components; a diretiva “use server” é utilizada para Server Functions (Server Actions), um recurso separado.
  • Inicie fetches independentes sem await e resolva-os com Promise.all para evitar um waterfall sequencial.

Como buscar dados em um Server Component?

O padrão central é uma única mudança: transformar o componente em uma função async e aguardar a requisição com await. Os Server Components são o padrão para layouts e páginas no App Router, portanto nenhuma diretiva é necessária para obter esse comportamento — componentes async são uma funcionalidade dos Server Components que permitem o uso de await durante a renderização.

// app/blog/page.tsx
export default async function Page() {
  const res = await fetch('https://api.vercel.app/blog')
  const posts = await res.json()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Como o código é executado apenas no servidor, você pode ignorar a camada de API e consultar sua fonte de dados diretamente. É possível realizar consultas ao banco de dados com segurança usando um ORM ou cliente de banco de dados, embora você ainda deva garantir que as requisições sejam devidamente autenticadas e autorizadas.

// app/blog/page.tsx
import { db, posts } from '@/lib/db'

export default async function Page() {
  const allPosts = await db.select().from(posts)
  return (
    <ul>
      {allPosts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Compare isso com o padrão client-side que você está substituindo: buscar dados em um useEffect, armazenar o resultado em useState e enviar a lógica de fetch (além de possíveis segredos) para o navegador. Essa abordagem causa um round-trip extra entre cliente e servidor após o carregamento da página; aguardar no servidor com await elimina essa necessidade.

fetch não é cacheado por padrão — a mudança no Next.js 15/16

No Next.js atual, fetch não é cacheado por padrão — vale deixar isso claro, pois a maioria dos conteúdos mais antigos afirma o contrário. De acordo com a referência atual do fetch no Next.js, o padrão é auto no cache: o Next.js busca o recurso no servidor remoto a cada requisição. O modelo de cache anterior se comportava da mesma forma — por padrão, requisições fetch não são cacheadas, e você cacheia uma requisição individualmente definindo a opção cache como 'force-cache'. No Next.js 13/14, um fetch sem qualificação era cacheado (force-cache) por padrão, portanto qualquer tutorial que dependa desse comportamento está desatualizado.

Para cachear no modelo anterior, opte por requisição:

// Cache até ser revalidado manualmente
await fetch('https://api.example.com/posts', { cache: 'force-cache' })

// Serve dados cacheados, revalidando no máximo a cada 3600s
await fetch('https://api.example.com/posts', { next: { revalidate: 3600 } })

O Next.js 16 também traz um segundo modelo, os Cache Components, cujo opt-in é a diretiva use cache. O detalhe que confunde muitos desenvolvedores: use cache é uma funcionalidade dos Cache Components; para habilitá-la, adicione a opção cacheComponents ao seu arquivo next.config.ts. Sem essa flag, use cache não tem efeito e você volta a utilizar as opções de fetch descritas acima.

// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = { cacheComponents: true }
export default nextConfig
import { cacheLife } from 'next/cache'

export async function getPosts() {
  'use cache'
  cacheLife('hours')
  const res = await fetch('https://api.example.com/posts')
  return res.json()
}

Aqui, a diretiva use cache cacheia o valor de retorno de funções e componentes assíncronos, e cacheLife() define por quanto tempo. Observe que unstable_cache é substituído pela diretiva use cache no Next.js 16 — evite utilizá-lo em código novo.

Cache Components DESATIVADO (modelo anterior)Cache Components ATIVADO (cacheComponents: true)
Padrão do fetchSem cacheSem cache
Cachear um fetch{ cache: 'force-cache' }Diretiva use cache
Revalidação por tempo{ next: { revalidate: n } }Perfil cacheLife()
Cachear dados sem fetchReact.cache / configuração de rotause cache na função

Dois comportamentos são independentes do cache. Primeiro, requisições fetch usando GET com a mesma URL e opções são automaticamente memoizadas durante um ciclo de renderização no servidor; portanto, se você chamar o mesmo fetch em múltiplos componentes, o Next.js o executa uma única vez e compartilha o resultado. Segundo, para fontes que não utilizam fetch, envolva a chamada com cache() do React; React.cache tem escopo limitado à requisição atual — cada requisição obtém seu próprio escopo de memoização, sem compartilhamento entre requisições.

”use server” não cria um Server Component

Se há algo que confunde os iniciantes em RSC, é essa diretiva. Um equívoco comum é acreditar que Server Components são identificados por "use server", mas não existe diretiva para Server Components; a diretiva “use server” é utilizada para Server Functions. Os Server Components são simplesmente o padrão no App Router — um arquivo sem diretiva já é um Server Component. "use server" marca Server Functions (Server Actions) e, de acordo com a referência do React, as Server Functions são projetadas para mutações que atualizam o estado no servidor; elas não são recomendadas para busca de dados. Use Server Components async simples para leitura de dados; use "use server" para escrita.

Evite waterfalls e transmita dados lentos

Aguardar requisições uma após a outra com await cria um waterfall sequencial. Dentro de qualquer componente, múltiplas requisições async/await ainda podem ser sequenciais se colocadas uma após a outra; inicie múltiplas requisições chamando fetch e aguarde-as com Promise.all. Chamar as funções sem await as inicia imediatamente:

export default async function Page({ params }: { params: Promise<{ username: string }> }) {
  const { username } = await params
  const artistData = getArtist(username)   // inicia agora
  const albumsData = getAlbums(username)   // inicia agora
  const [artist, albums] = await Promise.all([artistData, albumsData])
  return <h1>{artist.name}</h1>
}

Para dados genuinamente lentos, não bloqueie a rota. Se você tiver requisições de dados lentas, toda a rota fica bloqueada até que todos os dados sejam buscados; para melhorar o tempo de carregamento, divida a página em partes e as envie progressivamente ao cliente. Envolva a subárvore lenta em <Suspense> (ou adicione um loading.js) e, para dados de menor prioridade, inicie a promise no servidor e leia-a no cliente com a API use:

// Server Component — observe: commentsPromise NÃO está sendo aguardada
import { Suspense } from 'react'
import Comments from './comments'

export default async function Page({ id }: { id: string }) {
  const commentsPromise = getComments(id)
  return (
    <Suspense fallback={<p>Carregando comentários…</p>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  )
}
// Client Component
'use client'
import { use } from 'react'

export default function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
  const comments = use(commentsPromise)
  return comments.map((c) => <p key={c.id}>{c.text}</p>)
}

Você inicia a promise no servidor e a aguarda no cliente com a API use; como componentes async não são suportados no cliente, você aguarda a promise com use. Quando algo dá errado, a falha é visível apenas onde o usuário está — um fallback do Suspense que nunca resolve para conteúdo, ou um chunk transmitido que chega após a hidratação — e uma gravação de sessão é o que permite reconstruir exatamente o que o navegador renderizou naquele momento.

Mantenha segredos no lado servidor da fronteira

Os dados buscados no servidor são transmitidos para Client Components como props, e essas props devem ser serializáveis — passe dados simples, não funções ou instâncias de classes. Os segredos permanecem no servidor por padrão, pois o código dos Server Components nunca é enviado ao cliente, mas módulos compartilhados podem causar vazamentos. No Next.js, apenas variáveis de ambiente prefixadas com NEXT_PUBLIC_ são incluídas no bundle do cliente; se as variáveis não tiverem esse prefixo, o Next.js as substitui por uma string vazia. Para proteger um módulo que contém segredos, importe o pacote server-only no início do arquivo, de modo que uma importação acidental no cliente falhe em tempo de build. O React Context também não pode existir em um Server Component — envolva-o em um provider com 'use client' e renderize esse provider dentro do seu layout de servidor.

A mudança de mentalidade é pequena, mas completa: mova o fetch para um componente async, aguarde-o com await e trate o cache como algo que você opta por utilizar, e não como algo que acontece automaticamente. Comece portando um fetch de dados com useEffect para um Server Component async, confirme que a requisição é executada no servidor e então decida por rota se os dados devem ser cacheados com { next: { revalidate } } ou com a diretiva use cache.

Perguntas Frequentes

O fetch é cacheado por padrão no Next.js 16?

Não. A partir do Next.js 15 e continuando no 16, o fetch não é cacheado por padrão e acessa a origem a cada requisição. Isso é o inverso do Next.js 13/14, onde um fetch sem qualificação era cacheado com force-cache por padrão. Para cachear no modelo anterior, você opta por requisição definindo cache como force-cache ou com um valor de revalidate; tutoriais mais antigos que afirmam o comportamento de cache por padrão estão desatualizados.

Qual é a diferença entre use cache e next revalidate para cachear um fetch?

Eles pertencem a dois modelos diferentes. A opção next revalidate e force-cache funcionam no modelo de cache anterior e padrão, sem configuração adicional. A diretiva use cache é uma funcionalidade dos Cache Components que só tem efeito quando cacheComponents está definido como true no next.config.ts, e sua duração é controlada com cacheLife. Sem essa flag, use cache não faz nada, portanto um fetch que você pretendia cachear silenciosamente acessa a origem a cada requisição.

Posso usar useEffect para buscar dados em um Server Component?

Não. O useEffect só é executado no navegador, portanto não pode ser executado dentro de um Server Component, que é renderizado apenas no servidor. Em um Server Component, você torna o componente async e aguarda a requisição diretamente na renderização, sem estado de carregamento e sem round-trip no cliente. Se você precisar de busca de dados no lado do cliente, adicione a diretiva use client para torná-lo um Client Component, ou inicie a promise no servidor e leia-a no cliente com a API use do React.

A diretiva use server transforma um componente em um Server Component?

Não. Não existe diretiva para Server Components; eles são simplesmente o padrão no App Router, portanto um arquivo sem diretiva já é um Server Component. A diretiva use server marca Server Functions, também chamadas de Server Actions, que são projetadas para mutações que atualizam o estado no servidor e não são recomendadas para busca de dados. Use um Server Component async simples para leitura de dados e use server para escrita.

O que acontece se um fetch falhar ao usar Promise.all para requisições paralelas?

Promise.all rejeita assim que qualquer requisição individual rejeita, descartando os resultados das demais e falhando toda a renderização. Se uma requisição puder falhar sem comprometer as demais, use Promise.allSettled, que resolve com o status de cada requisição e permite tratar falhas individualmente. Inicie os fetches sem await para executá-los em paralelo e então escolha Promise.all ou Promise.allSettled com base em como você deseja tratar as falhas.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.