Alternar tema

Guia completo de otimização de APIs no Next.js: cache, streaming e Edge Functions na prática

Easton editorial illustration: state-management shelf

Numa sexta-feira, às nove da noite, o gerente de produto mandou uma captura de tela no grupo. No vídeo de teste com usuários, a pessoa abria a lista do blog pelo celular, o ícone de Loading girava por 5 segundos inteiros e a página continuava branca. No canto inferior, alguém soltou: “Que tipo de site é esse em pleno século XXI?”

Abri o Chrome DevTools e veio o susto: a requisição da API levava 3200ms. Na hora deu aquele aperto. Eu já sabia que o endpoint era lento, mas vinha empurrando a otimização. Não imaginava que estava tão ruim.

Depois passei dois dias estudando otimização de desempenho no Next.js e percebi que a solução não era tão complicada. Com a estratégia certa de cache, mais resposta em streaming e edge computing, o tempo de resposta caiu direto de 3 segundos para menos de 500ms. Mais importante: eu entendi quando usar cada abordagem. Isso vale muito mais do que só conhecer as técnicas.

Hoje vamos falar dessas três frentes: como escolher cache, como implementar streaming e em quais cenários Edge Functions fazem sentido. O código aqui foi rodado de verdade, e os números de desempenho também vêm de teste real. Dá para pegar e aplicar.

Por que sua API no Next.js está tão lenta?

Vamos começar pelos gargalos mais comuns. Quando investiguei aquele endpoint de 3 segundos, encontrei alguns problemas bem típicos:

Consulta ao banco sem otimização. O código tinha um loop em que cada post buscava as informações do autor separadamente: o clássico problema de N+1 queries. Com 100 posts, eram 100 requisições ao banco. Como isso não ficaria lento? Para piorar, algumas tabelas nem tinham índice.

Zero cache. A cada atualização da página, o servidor consultava o banco, recalculava tudo e formatava a resposta de novo. Algumas configurações mudavam uma vez por mês, mas eram recalculadas várias vezes por segundo.

Tudo voltava de uma vez. O endpoint retornava o conteúdo completo de 100 posts, incluindo o corpo dos artigos. A resposta JSON passava de 2MB, e só a transferência pela rede já consumia 1 segundo. A página de lista nem precisava do corpo completo; título e resumo bastavam.

Localização do servidor. Nosso servidor estava na costa oeste dos Estados Unidos. Para usuários na China, uma ida e volta já começava em 200ms, sem contar o impacto do GFW… melhor nem entrar nesse assunto.

A mudança de cache trazida pelo Next.js 16

Em outubro de 2025, o Next.js 16 trouxe uma mudança importante: saiu do cache implícito para o cache explícito.

Antes, o Next.js tentava cachear muita coisa automaticamente. Parecia conveniente, mas na prática gerava confusão: isso foi cacheado ou não? Por quanto tempo? Como limpo? Muitas vezes os dados já tinham sido atualizados, mas a página continuava mostrando a versão antiga. Depois de meia hora depurando, você descobria que o culpado era o cache.

Agora você precisa dizer explicitamente ao Next.js o que cachear e por quanto tempo. Dá um pouco mais de trabalho, mas pelo menos você sabe o que está acontecendo. O controle fica muito melhor.

Três direções para otimizar desempenho

Depois de entender o problema, os caminhos ficaram claros:

  1. Cache: não repita trabalho que já foi feito.
  2. Resposta em streaming: calcule e envie aos poucos; não espere tudo ficar pronto para responder.
  3. Edge computing: leve o servidor para mais perto do usuário.

Vamos por partes.

Estratégia de cache: escolher certo já resolve metade do problema

O Next.js tem quatro mecanismos de cache: Request Memoization, Data Cache, Full Route Cache e Router Cache. Na primeira vez que li a documentação, também fiquei perdido. Como memorizar tudo isso?

Na prática, você não precisa decorar todos. Para API Routes, o mais comum é usar Data Cache, guardando o resultado de uma consulta ao banco ou a resposta de uma API externa.

