Agrupamento de requisições no App Router com Server Actions e Edge Cache
Por John Pires · 14 de março de 2026

Agrupamento de requisições no App Router com Server Actions e Edge Cache
Cenario pratico do dia a dia
Problema comum em apps modernos
Em páginas do App Router é comum que um componente precise de múltiplos pedaços de dados vindos de APIs diferentes: detalhes do usuário, permissões, preferências e dados de terceiros. Se cada pedaço faz uma chamada separada ao backend ou a um serviço externo, você acaba com dezenas de requisições paralelas por renderização. Isso aumenta latência, custo de rede e risco de throttling.
Além disso, chamadas pequenas e frequentes tornam difícil aplicar caching eficiente. APIs de terceiros podem não suportar cache granular, e chamadas repetidas deterioram a experiência do usuário em conexões lentas.
O que queremos resolver
Objetivos práticos: reduzir número de requisições, agrupar chamadas quando possível, manter SSR/ISR previsível e aproveitar Edge Cache para respostas que podem ser servidas rápido. Tudo isso sem comprometer consistência nos dados sensíveis (por exemplo, páginas personalizadas).
Vamos focar em um padrão: usar Server Actions para coordenar agrupamento e orquestração, e Edge Cache + headers de revalidação para respostas públicas e semi-dinâmicas.
Arquitetura da solucao
Componentes principais
A solução proposta tem três camadas:
Server Action: função server-side dentro do App Router que recebe pedidos do cliente, agrupa IDs ou parâmetros e faz uma única chamada ao backend (ou batch endpoints).
Batching Layer: utilitário que consolida requisições, elimina duplicatas e aplica backoff/retries controlados.
Edge Cache / ISR: camada de cache na borda que mantém respostas agregadas por um tempo configurável (revalidate), com cache-control adequado.
Quem orquestra isso é a Server Action. O front envia uma lista de IDs ou uma intenção (por ex: "carregar dados para dashboard"), a server action decide se pode servir do cache de borda, se precisa agrupar e chamar um batch endpoint, ou se deve delegar a um worker assíncrono.
Trade-offs e delimitações
Não use esse padrão para dados estritamente user-specific que não podem ser cacheados. Para páginas com autenticação, combine com cache por sessão ou vary-by-cookie. A vantagem real aparece em dados parcialmente estáticos — catálogos, perfis públicos, metadados.
Edge Cache reduz latência, mas aumenta complexidade de invalidação. Decida políticas de revalidação (revalidate: 60, stale-while-revalidate) com cuidado e documente-as para o time de backend.
Codigo completo com explicacoes
Contexto: o que vamos implementar
Implementaremos um exemplo com três arquivos principais:
app/actions/batchActions.ts — Server Action que agrupa requisições.
app/utils/edgeCache.ts — helper para aplicar cache-control e revalidate.
app/dashboard/page.tsx — componente que usa a Server Action via formulário ou fetch do servidor.
Cada bloco de código vem com explicação do que faz e o que acontece ao executar.
Server Action para agrupamento
Este arquivo é uma Server Action que aceita um array de ids e retorna dados em batch. Ele demonstra uso de fetch com opções next: { revalidate }, tratamento de erros e deduplicacao.
// app/actions/batchActions.ts
'use server';
import { headers } from 'next/headers';
export type Item = { id: string; name: string; updatedAt: string };
// Tempo de revalidação para ISR (em segundos)
export const REVALIDATE_SECONDS = 60;
// Função que faz a chamada batch ao backend. Use um endpoint que suporte POST com lista de ids.
export async function fetchItemsBatch(ids: string[]): Promise> {
if (!ids || ids.length === 0) return {};
const response = await fetch(process.env.API_URL + '/items/batch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids }),
// Instrui Next.js a revalidar essa resposta após X segundos (ISR)
next: { revalidate: REVALIDATE_SECONDS },
});
if (!response.ok) {
const text = await response.text().catch(() => 'no body');
throw new Error(`batch fetch failed: ${response.status} ${text}`);
}
const payload = await response.json();
// payload deve ser um objeto: { items: [{id,name,updatedAt}, ...] }
return Object.fromEntries(payload.items.map((it: Item) => [it.id, it]));
}
// Server Action que o App Router pode chamar via form/cliente server-side
export async function getItemsByIds(ids: string[]) {
// Dedup
const uniq = Array.from(new Set(ids));
try {
const map = await fetchItemsBatch(uniq);
return uniq.map(id => map[id] ?? null);
} catch (err) {
// Re-lançar para que o componente superior trate
throw err;
}
}
O que faz: consolida ids, chama /items/batch e configura ISR via next.revalidate. O que acontece: Next armazenará essa resposta no cache de borda se disponível, e revalidará conforme configurado.
Edge Cache helper
Utilitário para forçar cabeçalhos de cache quando usamos rotas em app/api/.. ou rotas que retornam Response manualmente.
// app/utils/edgeCache.ts
export function cacheResponse(data: T, seconds = 60) {
const headers = new Headers();
// Cache público por X segundos, com stale-while-revalidate para melhorar p95
headers.set('Cache-Control', `public, max-age=${seconds}, s-maxage=${seconds}, stale-while-revalidate=30`);
return new Response(JSON.stringify(data), {
status: 200,
headers: headers,
});
}
O que faz: monta uma Response com cabeçalhos de cache. O que acontece: em plataformas que respeitam Cache-Control na borda (Vercel, Cloudflare), a resposta será armazenada e servida sem tocar o backend na maior parte dos requests.
Componente do App Router usando a Server Action
Exemplo de página que solicita dados agrupados. A interação pode ser via formulário que dispara uma Server Action ou via fetch server-side em layout.
// app/dashboard/page.tsx
import { getItemsByIds } from '../actions/batchActions';
import { useState } from 'react';
export default async function DashboardPage() {
// Exemplo: ids que precisamos carregar no servidor para SSR
const ids = ['a', 'b', 'c'];
// Chamada server-side: roda durante render no servidor
const items = await getItemsByIds(ids);
return (
Dashboard
{items.map((it, i) => (
{it ? `${it.name} (${it.updatedAt})` : 'not found'}
))}
);
}
O que faz: durante render server-side a page chama a Server Action e recebe os dados agregados. O que acontece: se a chamada ao backend tiver cache configurado, a resposta será rápida e beneficiada pela borda; caso contrário a render espera pela resposta do batch.
Tratamento de erros e edge cases
Erros comuns e padrões de tratamento
Erros que veremos:
Backends retornando parcial success (alguns ids não encontrados).
Throttling ou 429 quando o batch é grande demais.
Inconsistência entre cache e dados ao atualizar registros.
Boas práticas:
Retornar estrutura por id (map) para fácil montagem de resultados parciais.
Implementar retries exponenciais para falhas transitórias, com jitter.
Lidar com timeouts e fallback UI no cliente.
Exemplo: tratamento parcial e fallback
Implementação rápida de retry com limite e fallback para dados não encontrados.
// app/actions/retryHelpers.ts
export async function withRetry(fn: () => Promise, retries = 2, delayMs = 200): Promise {
let attempt = 0;
while (true) {
try {
return await fn();
} catch (err) {
if (attempt >= retries) throw err;
attempt++;
await new Promise(res => setTimeout(res, delayMs * Math.pow(2, attempt)));
}
}
}
O que faz: tenta executar fn e, em caso de erro, faz retries exponenciais. O que acontece: reduz a chance de uma falha temporária derrubar a render inteira.
Armadilhas com cache compartilhado
Uma armadilha frequente é servir conteúdo personalizado a partir de cache público. Use headers Vary e chaves lógicas para garantir que não exponha dados de um usuário a outro. Se a página for autenticada, prefira cache por-token ou cache local e não público.
Outra armadilha: revalidate baixo demais + alta latência de origem = picos de carga no backend. Ajuste revalidate considerando carga e tolerância de staleness.
Tip: se seu backend suporta endpoints batch, prefira-os para reduzir chamadas. Se não, implemente um worker que agregue requisições por janela de 50-200ms e faça um único request.
Performance e otimizacao
Medidas práticas para reduzir latência
Liste de ações imediatas:
Substituir N chamadas por um único /batch quando possível.
Usar next: { revalidate } em fetch para tirar proveito do ISR.
Ativar Edge Cache por meio de Cache-Control em rotas API e respostas estáticas.
Implementar deduplicação de ids antes do batch (Array.from(new Set(ids))).
Também considere: compactar payloads e solicitar apenas campos necessários (sparse fieldsets).
Medição e observabilidade
Para validar ganhos, meça:
Tempo ao primeiro byte (TTFB) antes e depois de aplicar Edge Cache.
Quantidade de chamadas ao backend por render de página.
Taxa de 5xx/429 após deploy.
Instrumente métricas no batch layer (latência média do batch, tamanho médio de batch) e crie alarmes para crescimento inesperado.
Exemplo prático: worker de agregação por janela (pseudocódigo)
Em casos onde o backend não suporta batch, um padrão é agrupar requisições por uma janela curta (50-200ms). O código a seguir é um esboço server-side que você pode adaptar como singleton em uma rota Edge.
// app/actions/windowBatcher.ts (esboço)
const queue: { id: string; resolve: (v:any) => void; reject: (e:any) => void }[] = [];
let timer: NodeJS.Timeout | null = null;
function scheduleFlush() {
if (timer) return;
timer = setTimeout(async () => {
timer = null;
const batch = queue.splice(0, queue.length);
const ids = batch.map(x => x.id);
try {
const res = await fetch(process.env.API_URL + '/items/batch', { method: 'POST', body: JSON.stringify({ ids }) });
const data = await res.json();
const map = Object.fromEntries(data.items.map((it:any) => [it.id, it]));
for (const item of batch) {
item.resolve(map[item.id] ?? null);
}
} catch (err) {
for (const item of batch) item.reject(err);
}
}, 100); // agrupa 100ms
}
export function enqueue(id: string) {
return new Promise((resolve, reject) => {
queue.push({ id, resolve, reject });
scheduleFlush();
});
}
O que faz: agrupa solicitações que chegam num curto intervalo e faz um único POST. O que acontece: diminui número de chamadas quando muitos componentes pedem dados quase simultaneamente.
Conclusao
O padrão de agrupar requisições no App Router usando Server Actions e Edge Cache oferece redução significativa de latência e custo quando aplicado a dados parcialmente estáticos ou públicos. O ponto central é orquestrar requests no servidor (Server Actions), preferir endpoints batch e configurar cache/revalidate corretamente.
Implementar retries, deduplicação e fallback aumenta robustez. Para backends sem suporte a batch, use um window-batcher. Para dados sensíveis, evite cache público ou Vary conforme tokens.
Checklist prático antes de implementar em produção:
Identificar dados agrupáveis e se o backend suporta batch.
Definir política de revalidação (revalidate seconds) e stale behavior.
Instrumentar métricas de chamadas ao backend, latência e erros.
Garantir headers Cache-Control corretos nas rotas que retornam Response.
Aplicando esses padrões você reduz chamadas redundantes, melhora p95/p99 e mantém a arquitetura previsível. Comece com pequenos batches e monitore impacto antes de aumentar janela ou tempo de revalidação.
Assuntos: Next.js, app-router, server-actions, edge-cache, isr, Performance
Leia também
Busca semântica no Next.js com embeddings OpenAI e Redis Vector — passo a passo
Guia prático para montar busca semântica em Next.js usando embeddings OpenAI e Redis Vector. Mostra o jeito errado e o jeito certo, com exemplos de código…
Caching avançado no App Router: Cache-Control, tags e Edge para rotas dinâmicas
Guia prático para caching avançado no App Router do Next.js: combine Cache-Control, revalidate, next/cache tags e edge routes para controlar performance e…
Formulários sem JavaScript com Server Actions no Next.js (App Router)
Aprenda a criar formulários totalmente funcionais sem JavaScript usando Server Actions no Next.js (App Router). Exemplo prático com código, validação…
SSR multi-região no Next.js App Router: sincronizar ISR e invalidar caches
Guia prático para sincronizar ISR em implantações multi-região com Next.js App Router: padrões, Server Actions, endpoint de revalidate, broadcast entre…
Preview CMS com Next.js App Router: Server Actions, Middleware e ISR na prática
Implementar preview CMS no Next.js App Router exige cookies seguros, middleware para marcar requisições e uso combinado de ISR e Server Actions. Neste artigo…
ISR on-demand com Server Actions: revalidação via webhook e middleware
Como migrar de endpoints de revalidate inseguros para uma arquitetura segura com Server Actions, webhooks assinados e middleware em Next.js App Router…