Capítulo 7 de 456

Fetching Data

Core Idea

Como buscar dados em Server Components (fetch API, ORM) e Client Components (use API, SWR/React Query), e como fazer streaming de conteúdo que depende de dados lentos via loading.js ou <Suspense>.

Key Concepts

  • fetch em Server Component: componente vira async function e usa await fetch(...); requisições idênticas na mesma árvore React são memoizadas automaticamente por padrão.
  • ORM/database direto: como Server Components rodam no servidor, credenciais e query logic nunca entram no bundle do cliente.
  • Streaming: quebrar a página em chunks e enviá-los progressivamente; duas formas: loading.js (rota inteira) ou <Suspense> (granular).
  • loading.js: envolve automaticamente page.js (e filhos) numa <Suspense> boundary, aninhado dentro de layout.js.
  • <Suspense fallback={...}>: permite mostrar parte da página imediatamente e estriar só o trecho dinâmico dentro da boundary.
  • use API (React): hook para ler uma Promise em Client Component; a Promise é criada (não awaitada) no Server Component e passada como prop.
  • React.cache: memoiza uma função de fetch por request, múltiplas chamadas no mesmo request retornam o mesmo resultado sem refetch; escopo é por request, sem compartilhamento entre requests.
  • Sequential vs. parallel fetching: requests sequenciais dependem um do outro (ex.: Playlists precisa do artistID de getArtist); requests paralelos são iniciados juntos via Promise.all.
  • Bots e crawlers: recebem a página totalmente renderizada (sem streaming progressivo), diferente de browsers normais.

Code Examples

// app/blog/page.tsx
export default async function Page() {
  const data = await fetch('https://api.vercel.app/blog')
  const posts = await data.json()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
  • O que demonstra: fetch básico em Server Component assíncrono.
// app/blog/page.tsx
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'

export default function BlogPage() {
  return (
    <div>
      <header><h1>Welcome to the Blog</h1></header>
      <main>
        <Suspense fallback={<BlogListSkeleton />}>
          <BlogList />
        </Suspense>
      </main>
    </div>
  )
}
  • O que demonstra: streaming granular, header aparece de imediato enquanto a lista estica.
// app/ui/posts.tsx
'use client'
import { use } from 'react'

export default function Posts({ posts }: { posts: Promise<{ id: string; title: string }[]> }) {
  const allPosts = use(posts)
  return (
    <ul>
      {allPosts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
  • O que demonstra: consumir uma Promise passada do servidor via use() em Client Component.
// app/lib/user.ts
import { cache } from 'react'

export const getUser = cache(async () => {
  const res = await fetch('https://api.example.com/user')
  return res.json()
})
  • O que demonstra: memoização de fetch por request com React.cache, evitando refetch entre componentes.
// app/artist/[username]/page.tsx
export default async function Page({ params }) {
  const { username } = await params
  const artistData = getArtist(username)
  const albumsData = getAlbums(username)
  const [artist, albums] = await Promise.all([artistData, albumsData])
  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums} />
    </>
  )
}
  • O que demonstra: iniciar dois fetches em paralelo e aguardá-los juntos.

Reference Tables

MétodoOndeCaracterística
fetch APIServer Componentmemoizado por padrão, não cacheado entre requests sem use cache
ORM/DB clientServer Componentcredenciais nunca vão ao cliente
use APIClient Componentlê Promise vinda do servidor, precisa de <Suspense>
SWR / React QueryClient Componentcache/streaming próprios, fetch direto do browser

Anti-patterns

  • await em cascata sem necessidade: getAlbums esperando getArtist terminar quando os dois são independentes, desperdiça tempo, use Promise.all para iniciar juntos.
  • Promise.all quando uma falha não deve derrubar tudo: usar Promise.allSettled para tolerar falha parcial.
  • await getPosts() antes de passar para Client Component: perde o streaming, passe a Promise sem await e resolva com use() dentro do Client Component.

Key Takeaways

  1. Server Components buscam dados com fetch ou ORM direto; fetch idêntico na mesma árvore é memoizado automaticamente.
  2. Streaming acontece via loading.js (rota inteira, <Suspense> implícito) ou <Suspense> explícito (granular); a primeira request lenta bloqueia tudo que está fora da boundary.
  3. Requests independentes devem ser iniciados juntos com Promise.all, não encadeados com await sequencial.
  4. React.cache deduplica fetches idênticos dentro do mesmo request, mas não compartilha nada entre requests diferentes.
  5. Client Components podem receber uma Promise não resolvida via prop e consumi-la com use(), mantendo o streaming do servidor até o cliente.

Connects To

  • Server and Client Components: base conceitual de onde cada tipo de fetch acontece.
  • Mutating Data: após mutação, revalidatePath/revalidateTag invalidam o que foi buscado aqui.