Cenário 1: cache de dados estáticos

Configurações do site e listas de categorias quase não mudam. Dá para cachear por algumas horas sem medo.

// app/api/categories/route.js
export async function GET() {
  const data = await fetch('https://api.example.com/categories', {
    next: { revalidate: 3600 } // cache por 1 hora
  })

  return Response.json(await data.json())
}

É só isso. revalidate: 3600 significa cache por 1 hora, com atualização automática depois desse período.

500ms → 50ms
Tempo de resposta 90% menor
Depois de adicionar cache à lista de categorias, a maioria das requisições voltou direto do cache, sem passar pelo banco

Cenário 2: cache de dados relacionados ao usuário

Dados como perfil do usuário não mudam o tempo todo, mas também não podem ficar antigos para sempre. Nesses casos, stale-while-revalidate funciona bem:

// app/api/user/profile/route.js
export async function GET(request) {
  const user = await getUserFromDB()

  return new Response(JSON.stringify(user), {
    headers: {
      'Content-Type': 'application/json',
      'Cache-Control': 's-maxage=60, stale-while-revalidate=300'
    }
  })
}

A ideia é esperta: primeiro devolve os dados em cache, mesmo que possam estar um pouco antigos, e atualiza o cache de forma assíncrona em segundo plano. O usuário não sente a latência, e os dados não ficam velhos demais.

s-maxage=60 indica que o cache fica fresco por 60 segundos. stale-while-revalidate=300 indica que, depois de expirar, os dados antigos ainda podem ser usados por 300 segundos enquanto o cache é atualizado em segundo plano.

Cenário 3: dados em tempo real não devem usar cache

Preço de ações e mensagens de chat exigem atualização imediata. Nesses casos, não use cache. Ou você não cacheia nada, ou usa WebSocket ou Server-Sent Events para enviar atualizações.

export async function GET() {
  const price = await getStockPrice()

  return new Response(JSON.stringify(price), {
    headers: {
      'Cache-Control': 'no-store' // sem cache
    }
  })
}

Invalidação de cache: o que fazer depois que os dados mudam?

O usuário atualizou o perfil, mas o cache ainda mostra a versão antiga? Aí você precisa limpar o cache manualmente.

O Next.js oferece duas APIs para isso: revalidateTag e revalidatePath.

// app/api/user/update/route.js
import { revalidateTag } from 'next/cache'

export async function POST(request) {
  const data = await request.json()
  await updateUserProfile(data)

  // Limpa o cache relacionado ao usuário
  revalidateTag('user-profile')

  return Response.json({ success: true })
}

No endpoint de consulta, marque o cache com uma tag:

export async function GET() {
  const data = await fetch('db-api/user', {
    next: {
      revalidate: 3600,
      tags: ['user-profile'] // tag
    }
  })

  return Response.json(await data.json())
}

Assim, depois que o perfil é atualizado, o cache relacionado expira imediatamente. Na próxima requisição, o usuário recebe os dados novos.

Pegadinhas comuns

Pegadinha 1: cache em excesso. Já vi gente cachear status de pedido por 1 hora. Resultado: depois do pagamento, o usuário passava um bom tempo sem ver o status atualizado. O tempo de cache precisa acompanhar a natureza dos dados. Não é quanto mais longo, melhor.

Pegadinha 2: esquecer o aquecimento do cache. A primeira requisição ainda pode ser lenta, porque o cache está vazio. Você pode chamar os endpoints principais logo após o deploy e carregar os dados mais acessados antes dos usuários chegarem.

Pegadinha 3: cache key mal desenhada. Se os dados do usuário A entram no cache e o usuário B recebe esses mesmos dados, o problema é sério. Garanta que a cache key inclua identificadores como o ID do usuário.

Resposta em streaming: listas grandes sem travar a experiência

Cache resolve trabalho repetido, mas alguns dados continuam demorados de calcular ou grandes demais para enviar de uma vez. É aí que resposta em streaming entra.

O que é resposta em streaming?

