404 Not Found

404 Not Found


nginx

Recuperação de Dados: useFetch

Quando Alice abriu a homepage da MegaShop, viu uma página em branco—os dados dos produtos só eram buscados quando o cliente fazia a requisição, e levava 3 segundos para renderizar. Charlie sabia que o useFetch do Nuxt 3 podia pré-carregar dados no lado do servidor, permitindo que o HTML incluísse diretamente as informações dos produtos para que o conteúdo ficasse imediatamente visível na primeira tela.

1. O Que Você Vai Aprender


2. Uma História Real de uma Consumidora

(1) Dor: Uma tela em branco por 3 segundos

Alice abriu a MegaShop no celular e viu uma tela em branco com um ícone de carregamento por 3 segundos antes dos produtos aparecerem. No metrô com conexão fraca, era pior—5 segundos de tela em branco. Charlie analisou e descobriu que os dados dos produtos só são buscados após o JavaScript do lado do cliente terminar de carregar. Todo o processo segue esta sequência: download do JavaScript → execução → fetch → renderização—o que é lento demais.

(2) Solução com Pré-busca SSR do useFetch

O useFetch busca dados durante a fase de SSR, e o HTML inclui diretamente o conteúdo dos produtos:

VUE
<script setup lang="ts">
// Servidor: buscar dados durante o SSR
// Cliente: usa dados pré-buscados, sem requisição duplicada
const { data: products } = await useFetch('/api/products')
</script>

(3) Resultados: Tempo de carregamento da primeira tela reduzido de 3 segundos para 0,8 segundos

Após o pré-carregamento SSR, Alice vê o produto imediatamente ao abrir a página; o LCP caiu de 3 segundos para 0,8 segundos, e a pontuação de experiência do usuário melhorou de 52 para 94.


3. useFetch e useAsyncData

(1) Linha do Tempo da Pré-busca de Dados SSR até a Hidratação no Cliente

100%
sequenceDiagram
    participant B as Navegador
    participant S as Servidor Nuxt
    participant A as Servidor API

    B->>S: GET /products
    S->>A: fetch /api/products
    A-->>S: Dados JSON
    S->>S: Renderizar HTML com dados
    S-->>B: HTML + payload (dados embutidos)
    B->>B: Exibir HTML (primeira pintura instantânea)
    B->>B: Hidratar com payload (sem re-fetch)

(2) Comparação de useFetch e useAsyncData

Dimensão useFetch useAsyncData
Propósito Wrapper para $fetch Recuperação geral de dados
Método de Requisição $fetch integrado Handler Personalizado
URL Responsiva ✅ Re-requisição automática quando a URL muda ❌ Deve ser observado manualmente
Inferência de Tipos ✅ Infere automaticamente o tipo de resposta ⚠️ Tipos genéricos devem ser especificados manualmente
Caso de Uso Maioria das Requisições API Requisições Não-padronizadas/Lógica Complexa

(1) ▶Exemplo: Uso Básico do useFetch

VUE
<!-- pages/products/index.vue -->
<template>
  <div>
    <h1>Produtos</h1>
    <div v-if="pending">Carregando...</div>
    <div v-else-if="error">Falha ao carregar produtos</div>
    <div v-else>
      <ProductCard v-for="p in products" :key="p.id" :product="p" />
    </div>
  </div>
</template>

<script setup lang="ts">
const { data: products, pending, error } = await useFetch('/api/products')
</script>

Saída:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: Handler personalizado para useAsyncData

VUE
<script setup lang="ts">
const { data: stats } = await useAsyncData('product-stats', async () => {
  // Lógica personalizada de busca de dados
  const [total, featured, onSale] = await Promise.all([
    $fetch('/api/products/count'),
    $fetch('/api/products/featured'),
    $fetch('/api/products/on-sale')
  ])
  return { total, featured, onSale }
})
</script>

Saída:

TEXT
// Execução Bem-sucedida

4. $fetch vs. useFetch

(1) Comparação das Diferenças Chave

Dimensão $fetch useFetch
Passagem de Dados SSR ❌ Sem passagem ✅ Passagem automática de payload
Reatividade ❌ Dados brutos ✅ ref Reativo
Gerenciamento de Status ❌ Nenhum ✅ pending/error/refresh
Requisição Duplicada ❌ Possível Duplicação ✅ Dedupe
Casos de Uso Tratamento de Eventos/Rota API Recuperação de Dados da Página

(1) ▶Exemplo: Usando $fetch para tratamento de eventos

VUE
<script setup lang="ts">
// $fetch é para requisições únicas (sem necessidade de payload SSR)
async function submitOrder() {
  const order = await $fetch('/api/orders', {
    method: 'POST',
    body: { items: cart.value, total: totalPrice.value }
  })
  navigateTo(`/orders/${order.id}`)
}
</script>

