Guia completo do SWR: estratégias de cache e atualizações otimistas na prática

Eu estava olhando mais uma vez para aquele conhecido círculo de loading na tela e atualizando a página da lista de usuários pela enésima vez. Toda troca de aba: loading. Toda volta de outra página: loading. Até quando o navegador perdia o foco porque eu atendia uma ligação, ao retornar aparecia outro loading. E o mais irritante? Aqueles dados tinham sido carregados apenas um segundo antes.
Em projetos React, cada busca de dados costuma exigir useState, useEffect e toda a lógica de loading e error. São pelo menos 20 linhas. Pior ainda: quando vários componentes precisam dos mesmos dados, você eleva o estado para o componente pai e escreve ainda mais código, ou deixa cada componente fazer sua própria requisição e desperdiça recursos de rede.
Quando descobri o SWR, tive aquela sensação de “então dava para fazer assim”, parecida com a primeira vez que troquei a cópia manual de pastas pelo Git. Três linhas resolveram 80% dos problemas. Sem exagero.
Hoje vamos falar sobre o SWR, a biblioteca de busca de dados para React criada pela Vercel. Tudo gira em torno de uma expressão: stale-while-revalidate. Parece muito técnico? A ideia é bem simples: primeiro você vê uma “foto antiga” guardada no cache, o que é rápido; em segundo plano, uma foto nova é buscada, garantindo precisão; quando ela chega, a troca acontece sem interromper você.
Por que usar SWR? Três grandes problemas da busca de dados tradicional
Primeiro, veja como costuma ser a busca de dados tradicional em React. Suponha que você precise exibir uma lista de usuários:
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch('/api/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}
São 24 linhas apenas para exibir uma lista. Talvez pareça aceitável, mas pense nestes cenários:
Problema 1: código repetitivo até cansar
Todo componente que precisa de dados repete esse padrão. Lista de usuários, lista de artigos, lista de comentários: três estados, vários ifs, tratamento de erro… Até copiar e colar dá vergonha. Certa vez contei e, em um projeto de médio porte, esse código repetitivo ocupava 20% da base.
Problema 2: cache? Não existe
O pior é não haver cache. O usuário sai da página inicial, abre os detalhes e volta: mais um longo loading. Os dados foram buscados há apenas 30 segundos, mas tudo é solicitado novamente. Quando alguém reclama que “o site está lento”, muitas vezes a API não é lenta; simplesmente não criamos cache.
Alguém pode sugerir um gerenciador de estado global. Sim, é possível. Depois, porém, você precisa manter store, action e reducer do Redux ou Zustand. Uma funcionalidade que levaria três minutos vira meia hora de desenho de arquitetura.
Problema 3: sincronizar dados entre componentes é um pesadelo
O caso fica ainda pior quando vários componentes usam os mesmos dados. Imagine que a navegação superior, a barra lateral e a página de mensagens precisam mostrar a contagem de mensagens não lidas. O que fazer? Elevar o estado até o topo? Cada atualização então atravessa mais de dez níveis de props. Usar Context? A cada mensagem, toda a árvore pode renderizar novamente.
Lembro de ter passado dois dias ajustando a sincronização de estado de uma funcionalidade simples de notificações. Ao enviar o código, a tela estava tomada por setState e useEffect. Nem eu queria olhar para aquilo.
É por isso que o SWR ganhou espaço. Não é apenas mais uma biblioteca: ele resolve um problema real. Como fica o código anterior reescrito com SWR?
import useSWR from 'swr';
function UserList() {
const { data, error, isLoading } = useSWR('/api/users', fetcher);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error!</div>;
return <ul>{data.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}
Nove linhas. Pronto. E já inclui cache, revalidação automática e compartilhamento entre componentes. O fetcher é apenas a sua função de fetch, normalmente definida uma vez de forma global:
const fetcher = url => fetch(url).then(r => r.json());
A diferença é evidente.
O conceito central do SWR: a estratégia stale-while-revalidate
O nome SWR vem da estratégia de expiração de cache definida na RFC 5861. Não se assuste com “RFC”: a lógica é fácil de entender.
Uma analogia com fotos: você quer ver uma foto recente de um amigo. No método tradicional, liga para ele, pede uma foto e fica esperando. Com SWR, primeiro abre uma foto antiga do último encontro, talvez da semana passada, enquanto envia uma mensagem pedindo outra. Quando a nova foto chega, você faz a troca.
Qual é a diferença essencial? Você não precisa esperar sem ver nada. O conteúdo aparece imediatamente, ainda que possa estar um pouco desatualizado, e no fim você recebe a versão mais recente. Isso é stale-while-revalidate: mostrar a versão antiga enquanto a nova é validada.
Fluxo completo do SWR:
-
Primeira etapa: retornar o cache imediatamente (stale)
- Quando o componente é montado, o SWR consulta primeiro o cache local
- Há cache? Ele retorna imediatamente e a página abre na hora
- Não há cache? Retorna undefined e exibe loading
-
Segunda etapa: buscar em segundo plano (revalidate)
- Com ou sem cache, uma requisição à API é iniciada
- O usuário não percebe, pois já está vendo o conteúdo
-
Terceira etapa: atualizar os dados
- Quando a API responde, cache e interface são atualizados discretamente
- Se os dados mudaram, o React renderiza novamente
- Se nada mudou, nada acontece
Veja um exemplo real de uma página de cotação de ações:
function StockPrice({ symbol }) {
const { data, error } = useSWR(`/api/stock/${symbol}`, fetcher);
return (
<div>
<h2>{symbol}</h2>
<p>Preço: {data ? `$${data.price}` : 'Loading...'}</p>
<span>Atualizado em: {data?.updatedAt}</span>
</div>
);
}
Na primeira visita, data é undefined e aparece “Loading…”. Um segundo depois, a API responde e o preço é exibido.
O usuário troca de aba para ler e-mails e volta dez minutos depois. O preço aparece instantaneamente a partir do cache, sem piscar o loading. Ao mesmo tempo, o SWR já iniciou uma nova requisição em segundo plano. Se a cotação mudou, a página é atualizada; se não mudou, permanece como está.
A experiência fica muito fluida.
Três momentos de revalidação automática:
Por padrão, o SWR busca os dados novamente nestas situações:
- Quando o componente é montado novamente (
revalidateOnMount): ao atualizar a página ou retornar pela navegação - Quando a janela recupera o foco (
revalidateOnFocus): ao voltar para a aba do navegador - Quando a rede é restabelecida (
revalidateOnReconnect): depois de ficar offline
Na prática, você não precisa gerenciar esses eventos: o SWR mantém os dados atualizados. O usuário passa meia hora lendo e-mails e volta ao site? O SWR atualiza automaticamente. A conexão cai no metrô e volta no 4G ao sair da estação? O SWR carrega novamente.
Quando descobri que esses comportamentos eram o padrão, fiquei surpreso. Antes, ou os cenários eram ignorados e o usuário via dados antigos, ou era preciso monitorar manualmente os eventos visibilitychange e online. Agora tudo já vem pronto.
O importante conceito de key:
Talvez você tenha notado que o primeiro argumento de useSWR é uma string como '/api/users'. Essa é a “key”, um conceito muito importante.
// Dois componentes usam a mesma key
function Header() {
const { data } = useSWR('/api/user', fetcher);
return <div>Olá, {data?.name}</div>;
}
function Profile() {
const { data } = useSWR('/api/user', fetcher);
return <div>Perfil: {data?.email}</div>;
}
Quando a key é igual, os dados são compartilhados. O SWR faz apenas uma requisição à API, entrega o mesmo resultado aos dois componentes e mantém as atualizações sincronizadas.
É assim que ele resolve a sincronização entre componentes. Não é preciso gerenciar estado nem usar Context; basta a mesma key.
Estratégias de cache em detalhes: buscas mais inteligentes
Já vimos que o SWR faz cache e revalida automaticamente. Em projetos reais, porém, cada tipo de dado tem uma exigência diferente de atualização. Um avatar pode permanecer igual por uma semana; uma cotação precisa mudar a cada segundo. Por isso, é necessário ajustar a estratégia de cache.
O comportamento padrão é inteligente, mas não serve para tudo
A configuração padrão do SWR é bastante agressiva:
- Revalida sempre que o componente é montado
- Revalida sempre que a janela recupera o foco
- Revalida sempre que a conexão volta
Para dados que exigem alta atualidade, como mensagens e status online, isso é perfeito. Para dados relativamente estáveis, como listas de artigos e perfis, pode ser desperdício. Não há necessidade de enviar uma requisição a cada troca entre duas abas.
Principais opções de configuração
O SWR oferece várias opções, mas estas são as mais usadas:
const { data } = useSWR('/api/articles', fetcher, {
revalidateOnFocus: false, // Não busca novamente ao recuperar o foco
revalidateOnReconnect: false, // Não busca novamente quando a rede volta
refreshInterval: 0, // Intervalo de polling em ms; 0 desativa
dedupingInterval: 2000, // Requisições iguais em 2 s são desduplicadas
});
Os nomes são diretos e praticamente explicam o comportamento.
Cenário 1: dados em tempo real, como ações e usuários online
const { data } = useSWR('/api/stock/AAPL', fetcher, {
refreshInterval: 1000, // Polling a cada segundo
revalidateOnFocus: true // Atualiza imediatamente ao voltar à página
});
Esses dados precisam estar sempre atuais. Polling é a solução mais simples, embora WebSocket seja melhor em alguns casos — assunto para outro artigo.
Cenário 2: dados relativamente estáveis, como perfil e lista de artigos
const { data } = useSWR('/api/profile', fetcher, {
revalidateOnFocus: false, // Não atualiza a cada foco
refreshInterval: 0, // Sem polling
dedupingInterval: 60000, // Não repete a requisição por 1 minuto
});
Um perfil de usuário não muda com frequência. Não é preciso atualizar ao trocar de aba, poupando requisições. Quando o usuário altera o perfil, você ainda pode disparar uma atualização manualmente com mutate, como veremos adiante.
Cenário 3: conteúdo quase estático, como documentação e ajuda
const { data } = useSWR('/api/docs', fetcher, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateOnMount: false, // Nem sequer revalida ao montar
revalidateIfStale: false, // Não revalida mesmo se os dados estiverem stale
});
Esse tipo de dado pode não mudar durante um mês. Depois do primeiro carregamento, ele praticamente fica em cache para sempre, até o usuário atualizar a página manualmente.
Configuração global versus local
Se a estratégia de toda a aplicação for parecida, use SWRConfig para definir padrões globais:
import { SWRConfig } from 'swr';
function App() {
return (
<SWRConfig value={{
refreshInterval: 3000,
fetcher: (url) => fetch(url).then(r => r.json()),
revalidateOnFocus: false,
}}>
<Dashboard />
</SWRConfig>
);
}
Todos os useSWR nos componentes filhos herdam essas opções. Um useSWR específico ainda pode sobrescrevê-las:
// Neste componente, revalidateOnFocus será true e substituirá o false global
const { data } = useSWR('/api/realtime', fetcher, {
revalidateOnFocus: true
});
Desduplicação de requisições: economize sua cota da API
Um recurso muito útil é a desduplicação automática. Se três componentes forem montados ao mesmo tempo e todos usarem useSWR('/api/user'), o SWR não enviará três requisições. Ele envia apenas uma e compartilha o resultado.
A janela padrão de desduplicação é de dois segundos (dedupingInterval: 2000). Portanto, requisições idênticas nesse período são combinadas. Se componentes puderem ser remontados rapidamente, como em trocas rápidas de aba, aumente esse valor.
Esse recurso já me salvou. Em uma página de lista, um problema na lógica fazia o carregamento disparar várias vezes durante a rolagem rápida, enviando mais de dez requisições iguais. Depois de ajustar a desduplicação, tudo voltou ao normal. Não corrigiu a causa, mas pelo menos evitou esgotar a cota da API (risos).
Busca condicional: requisições que dependem de outros dados
Às vezes, você precisa buscar A e então buscar B a partir do resultado de A. No SWR, passar null como key pausa a requisição:
// Primeiro, busca o usuário
const { data: user } = useSWR('/api/user', fetcher);
// Depois que user carregar, busca os projetos do usuário
const { data: projects } = useSWR(
user ? `/api/projects?userId=${user.id}` : null,
fetcher
);
Enquanto user é undefined, a key é null e o SWR não envia a requisição. Quando o usuário termina de carregar, a key se torna válida e a busca de projects começa. Dependências sequenciais podem ser simples assim.
Atualização otimista: a arma secreta da experiência do usuário
O cache resolve a velocidade de leitura, mas ainda há as ações do usuário. Quando alguém clica em “curtir”, você espera a API responder para acender o coração ou o acende imediatamente e envia a requisição em segundo plano?
Essa é a ideia da atualização otimista: presumir que a operação terá sucesso, atualizar a interface imediatamente e reverter em caso de falha. Parece arriscado, mas a maioria das operações funciona, exceto quando há problema de rede ou servidor. A melhora na experiência é perceptível.
Comparação entre o método tradicional e a atualização otimista
Método tradicional:
- O usuário clica em “curtir”
- O botão entra em loading ou fica desativado
- Aguarda de 500 ms a 2 s, dependendo da rede
- A API confirma o sucesso e o coração acende
- O usuário pensa: “este site é um pouco lento”
Atualização otimista:
- O usuário clica em “curtir”
- O coração acende imediatamente pela atualização local
- A requisição é enviada em segundo plano
- Normalmente funciona e nada mais precisa acontecer
- Raramente falha; nesse caso, o coração volta a ficar cinza e aparece “Falha na operação”
No segundo caso, a latência percebida é de 0 ms. A sensação é de resposta imediata.
A função mutate: controle manual do cache
O SWR oferece mutate para atualizar o cache manualmente. O uso mais simples é:
import { mutate } from 'swr';
// Dispara manualmente a revalidação de /api/user
mutate('/api/user');
Uma atualização otimista exige mais controle. Veja um exemplo completo de lista de tarefas:
import useSWR, { mutate } from 'swr';
function TodoList() {
const { data: todos } = useSWR('/api/todos', fetcher);
const addTodo = async (text) => {
const newTodo = { id: Date.now(), text, completed: false };
// Configuração central da atualização otimista
mutate(
'/api/todos',
async (currentTodos) => {
// 1. Exibe o novo item imediatamente na interface
const optimisticData = [...currentTodos, newTodo];
// 2. Envia a requisição real em segundo plano
const savedTodo = await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(newTodo)
}).then(r => r.json());
// 3. Substitui os dados temporários pelos dados reais
return [...currentTodos, savedTodo];
},
{
optimisticData: [...todos, newTodo], // Exibe imediatamente
rollbackOnError: true, // Reverte em caso de erro
revalidate: false, // Não precisa revalidar novamente
}
);
};
return (
<div>
{todos?.map(todo => <div key={todo.id}>{todo.text}</div>)}
<button onClick={() => addTodo('New task')}>Add</button>
</div>
);
}
O trecho é um pouco longo, mas a lógica é clara:
- O usuário clica em “Add”
- O novo item aparece imediatamente na lista por meio de
optimisticData - A requisição POST é enviada em segundo plano
- Em caso de sucesso, o resultado real do servidor substitui o temporário, possivelmente com campos como id e timestamp
- Em caso de falha, os dados anteriores são restaurados por
rollbackOnError: true
Quatro opções essenciais
O objeto options de mutate tem quatro configurações importantes:
1. optimisticData: os dados que aparecem imediatamente
optimisticData: [...todos, newTodo] // Ou uma função
Você pode passar um valor fixo ou uma função. A forma de função é mais flexível:
optimisticData: (currentTodos) => [...currentTodos, newTodo]
2. populateCache: usar ou não o retorno para atualizar o cache
O padrão é true. Na maioria dos casos, você quer atualizar o cache com a resposta da API, pois o servidor pode adicionar id, createdAt e outros campos. Se a API não retornar os dados completos, use false.
3. revalidate: revalidar ou não
Normalmente, use false. Como os dados já foram atualizados manualmente, não é preciso enviar outra requisição. Se você não confiar na própria lógica de atualização, pode pedir ao SWR que confirme novamente.
4. rollbackOnError: reverter ou não em caso de erro
Essa opção é muito importante. Com true, se a função passada a mutate lançar um erro, o SWR restaura automaticamente os dados anteriores. O usuário curtiu e o coração acendeu, mas a API falhou? O SWR o deixa cinza outra vez.
Também é possível passar uma função para ter controle mais detalhado:
rollbackOnError: (error) => {
// Não reverte em timeout de rede; reverte nos outros erros
return error.name !== 'AbortError';
}
Na prática: excluir uma tarefa
A exclusão também é um caso clássico de atualização otimista:
const deleteTodo = async (id) => {
mutate(
'/api/todos',
async (currentTodos) => {
// Remove imediatamente da lista
const optimistic = currentTodos.filter(t => t.id !== id);
// Envia a exclusão em segundo plano
await fetch(`/api/todos/${id}`, { method: 'DELETE' });
// Retorna os dados atualizados
return optimistic;
},
{
optimisticData: todos.filter(t => t.id !== id),
rollbackOnError: true,
}
);
};
Quando o usuário clica em excluir, a tarefa desaparece imediatamente. Se o servidor disser que ela não existe ou a requisição falhar, a tarefa volta e uma mensagem de erro pode ser mostrada.
useSWRMutation: uma forma mais elegante
O SWR 2.0 introduziu o Hook useSWRMutation, dedicado a mutações e com código mais conciso:
import useSWRMutation from 'swr/mutation';
async function updateUser(url, { arg }) {
await fetch(url, {
method: 'POST',
body: JSON.stringify(arg)
});
}
function Profile() {
const { trigger, isMutating } = useSWRMutation('/api/user', updateUser);
return (
<button
onClick={() => trigger({ name: 'John' })}
disabled={isMutating}
>
Update Name
</button>
);
}
trigger dispara a mutação, e isMutating informa se a requisição está em andamento. É bem mais confortável do que gerenciar loading à mão.
Quando usar uma atualização otimista?
Nem toda operação é adequada. Uma boa regra prática:
✅ Adequado para atualização otimista:
- Curtir ou favoritar, pois a chance de falha é baixa e a ação é reversível
- Adicionar ou excluir itens de uma lista, quando o resultado imediato importa
- Alternar estados, como ativar notificações
- Salvar campos de formulário, como em rascunhos
❌ Inadequado para atualização otimista:
- Pagamentos, que exigem confirmação
- Exclusão de conta, que é irreversível e deve ser tratada com cuidado
- Mudanças em dados sensíveis, como senha e permissões
- Operações calculadas pelo servidor, como “gerar relatório”, cujo resultado não é conhecido antes
O princípio é simples: use atualização otimista em operações reversíveis, com baixa taxa de falha e nas quais a experiência tem prioridade. Em lógica crítica ou ações irreversíveis, espere a resposta do servidor.
Já cometi um erro com isso. Em uma funcionalidade de comentários, o texto aparecia imediatamente e a experiência parecia ótima. Um dia, percebi que usuários estavam enviando o mesmo comentário várias vezes. Com a rede lenta, eles clicavam em “Enviar”, não viam uma resposta de confirmação e clicavam mais dez vezes. Cada clique fazia uma atualização otimista e a tela mostrava dez comentários iguais. A API evitava duplicatas, mas a interface ficava confusa.
Depois, adicionei uma verificação de isMutating para desativar o botão durante a requisição. Problema resolvido. A lição: atualização otimista é excelente, mas exige atenção aos casos extremos.
SWR versus React Query: qual escolher?
Depois de tantas vantagens do SWR, talvez você pergunte sobre React Query. Ao pesquisar “busca de dados em React”, também aparecem muitos artigos sobre ele. Qual usar?
As duas bibliotecas são excelentes e continuavam recebendo manutenção ativa em 2025. Uma escolha diferente não vai condenar o projeto, mas a opção adequada deixa o desenvolvimento mais confortável.
Comparação de tamanho: SWR é mais leve
- SWR: 5,3 KB após gzip
- React Query (TanStack Query): 16,2 KB
Em projetos muito sensíveis ao tamanho do bundle, como landing pages e aplicações web móveis, a vantagem do SWR é clara. Para a maioria dos projetos, porém, os cerca de 10 KB de diferença não são decisivos.
Comparação de complexidade: SWR é mais simples
A API do SWR é minimalista e gira em torno de um Hook, useSWR. Você entende em cinco minutos e começa a usar em dez.
React Query oferece mais recursos, mas a curva de aprendizado também é maior. É preciso entender QueryClient, useQuery, useMutation, queryKeys, cache time, stale time e outros conceitos. A documentação é extensa.
Para equipes com uma stack mais recente ou menos experiência em frontend, SWR é mais amigável.
Comparação de recursos: React Query é mais completo
React Query tem alguns recursos que o SWR não oferece:
-
DevTools oficiais: permitem visualizar estados das queries, conteúdo do cache e ciclos de refetch. A experiência de depuração é excelente; o SWR não tem DevTools oficiais.
-
Controle mais detalhado do cache: permite configurar separadamente cache time, por quanto tempo os dados ficam na memória, e stale time, quanto tempo levam para ficar stale. O SWR trabalha de forma mais simples com cache e revalidação.
-
Queries paginadas e infinitas: React Query oferece
useInfiniteQuery, enquanto o SWR usauseSWRInfinite. Os recursos são parecidos, mas a API de React Query é um pouco mais madura. -
Suporte mais poderoso a mutações:
useMutationtem um desenho mais sistemático, com estado global de mutação, estratégia de retry e callback onSettled.useSWRMutationsó chegou no SWR 2.0 e é relativamente simples.
Comunidade e ecossistema
React Query, hoje chamado TanStack Query, tem uma comunidade maior e mais downloads semanais no NPM. Isso facilita encontrar respostas no StackOverflow.
SWR é criado pela Vercel, assim como Next.js. A documentação oficial inclui orientações específicas para integrar os dois, então SWR é a opção mais próxima do ecossistema Next.js.
Minha recomendação
| Escolha SWR | Escolha React Query |
|---|---|
| Projeto relativamente simples, sem lógica complexa de busca | Projeto complexo, com necessidade de controle detalhado do cache |
| Bundle sensível a tamanho, como no mobile | Cerca de 10 KB extras não importam |
| Usa Next.js | Usa outro framework, como CRA ou Vite |
| Equipe com menos experiência em frontend | Equipe familiarizada com bibliotecas complexas e recursos avançados |
| Precisa começar rapidamente | Pode investir tempo para aprender o ecossistema completo |
Na minha experiência, SWR é a primeira opção em projetos pequenos ou na fase de MVP: simples e rápido. Quando o projeto cresce e as necessidades ficam complexas, talvez faça sentido migrar para React Query. Ainda assim, SWR é suficiente para a maioria dos projetos.
Outro ponto: o custo de migração não é tão alto. Os conceitos centrais são semelhantes — cache, revalidação e mutação. Se um dia você realmente precisar dos recursos avançados de React Query, a troca não será tão dolorosa. Não vale a pena gastar energia demais com essa decisão antecipadamente.
Next.js e SWR: uma combinação natural
SWR e Next.js são produtos da Vercel e funcionam muito bem juntos. Quem usa Next.js encontra alguns pontos de integração particularmente convenientes.
Uso do SWR no App Router
No App Router do Next.js 13+, componentes são Server Components por padrão. Como SWR é uma biblioteca de cliente, ele deve ser usado em um Client Component:
'use client'; // É obrigatório marcar como Client Component
import useSWR from 'swr';
export default function Profile() {
const { data } = useSWR('/api/user', fetcher);
return <div>{data?.name}</div>;
}
Não esqueça de adicionar 'use client'. É um detalhe que iniciantes esquecem com frequência.
SSR + SWR: uso de fallback data
Um dos pontos fortes do Next.js é a renderização no servidor. Você pode buscar os dados iniciais em getStaticProps ou getServerSideProps e entregá-los ao SWR como fallback:
// pages/profile.js
export async function getStaticProps() {
const user = await fetch('https://api.example.com/user').then(r => r.json());
return {
props: {
fallback: {
'/api/user': user // A key corresponde à key do SWR
}
},
revalidate: 60 // ISR: gera novamente a cada 60 segundos
}
}
export default function Profile({ fallback }) {
return (
<SWRConfig value={{ fallback }}>
<UserProfile />
</SWRConfig>
);
}
function UserProfile() {
// A primeira renderização usa os dados de fallback, sem loading
const { data } = useSWR('/api/user', fetcher);
return <div>{data.name}</div>;
}
Assim, o visitante vê o conteúdo completo desde o primeiro acesso, o que favorece SEO, e o SWR continua revalidando depois de assumir no cliente. Você obtém as duas vantagens.
Pré-busca (prefetching)
Dados importantes podem ser carregados antes mesmo do clique:
import { mutate } from 'swr';
function ArticleLink({ id }) {
const prefetch = () => {
mutate(`/api/article/${id}`, fetch(`/api/article/${id}`).then(r => r.json()));
};
return (
<Link href={`/article/${id}`} onMouseEnter={prefetch}>
Read more
</Link>
);
}
Quando o cursor passa sobre o link, o artigo começa a ser carregado. Ao clicar, os dados talvez já estejam no cache e a página abre instantaneamente.
Rolagem infinita com useSWRInfinite
Um requisito comum em páginas de lista é carregar mais itens durante a rolagem:
import useSWRInfinite from 'swr/infinite';
function ArticleList() {
const getKey = (pageIndex, previousPageData) => {
if (previousPageData && !previousPageData.length) return null; // Não há mais itens
return `/api/articles?page=${pageIndex + 1}&limit=10`;
};
const { data, size, setSize, isLoading } = useSWRInfinite(getKey, fetcher);
const articles = data ? data.flat() : [];
const isLoadingMore = isLoading || (size > 0 && data && typeof data[size - 1] === 'undefined');
return (
<div>
{articles.map(article => (
<div key={article.id}>{article.title}</div>
))}
<button
onClick={() => setSize(size + 1)}
disabled={isLoadingMore}
>
{isLoadingMore ? 'Loading...' : 'Load More'}
</button>
</div>
);
}
setSize(size + 1) carrega a próxima página. O SWR mantém os dados de cada página em cache, então voltar a rolagem não exige outro carregamento.
Recomendações de desempenho
Ao usar SWR com Next.js, considere estas otimizações:
-
Ajuste a frequência de revalidação de acordo com os dados
- Conteúdo estático, como documentação e ajuda: desative a revalidação automática
- Dados do usuário: mantenha a revalidação padrão ao recuperar o foco
- Dados em tempo real: use polling com
refreshInterval
-
Cache das rotas de API
- As rotas de API do Next.js podem usar o header
Cache-Control - O SWR respeita os headers de cache HTTP
- As rotas de API do Next.js podem usar o header
-
Evite requisições em cascata
- Se vários dados dependerem uns dos outros, considere combinar as requisições no servidor
- Outra opção é usar a busca paralela de dados do Next.js
Em um projeto real, usei SWR com ISR (Incremental Static Regeneration) na lista de artigos de um blog. A primeira tela era HTML gerado estaticamente e abria na hora; enquanto o usuário lia, o SWR verificava em segundo plano se havia artigos novos e atualizava a lista quando necessário. A experiência ficou muito fluida e o SEO, excelente.
Esse é o atrativo de Next.js com SWR: o desempenho de um site estático e a atualidade de uma aplicação dinâmica.
Conclusão
Depois de tudo isso, vale resumir.
O SWR resolve três grandes problemas da busca de dados em React: código redundante, ausência de cache e sincronização entre componentes. A estratégia stale-while-revalidate combina velocidade, ao exibir o cache imediatamente, com precisão, ao atualizar em segundo plano. Somada à revalidação automática, à desduplicação e às atualizações otimistas, ela cobre cerca de 90% dos cenários de busca de dados.
Se eu tivesse de resumir as melhores práticas:
- Ajuste a estratégia de cache às características dos dados: faça polling de dados em tempo real e use cache longo para dados estáveis
- Use atualizações otimistas em ações do usuário: desde que a taxa de falha seja baixa e a ação seja reversível
- Aproveite a key para compartilhar dados: não adicione um gerenciador de estado apenas para esse compartilhamento
- Em projetos Next.js, considere SWR primeiro: combine fallback data com SSR
- Use SWR em projetos simples e React Query nos complexos: evite arquitetura excessiva
Uma última observação honesta: SWR não é uma solução mágica. Ele não corrige uma API mal projetada nem substitui uma boa arquitetura. Mas, quando a API do backend já está bem desenhada, ele deixa o código frontend muito mais elegante.
Que tal experimentar SWR no próximo projeto? Aposto que você vai gostar do resultado.
Fluxo completo de uso do SWR
Etapas completas, da instalação e configuração ao uso e à otimização do cache
⏱️ Estimated time: 1 hr
- 1
Step 1: Instalação e uso básico
Instale:
```bash
npm install swr
```
Uso básico:
```tsx
'use client'
import useSWR from 'swr'
const fetcher = (url: string) => fetch(url).then(r => r.json())
export function UserList() {
const { data, error, isLoading } = useSWR('/api/users', fetcher)
if (error) return <div>Failed to load</div>
if (isLoading) return <div>Loading...</div>
return <div>{data.map(user => <div key={user.id}>{user.name}</div>)}</div>
}
```
Pontos principais:
• Use 'use client' para marcar o componente como Client Component
• A função fetcher cuida da busca dos dados
• useSWR retorna data, error e isLoading - 2
Step 2: Configuração das opções globais
Use SWRConfig:
```tsx
'use client'
import { SWRConfig } from 'swr'
const fetcher = (url: string) => fetch(url).then(r => r.json())
export function Providers({ children }) {
return (
<SWRConfig
value={{
fetcher,
revalidateOnFocus: true,
revalidateOnReconnect: true,
refreshInterval: 0,
}}
>
{children}
</SWRConfig>
)
}
```
Opções comuns:
• revalidateOnFocus: revalida quando a janela recupera o foco
• revalidateOnReconnect: revalida quando a conexão é restabelecida
• refreshInterval: atualização periódica; 0 desativa
• dedupingInterval: intervalo de desduplicação; padrão de 2000 ms
Ponto principal: configure no componente raiz para que todos os componentes filhos compartilhem as opções. - 3
Step 3: Integração com Next.js (SSR)
Combine com fallback data:
```tsx
// app/users/page.tsx (Server Component)
import { getUsers } from '@/lib/users'
export default async function UsersPage() {
const initialUsers = await getUsers()
return <UsersList initialUsers={initialUsers} />
}
// components/UsersList.tsx (Client Component)
'use client'
import useSWR from 'swr'
export function UsersList({ initialUsers }) {
const { data } = useSWR('/api/users', fetcher, {
fallbackData: initialUsers // Usa os dados do SSR como valor inicial
})
return <div>{data.map(...)}</div>
}
```
Vantagens:
• Os dados da primeira tela vêm do SSR, acelerando o carregamento
• As atualizações seguintes usam SWR e oferecem uma experiência melhor
• Reúne as vantagens do SSR e do cache no cliente
Ponto principal: use fallbackData, e não initialData; fallbackData não dispara uma revalidação. - 4
Step 4: Atualização otimista
Use mutate para fazer uma atualização otimista:
```tsx
import { mutate } from 'swr'
async function updateUser(id: string, name: string) {
// Atualização otimista: atualiza a interface imediatamente
mutate(`/api/users/${id}`, { ...user, name }, false)
// Envia a requisição
await fetch(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify({ name })
})
// Revalida para garantir que os dados estejam corretos
mutate(`/api/users/${id}`)
}
```
Pontos principais:
• O terceiro argumento false de mutate significa não revalidar
• Depois que a requisição termina com sucesso, chame mutate novamente para revalidar
• A experiência melhora porque a atualização aparece imediatamente
FAQ
O que é SWR e por que ele é necessário?
Problemas da busca de dados tradicional:
• Cada busca exige useState, useEffect e tratamento de loading e error
• São pelo menos 20 linhas de código
• Quando vários componentes precisam dos mesmos dados, é preciso elevar o estado ou fazer requisições separadas
Vantagens do SWR:
• Cache automático: requisições com a mesma key compartilham o cache
• Revalidação automática: atualiza quando a janela recupera o foco ou a rede volta
• Desduplicação automática: uma requisição idêntica é enviada apenas uma vez
• Repetição automática após erro
• Um Hook simplifica 90% do código de busca de dados
Três linhas resolvem 80% dos problemas:
```tsx
const { data, error, isLoading } = useSWR('/api/users', fetcher)
```
Qual é a diferença entre SWR e React Query?
• Simples e leve, com curva de aprendizado baixa
• Adequado para a maioria dos projetos
• Recursos relativamente simples
• Criado pela Vercel e bem integrado ao Next.js
React Query:
• Poderoso e rico em recursos
• Adequado para cenários complexos
• Curva de aprendizado maior
• Mais flexível, porém mais complexo de configurar
Recomendação:
• Projeto simples → SWR
• Projeto complexo → React Query
• Em dúvida → comece com SWR e migre se realmente precisar
Ponto principal: evite excesso de arquitetura; escolha SWR para projetos simples e React Query para os complexos.
Como usar SWR no Next.js?
```tsx
// app/users/page.tsx (Server Component)
export default async function UsersPage() {
const initialUsers = await getUsers()
return <UsersList initialUsers={initialUsers} />
}
// components/UsersList.tsx (Client Component)
'use client'
export function UsersList({ initialUsers }) {
const { data } = useSWR('/api/users', fetcher, {
fallbackData: initialUsers // Usa os dados do SSR
})
return <div>{data.map(...)}</div>
}
```
Vantagens:
• Os dados da primeira tela vêm do SSR, acelerando o carregamento
• As atualizações seguintes usam SWR e oferecem uma experiência melhor
• Reúne as vantagens do SSR e do cache no cliente
Pontos principais:
• Use fallbackData, e não initialData
• fallbackData não dispara uma revalidação
• Adequado ao App Router do Next.js
Qual é a estratégia de cache do SWR?
Fluxo:
1. Primeira requisição: exibe loading
2. Sucesso: exibe e armazena os dados em cache
3. Nova requisição: exibe os dados em cache imediatamente
4. Revalidação em segundo plano: atualiza os dados sem interromper o usuário
5. Ao terminar: troca discretamente pelos dados novos
Recursos principais:
• Cache automático: a mesma key compartilha os dados
• Desduplicação automática: uma requisição idêntica é enviada apenas uma vez
• Revalidação automática: atualiza ao recuperar o foco ou a conexão
• Repetição automática após erro
Opções:
• revalidateOnFocus: revalida quando a janela recupera o foco
• revalidateOnReconnect: revalida quando a conexão volta
• refreshInterval: atualização periódica
• dedupingInterval: intervalo de desduplicação
Vantagem: os dados permanecem atualizados e a experiência é fluida, sem novas telas de loading.
Como implementar uma atualização otimista?
```tsx
import { mutate } from 'swr'
async function updateUser(id: string, name: string) {
// Atualiza a interface imediatamente
mutate(`/api/users/${id}`, { ...user, name }, false)
// Envia a requisição
await fetch(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify({ name })
})
// Revalida para garantir que os dados estejam corretos
mutate(`/api/users/${id}`)
}
```
Pontos principais:
• O terceiro argumento false de mutate significa não revalidar
• Depois do sucesso, chame mutate novamente para revalidar
• A atualização aparece imediatamente para o usuário
Atenção: se a requisição falhar, reverta para os dados originais.
Para quais cenários o SWR é adequado?
• Dados atualizados com frequência, como listas de usuários e notificações
• Vários componentes precisam dos mesmos dados
• Necessidade de cache e revalidação automáticos
• Projetos Next.js, graças à boa integração
Cenários inadequados:
• Dados estáticos simples, que não precisam de cache
• Dados buscados apenas uma vez, sem revalidação
• Necessidades complexas de cache, para as quais React Query é mais adequado
Recomendação:
• Maioria dos projetos → SWR
• Cenários complexos → React Query
• Dados simples → fetch diretamente
Ponto principal: escolha de acordo com a necessidade real; não use SWR apenas por usar.
22 min de leitura · Publicado em: 19 dez 2025 · Atualizado em: 8 set 2026
Guia completo Next.js
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
Next.js App Router + shadcn/ui: guia para combinar Server e Client Components
Aprenda a combinar Server Components e Client Components corretamente no Next.js App Router, com integração prática do shadcn/ui, design do fluxo de dados, correção de erros comuns e otimização de desempenho.
Parte 25 de 26
Próximo
Este é o post mais recente da série até agora.



Comentários
Entre com GitHub para comentar