Uma resposta tradicional de API é como ir a um restaurante e esperar que o cozinheiro prepare todos os pratos antes de servir a mesa. Se você pediu 10 pratos, precisa esperar o mais demorado ficar pronto.

Streaming é diferente: ficou pronto, serviu. Você começa a comer enquanto os próximos pratos ainda estão vindo. O tempo total pode até ser parecido, mas a experiência muda completamente, porque você não fica parado esperando.

Para o usuário, isso troca “olhar para uma tela branca por 3 segundos” por “ver os primeiros itens em 500ms e já começar a navegar”. A sensação é outra.

Quando usar resposta em streaming?

Alguns cenários aparecem o tempo todo:

  1. Listas longas: produtos, posts, resultados de busca.
  2. Conteúdo gerado por IA: aquele efeito de digitação do ChatGPT é, na prática, streaming.
  3. Processamento de arquivos grandes: exportar Excel, gerar PDF.
  4. Logs em tempo real: logs de build, progresso de tarefas.

Em geral, se há muito dado ou cálculo demorado, vale considerar streaming.

Como implementar no Next.js?

A forma mais comum é usar ReadableStream:

// app/api/posts/stream/route.js
export async function GET() {
  const encoder = new TextEncoder()

  const stream = new ReadableStream({
    async start(controller) {
      // Busca os dados em lotes
      for (let page = 0; page < 5; page++) {
        // Consulta 20 itens por vez
        const posts = await fetchPostsFromDB({ page, limit: 20 })

        // Envia este lote de dados
        const chunk = JSON.stringify(posts) + '\n'
        controller.enqueue(encoder.encode(chunk))

        // Simula tempo de processamento
        await new Promise(r => setTimeout(r, 100))
      }

      // Finaliza o envio dos dados
      controller.close()
    }
  })

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/plain; charset=utf-8',
      'Transfer-Encoding': 'chunked'
    }
  })
}

O código não é complicado. O núcleo é:

  1. Criar um ReadableStream.
  2. Buscar os dados em lotes dentro do método start.
  3. Enviar cada lote com controller.enqueue().
  4. Chamar controller.close() quando tudo terminar.

Como receber no frontend?

Se o backend envia dados em streaming, o frontend também precisa tratar a resposta:

async function fetchStreamData() {
  const response = await fetch('/api/posts/stream')
  const reader = response.body.getReader()
  const decoder = new TextDecoder()

  let allPosts = []

  while (true) {
    const { done, value } = await reader.read()

    if (done) {
      console.log('Dados recebidos com sucesso')
      break
    }

    // Decodifica os dados
    const chunk = decoder.decode(value)

    // Faz o parse do JSON, um item por linha
    const posts = JSON.parse(chunk)
    allPosts = [...allPosts, ...posts]

    // Atualiza a UI em tempo real
    updatePostList(allPosts)
  }
}

Quando o usuário abre a página, a lista aparece aos poucos em vez de ficar em branco por vários segundos.

Comparação do efeito real

Depois que adicionei streaming à lista do blog:

2800ms → 500ms
Tempo até o primeiro conteúdo visível
Antes, a página esperava 2800ms para mostrar tudo de uma vez; depois, mostrou os primeiros 20 posts em 500ms e o usuário já pôde navegar

O tempo total só caiu 1300ms, mas a velocidade percebida mais que dobrou. Com 500ms, a pessoa já interage; o restante do tempo é gasto navegando, não esperando.

Um truque pequeno

Se o volume de dados for muito grande, combine streaming com Virtual Scrolling. O frontend renderiza apenas a área visível e deixa o restante fora da tela. Assim, mesmo recebendo 1000 itens, a página não trava.

Em React, dá para usar react-window ou react-virtualized. Em Vue, vue-virtual-scroller resolve bem.

Edge Functions: leve a API para perto do usuário

Cache e streaming atuam na camada de software, mas existe uma solução ainda mais direta: levar o servidor para mais perto do usuário.

O impacto da distância física