Saída:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: Usando $fetch em uma API do servidor

TYPESCRIPT
// server/api/products/index.get.ts
export default defineEventHandler(async (event) => {
  // $fetch é a escolha certa dentro de handlers do servidor
  const query = getQuery(event)
  const products = await $fetch('https://api.supplier.com/products', {
    params: { category: query.category }
  })
  return products
})

Saída:

TEXT
// Execução Bem-sucedida

5. Explicação Detalhada das Opções de Requisição

(1) Referência Rápida das Opções Chave

Opção Tipo Padrão Descrição
server boolean true Se deve executar no servidor
lazy boolean false Se deve habilitar lazy loading (navegação não-bloqueante)
immediate boolean true Se deve executar imediatamente
dedupe string 'cancel' Política de Requisição Duplicada
transform function - Transformar dados de resposta
pick array - Extrair apenas os campos especificados
default function - Valor padrão antes dos dados serem carregados

(1) ▶Exemplo: Modo lazy (navegação não-bloqueante)

VUE
<template>
  <div>
    <h1>Recomendações de Produtos</h1>
    <!-- lazy: dados carregam em background, página renderiza imediatamente -->
    <div v-if="pending">Carregando recomendações...</div>
    <div v-else>
      <ProductCard v-for="p in recommendations" :key="p.id" :product="p" />
    </div>
  </div>
</template>

<script setup lang="ts">
const { data: recommendations, pending } = await useFetch('/api/recommendations', {
  lazy: true
})
</script>

Saída:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: transform e pick

VUE
<script setup lang="ts">
// Transform: processar dados antes de armazenar
const { data: products } = await useFetch('/api/products', {
  transform: (data: any[]) => {
    return data.map(p => ({
      ...p,
      formattedPrice: new Intl.NumberFormat('en-US', {
        style: 'currency', currency: 'USD'
      }).format(p.price)
    }))
  }
})

// Pick: extrair apenas campos específicos (reduzir tamanho do payload)
const { data: productNames } = await useFetch('/api/products', {
  pick: ['id', 'name', 'price']
})
</script>

Saída:

TEXT
// Execução Bem-sucedida

(3) ▶Exemplo: server: false (busca apenas no cliente)

VUE
<script setup lang="ts">
// Pular busca SSR - buscar apenas no lado do cliente
const { data: userWishlist } = await useFetch('/api/wishlist', {
  server: false,
  default: () => [] // Valor padrão antes dos dados carregarem
})
</script>

Saída:

TEXT
// Execução Bem-sucedida

6. Refresh e Polling

(1) Comparação de Estratégias de Refresh

Estratégia Abordagem Cenário Frequência
Refresh Manual refresh() Após ação do usuário Sob demanda
Auto-Refresh por Watch opções watch quando parâmetros mudam quando parâmetros mudam
Polling setInterval + refresh Dados em tempo real Agendado
Tempo real WebSocket Ultra-tempo real Push

(1) ▶Exemplo: Refresh Manual

VUE
<template>
  <div>
    <h1>Produtos</h1>
    <button @click="refresh()" :disabled="pending">
      {{ pending ? 'Atualizando...' : 'Atualizar' }}
    </button>
    <ProductCard v-for="p in products" :key="p.id" :product="p" />
  </div>
</template>

<script setup lang="ts">
const { data: products, pending, refresh } = await useFetch('/api/products')
</script>

Saída:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: Atualização Automática Quando os Valores dos Parâmetros Mudam

VUE
<template>
  <div>
    <select v-model="selectedCategory">
      <option value="all">Todos</option>
      <option value="electronics">Eletrônicos</option>
      <option value="clothing">Roupas</option>
    </select>
    <ProductCard v-for="p in products" :key="p.id" :product="p" />
  </div>
</template>

<script setup lang="ts">
const selectedCategory = ref('all')

// Auto-refetch quando selectedCategory muda
const { data: products } = await useFetch('/api/products', {
  query: { category: selectedCategory },
  watch: [selectedCategory]
})
</script>

Saída:

TEXT
// Execução Bem-sucedida

(3) ▶Exemplo: Polling de Estoque em Tempo Real

VUE
<script setup lang="ts">
const { data: stock, refresh } = await useFetch('/api/stock/live', {
  server: false // Polling apenas no cliente
})

// Polling a cada 30 segundos
const pollInterval = setInterval(() => {
  refresh()
}, 30000)

onUnmounted(() => clearInterval(pollInterval))
</script>

Saída:

TEXT
// Execução Bem-sucedida

7. Exemplo Completo: Pré-busca SSR da Lista de Produtos MegaShop

VUE
<!-- pages/products/index.vue -->
<template>
  <div class="product-list-page">
    <h1>Produtos MegaShop</h1>

    <!-- Filtros -->
    <div class="filters">
      <select v-model="filters.category" @change="applyFilters">
        <option value="">Todas as Categorias</option>
        <option value="electronics">Eletrônicos</option>
        <option value="clothing">Roupas</option>
        <option value="home">Casa e Jardim</option>
      </select>
      <select v-model="filters.sort">
        <option value="popular">Mais Populares</option>
        <option value="price-asc">Preço: Menor para Maior</option>
        <option value="price-desc">Preço: Maior para Menor</option>
      </select>
    </div>

    <!-- Estado de carregamento -->
    <div v-if="pending" class="loading">Carregando produtos...</div>

    <!-- Estado de erro -->
    <div v-else-if="error" class="error">
      Falha ao carregar produtos. <button @click="refresh()">Tentar Novamente</button>
    </div>

    <!-- Grid de produtos -->
    <div v-else class="product-grid">
      <ProductCard
        v-for="p in products"
        :key="p.id"
        :product="p"
        @add-to-cart="addToCart"
      />
    </div>

    <!-- Paginação -->
    <div class="pagination">
      <button :disabled="page <= 1" @click="page--">Anterior</button>
      <span>Página {{ page }}</span>
      <button @click="page++">Próxima</button>
    </div>
  </div>
</template>

<script setup lang="ts">
interface Product {
  id: number; name: string; price: number; image: string
}

const page = ref(1)
const filters = reactive({
  category: '',
  sort: 'popular'
})

// Pré-busca SSR com query reativa
const { data: products, pending, error, refresh } = await useFetch<Product[]>('/api/products', {
  query: computed(() => ({
    page: page.value,
    category: filters.category || undefined,
    sort: filters.sort
  })),
  default: () => [],
  transform: (data: Product[]) => data.map(p => ({
    ...p,
    formattedPrice: `$${p.price.toLocaleString()} USD`
  }))
})

const cart = useState<Product[]>('cart', () => [])
function addToCart(product: Product) {
  cart.value.push(product)
}

function applyFilters() {
  page.value = 1
  refresh()
}

// Observar mudança de página para auto-refresh
watch(page, () => refresh())
</script>

❓Perguntas Frequentes

P O useFetch faz o cliente fazer requisições duplicadas?
R Não. O mecanismo de payload do Nuxt 3 serializa os dados buscados durante o SSR no HTML, que o cliente usa diretamente durante a hidratação, sem requisições duplicadas.
P Quando usar $fetch em vez de useFetch?
R Use $fetch em handlers de eventos (como ao clicar em um botão para enviar um formulário) ou ao chamá-lo de dentro de uma API do servidor. Use useFetch para buscar dados iniciais da página.
P Como a key do useFetch é gerada?
R Por padrão, uma key única é gerada automaticamente usando a URL e as opções de requisição. Se precisar controlar isso manualmente (ex.: para compartilhar o cache entre múltiplas requisições), pode passar um parâmetro key.
P Qual é a diferença entre lazy e server: false?
R lazy ainda executa a requisição durante o SSR, mas não bloqueia a navegação (a página renderiza primeiro e depois espera os dados). server: false pula completamente a requisição SSR e busca os dados apenas no lado do cliente.
P Por que os dados de resposta do useFetch são um ref em vez de um valor bruto?
R Porque os dados podem mudar (refresh/watch), e um ref garante reatividade. Ao desestruturar, use toRefs ou acesse diretamente usando .value.
P Qual é a diferença entre as opções "cancel" e "defer" para dedupe?
R "cancel" (o padrão) cancela a requisição anterior não finalizada e inicia uma nova. "defer" reutiliza o resultado da requisição anterior e não inicia uma nova.

📖Resumo


📝Exercícios

  1. Exercício Básico (Dificuldade: ⭐): Use useFetch para obter dados de uma API pública (como jsonplaceholder), exiba a lista de dados e verifique se o SSR está funcionando conferindo o código-fonte para ver se os dados estão incluídos.
  2. Problema Avançado (Dificuldade ⭐⭐): Implemente uma lista de produtos com paginação que recarrega automaticamente os dados usando watch quando o botão "Próxima Página" é clicado.
  3. Desafio (Dificuldade: ⭐⭐⭐): Implemente simultaneamente a pré-busca SSR de produtos core e o lazy loading de produtos recomendados, e compare as diferenças na experiência do usuário entre as duas abordagens.

---|

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%