A latência de rede vem principalmente da distância física. A velocidade da luz é limitada. Um pacote saindo de Pequim até a costa oeste dos Estados Unidos leva pelo menos 200ms para ir e voltar. É física, não dá para otimizar.

Antes, a gente só conseguia colocar o servidor em um ponto fixo, como Alibaba Cloud em Pequim. Usuários de Pequim acessavam rápido; usuários dos Estados Unidos sofriam.

A ideia das Edge Functions é simples: implantar o código em dezenas ou até centenas de nós pelo mundo. Quando o usuário acessa, a requisição é roteada automaticamente para o nó mais próximo. Usuários em Pequim acessam um nó em Pequim; usuários em Nova York acessam um nó em Nova York. A latência pode cair para menos de 50ms.

Diferença entre Edge Runtime e Node.js Runtime

As API Routes do Next.js rodam por padrão no Node.js Runtime. Ali você pode usar todas as APIs do Node.js, como fs, crypto e conexões com banco de dados.

O Edge Runtime é diferente. Ele se baseia no motor V8, o mesmo do Chrome, e não é um ambiente Node.js completo. A vantagem é a inicialização muito rápida, de 0 a 5ms. A desvantagem é que muitas APIs do Node.js não estão disponíveis.

Uma comparação simples:

CaracterísticaNode.js RuntimeEdge Runtime
Velocidade de inicialização100-500ms0-5ms
APIs disponíveisTodas as APIs do Node.jsLimitado a APIs padrão da Web
Cenários indicadosLógica de negócio complexa, operações no bancoLógica leve, autenticação, proxy
Latência globalDepende do local do deployGlobal <50ms
Limite de memóriaMais altoMais baixo (128MB)

Quais cenários combinam com Edge Functions?

Nem toda API deve migrar para Edge. Estes são os casos em que ela costuma fazer mais sentido:

Cenário 1: autenticação

O melhor caso para Edge é autenticação. Checar JWT token e validar API key são operações leves. Faça isso na borda e as requisições inválidas nem chegam ao servidor central.

// app/api/auth/route.js
export const runtime = 'edge'

export async function GET(request) {
  const token = request.headers.get('authorization')

  if (!token) {
    return new Response('Não autorizado', { status: 401 })
  }

  // Valida o token, usando uma biblioteca como jose, compatível com Edge
  const isValid = await verifyToken(token)

  if (!isValid) {
    return new Response('Token inválido', { status: 401 })
  }

  return Response.json({ user: 'autenticado' })
}
200ms → 20ms
Latência de autenticação 90% menor
Validar o token rapidamente na borda impede que requisições inválidas cheguem ao servidor central e reduz a carga

Cenário 2: personalização por geolocalização

Retorne conteúdo diferente com base no IP do usuário, como idioma, moeda ou recomendações.

export const runtime = 'edge'

export async function GET(request) {
  // Obtém a localização do usuário, injetada automaticamente pela Vercel
  const country = request.geo?.country || 'US'
  const city = request.geo?.city || 'Unknown'

  // Retorna conteúdo diferente conforme a localização
  const content = getLocalizedContent(country)

  return Response.json({
    country,
    city,
    content,
    currency: country === 'CN' ? 'CNY' : 'USD'
  })
}

Não precisa consultar o banco. A borda processa tudo diretamente, e a resposta sai muito rápido.

Cenário 3: proxy de API

Às vezes o frontend precisa chamar várias APIs externas. Dá para agregar essas chamadas na camada Edge e reduzir o número de requisições do cliente.

export const runtime = 'edge'

export async function GET(request) {
  // Faz várias requisições de API em paralelo
  const [weather, news] = await Promise.all([
    fetch('https://api.weather.com/...'),
    fetch('https://api.news.com/...')
  ])

  return Response.json({
    weather: await weather.json(),
    news: await news.json()
  })
}

O usuário faz uma única requisição. O backend resolve tudo em paralelo e a latência total cai bastante.

Cenário 4: teste A/B

Na camada Edge, você decide qual versão do conteúdo devolver sem mexer na aplicação principal.

export const runtime = 'edge'

export async function GET(request) {
  const userId = request.headers.get('x-user-id')

  // Lógica simples de divisão A/B
  const variant = parseInt(userId) % 2 === 0 ? 'A' : 'B'

  const content = variant === 'A' ? getContentA() : getContentB()

  return Response.json({ variant, content })
}

Limitações das Edge Functions

Se Edge é tão bom, por que não migrar tudo? Porque há várias limitações:

Limitação 1: não dá para usar APIs exclusivas do Node.js

fs, path e child_process não funcionam. Se o código depende disso, migrar para Edge vai quebrar.

Limitação 2: conexão com banco de dados

Conectores tradicionais de banco, como pg e mysql2, não funcionam porque dependem do módulo net do Node.js. Use soluções HTTP-based, como:

  • Prisma Data Proxy
  • PlanetScale (MySQL)
  • Supabase (PostgreSQL)
  • Redis com suporte a HTTP API

Limitação 3: memória e tempo de execução

Edge Functions normalmente têm limite de memória, como 128MB, e limite de execução, como 30 segundos. Processamento pesado ou grandes volumes de dados não combinam com esse ambiente.

Minha sugestão: use em conjunto

Não precisa escolher um lado só. Minha abordagem é:

  • Camada de borda (Edge): autenticação, decisão por geolocalização e proxy simples.
  • Camada central (Node.js): lógica de negócio complexa, operações no banco e processamento de arquivos.

A camada Edge segura requisições inválidas e casos simples. O que for complexo segue para o servidor central. Assim você reduz latência sem ficar preso às limitações da borda.

Teste real de desempenho

Segundo um benchmark publicado no Medium:

  • Vercel Edge Functions: latência média de 48,3ms
  • Cloudflare Workers (customizado): latência média de 36,37ms
  • API tradicional em Node.js (região única): latência média de 200-500ms

Edge realmente é rápido, mas o resultado depende da distribuição dos seus usuários. Se todos estão no mesmo país, um servidor tradicional bem localizado pode ser mais rápido.

Caso prático combinado: otimização da API de lista de posts do blog

Até aqui falamos das três técnicas separadamente. Agora vamos juntar tudo em um caso real. Vamos usar a API de lista do blog do começo, aquela lenta o suficiente para gerar reclamação no teste de usuário.

Problemas antes da otimização

Veja o código original:

// app/api/posts/route.js
export async function GET() {
  // Problema 1: consulta o banco a cada requisição, sem cache
  const posts = await db.post.findMany({
    take: 100,
    include: {
      author: true, // Problema 2: query N+1
      tags: true
    }
  })

  // Problema 3: retorna o conteúdo completo dos posts, com muito dado
  return Response.json(posts)
}

Dados de desempenho:

  • Tempo de resposta: 2800ms
  • Tamanho do JSON: 2,3MB
  • Experiência do usuário: tela branca por 3 segundos

Passo 1: otimizar a consulta ao banco

Primeiro, resolva o problema de N+1 queries e retorne apenas os campos necessários:

export async function GET() {
  const posts = await db.post.findMany({
    take: 100,
    select: {
      id: true,
      title: true,
      summary: true,  // só o resumo, não o texto completo
      createdAt: true,
      author: {
        select: { name: true, avatar: true }
      }
    }
  })

  return Response.json(posts)
}

Resultado: o tempo de resposta caiu para 800ms, e o JSON saiu de 2,3MB para 180KB.

Passo 2: adicionar cache

A lista de posts não muda a todo instante, então pode ficar em cache por 5 minutos:

export async function GET() {
  const posts = await db.post.findMany({
    // ... igual ao exemplo acima
  }, {
    next: {
      revalidate: 300,  // cache por 5 minutos
      tags: ['posts']
    }
  })

  return Response.json(posts)
}

Combine isso com limpeza de cache quando um post for publicado:

// app/api/posts/publish/route.js
import { revalidateTag } from 'next/cache'

export async function POST(request) {
  const newPost = await request.json()
  await db.post.create({ data: newPost })

  // Limpa o cache da lista de posts
  revalidateTag('posts')

  return Response.json({ success: true })
}

Resultado: com cache hit, a resposta caiu para 50ms, e a carga do servidor baixou 90%.

Passo 3: trocar para resposta em streaming

Mesmo com tudo mais rápido, o primeiro acesso, quando o cache ainda não foi preenchido, ainda espera 800ms. Vamos usar streaming:

export async function GET() {
  const encoder = new TextEncoder()

  const stream = new ReadableStream({
    async start(controller) {
      const batchSize = 20

      for (let page = 0; page < 5; page++) {
        const posts = await db.post.findMany({
          skip: page * batchSize,
          take: batchSize,
          select: { /* igual ao exemplo acima */ }
        })

        const chunk = JSON.stringify(posts) + '\n'
        controller.enqueue(encoder.encode(chunk))
      }

      controller.close()
    }
  })

  return new Response(stream, {
    headers: {
      'Content-Type': 'application/x-ndjson', // Newline Delimited JSON
      'Cache-Control': 's-maxage=300, stale-while-revalidate=600'
    }
  })
}

Resultado: o primeiro lote volta em 300ms. O usuário já consegue navegar, e o tempo total de 800ms praticamente desaparece da percepção.

Passo 4: autenticação na camada Edge (opcional)

Se a API precisa de autenticação, você pode fazer uma validação inicial na camada Edge:

// app/api/posts/route.js (camada de autenticação Edge)
export const runtime = 'edge'

export async function GET(request) {
  const token = request.headers.get('authorization')

  if (!token) {
    return new Response('Não autorizado', { status: 401 })
  }

  // Depois da validação, encaminha para a API real, em Node.js Runtime
  return fetch(`${process.env.API_BASE_URL}/posts/internal`, {
    headers: { authorization: token }
  })
}

Requisições inválidas são bloqueadas na borda e não chegam ao servidor central.

Comparação do resultado

MétricaAntesDepoisGanho
Tempo de resposta no primeiro acesso2800ms300ms (primeiro lote)89% ↓
Tempo de resposta com cache hit-50ms98% ↓
Tamanho do JSON2,3MB180KB92% ↓
Tempo até interação2800ms300ms89% ↓
Carga do servidor100%10%90% ↓

O usuário nunca mais reclamou que parecia “um site de outra década”.

Monitoramento de desempenho e otimização contínua

Otimizar não é o fim. Você precisa monitorar continuamente para saber se o ganho continua real.

Métricas principais

Eu acompanho estes indicadores:

  1. Distribuição do tempo de resposta (P50, P95, P99)

    • P50, ou mediana: a experiência de metade dos usuários.
    • P95: a experiência de 95% dos usuários.
    • P99: a experiência do 1% mais lento, que costuma revelar casos anormais.
  2. Taxa de acerto do cache

    • Cache hit abaixo de 70% indica problema na estratégia de cache.
    • Cache hit acima de 95% pode indicar tempo de cache longo demais e dados pouco frescos.
  3. Taxa de erro

    • Depois da otimização, a taxa de erro não pode subir.
    • Streaming pode falhar no meio do caminho, então exige atenção extra.
  4. Distribuição geográfica

    • Diferença de latência entre regiões.
    • Ajuda a decidir se Edge Functions são necessárias.

Ferramentas de monitoramento

Vercel Analytics: se você faz deploy na Vercel, o monitoramento de desempenho já vem integrado. Dá para ver a distribuição de tempo de resposta de cada API.

Next.js Instrumentation API (recurso novo em 2026): permite inserir pontos de monitoramento no código.

// instrumentation.js
export function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    require('./monitoring')
  }
}

// monitoring.js
export function onRequestEnd(info) {
  console.log(`API ${info.url} levou ${info.duration}ms`)

  // Envia para a plataforma de monitoramento
  sendToMonitoring({
    url: info.url,
    duration: info.duration,
    status: info.status
  })
}

Logs customizados: simples, direto e ainda útil.

export async function GET() {
  const start = Date.now()

  const data = await fetchData()

  const duration = Date.now() - start
  console.log(`API /posts levou ${duration}ms`)

  return Response.json(data)
}

Sugestões para otimização contínua

  1. Revise a estratégia de cache periodicamente: se o negócio muda, o cache também precisa mudar.
  2. Faça testes A/B: não sabe qual abordagem é melhor? Teste.
  3. Ajuste com dados reais: não decida no palpite; olhe as métricas antes.

Otimização de desempenho é um processo contínuo, não algo que você resolve uma vez e esquece.

Resumo

Falamos bastante. O essencial é isto:

Estratégia de cache: escolha conforme a natureza dos dados. Dados estáticos aceitam cache longo. Dados de usuário combinam com cache curto e stale-while-revalidate. Dados em tempo real não devem ser cacheados. E não esqueça de limpar o cache quando os dados mudarem.

Resposta em streaming: é uma ótima saída quando há muito dado ou cálculo lento. Faça o usuário ver conteúdo mais cedo, em vez de encarar uma tela branca. Com Virtual Scrolling no frontend, fica ainda melhor.

Edge Functions: fazem sentido para autenticação, decisão por geolocalização e proxy de API. Não espere que resolvam lógica de negócio complexa. O caminho mais saudável é combinar Edge com Node.js Runtime.

Otimização não acontece de uma vez só. Comece pelo endpoint mais lento, aplique essas três ideias, meça o resultado e ajuste. Vá passo a passo, sem tentar buscar perfeição de primeira.

A API de lista do meu blog saiu de 3 segundos para 300ms, e a melhora na experiência foi bem visível. Você também pode escolher um endpoint lento e começar hoje. Se tiver dúvida, deixe um comentário; vamos melhorar isso juntos.

FAQ

Quando o cache de uma API no Next.js expira?
Há três formas principais: 1) expiração por tempo, quando o período definido em revalidate termina; 2) expiração manual, chamando revalidateTag ou revalidatePath; 3) atualização forçada pelo usuário, como Ctrl+Shift+R. As duas primeiras são as mais comuns. Defina o tempo de revalidate conforme a frequência de atualização dos dados.
Resposta em streaming serve para qualquer endpoint?
Não. Streaming faz sentido quando há muito dado, como listas longas, ou processamento demorado, como geração com IA. Se o volume é pequeno e o cálculo é rápido, uma resposta tradicional basta e evita complexidade extra. Como regra prática, considere streaming quando a resposta passa de 1 segundo ou o JSON passa de 500KB.
Quais são as limitações das Edge Functions?
As principais são três: 1) não dá para usar APIs exclusivas do Node.js, como fs e child_process; 2) conexões com banco de dados precisam usar soluções HTTP-based, como Prisma Data Proxy; 3) há limites típicos de 128MB de memória e 30 segundos de execução. Elas são boas para autenticação e proxy leve; lógica complexa continua melhor no Node.js Runtime.
Como escolher uma estratégia de cache?
Olhe para a frequência de atualização dos dados: dados estáticos, como configuração e categorias, podem usar cache longo (1 hora+); dados de usuário, como perfil, combinam com stale-while-revalidate (60 segundos fresco + 300 segundos de atualização em segundo plano); dados em tempo real, como preço de ações, não devem usar cache ou devem usar WebSocket. Lembre: quanto maior o cache, melhor o desempenho, mas maior o risco de dado desatualizado.
Como validar o resultado depois da otimização?
Acompanhe quatro métricas: 1) tempo de resposta (P50, P95 e P99); 2) taxa de acerto do cache, com meta entre 70% e 95%; 3) taxa de erro, que não pode subir por causa da otimização; 4) latência por região. Você pode usar Vercel Analytics, Next.js Instrumentation API ou logs customizados. Faça também um teste A/B para comparar antes e depois.

18 min de leitura · Publicado em: 5 jan 2026 · Atualizado em: 14 jul 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog