wandres.dev
ESTADO DEL SERVIDOR · TanStack Query, SWR

SWR y el enfoque stale-while-revalidate

El nombre de SWR no es una marca: es un algoritmo que viene de una cabecera HTTP (RFC 5861). Stale-while-revalidate significa servir la copia vieja ya mismo y comprobar en paralelo si hay una nueva. Esta lección mira el bosque en vez del árbol: la idea antes que la librería y su origen en HTTP, el mínimo común que comparten SWR, TanStack Query y RTK Query, el paisaje de 2026 incluidas las caches normalizadas de GraphQL y la frontera que mueven los frameworks, y cómo elegir por restricciones de arquitectura en vez de por moda.

⏱ 16 min

El nombre de SWR no es una marca: es un algoritmo, y viene de una cabecera HTTP. Stale-while-revalidate significa sirve la copia vieja ya mismo y, en paralelo, ve a comprobar si hay una nueva. Esa sola frase, nacida en 2010 para las caches de la web en el RFC 5861, se convirtió una década después en la filosofía de toda una familia de librerías de cliente: SWR, TanStack Query, RTK Query, Apollo. Esta lección mira el bosque en vez del árbol: qué idea comparten todas, en qué se diferencian, y cómo elegir sin caer en la trampa de creer que decides entre productos rivales cuando en realidad decides entre acentos de una misma idea.

🎯 Al terminar esta lección sabrás
  • Explicar stale-while-revalidate como idea antes que como librería, y su origen en HTTP.
  • Usar useSWR y reconocer el mínimo común de todas las caches de servidor.
  • Situar el paisaje de 2026: SWR, TanStack Query, RTK Query y las de GraphQL.
  • Elegir librería por criterios de arquitectura, no de moda.

Stale-while-revalidate: la idea antes que la librería

Antes de ser un hook de React, stale-while-revalidate fue una directiva de cache HTTP. Es una respuesta elegante a un dilema viejo: si sirves solo datos garantizados frescos, el usuario espera; si sirves datos cacheados, el usuario ve cosas viejas.

La tercera vía rompe el falso dilema: sirve lo viejo sin demora, el usuario ve algo al instante, y revalida en segundo plano, el dato se pone al día sin que nadie espere. Se paga con un parpadeo eventual de actualización a cambio de eliminar casi toda espera percibida.

Trasladar eso al cliente fue el salto que dio Vercel con SWR. Lo que en HTTP era una política del servidor de cache pasó a ser el comportamiento por defecto de un hook: al montar, te devuelve al instante lo que tenga en memoria aunque esté viejo, y dispara una revalidación silenciosa. La primera vez no hay nada que servir y esperas; a partir de ahí, casi nunca.

import useSWR from "swr";

const fetcher = (url: string) => fetch(url).then((r) => r.json());

function Perfil({ id }: { id: string }) {
  const { data, error, isLoading } = useSWR(`/api/usuario/${id}`, fetcher);

  if (isLoading) return <Spinner />;
  if (error) return <ErrorBox />;
  return <h1>{data.nombre}</h1>;
}

Fíjate en que la clave de cache aquí es la propia URL, una cadena, en vez de un array. Es la misma idea de identidad de la lección de useQuery, con menos ceremonia: SWR apuesta por lo mínimo.

flowchart LR
M[montar hook] -->|hay copia| SV[sirve stale al instante]
M -->|no hay copia| W[espera primera vez]
SV --> RV[revalida en segundo plano]
W --> RV
RV -->|dato nuevo| UP[actualiza la vista]
style SV fill:#fab387,color:#11111b
style UP fill:#a6e3a1,color:#11111b

El mínimo común de todas las caches

Si pones SWR, TanStack Query y RTK Query una al lado de otra, lo que comparten pesa más que lo que las separa. Las tres cachean por una clave, deduplican peticiones idénticas, revalidan al recuperar el foco y al reconectar, exponen estados de carga y error de primera clase, y reintentan ante fallos de red.

Las tres parten de la misma premisa del nivel: el estado de servidor es una cache, no un store. Aprendida una, las demás son dialectos del mismo idioma.

Hasta las escrituras comparten espíritu. En SWR no hay un invalidateQueries, pero sí un mutate que revalida una clave o le escribe un valor a mano: exactamente el mismo gesto de reconciliación de la lección anterior, con otra sintaxis.

import { useSWRConfig } from "swr";

function BotonRefrescar() {
  const { mutate } = useSWRConfig();
  // revalida la clave tras un cambio: el invalidateQueries de SWR
  return <button onClick={() => mutate("/api/usuario/1")}>refrescar</button>;
}
🪶

SWR

De Vercel. Minimalista, clave por URL, integración natural con Next.js. Ideal para lecturas y proyectos que valoran una superficie pequeña.

🧰

TanStack Query

La completa y agnóstica de framework. Mutaciones, invalidación por prefijo, cache infinita y DevTools. El estándar de facto fuera de Redux y GraphQL.

🗃️

RTK Query

La cache dentro de Redux Toolkit. Brilla cuando ya usas Redux para el cliente y quieres una sola fuente y unas solas DevTools.

🔷

Apollo y urql

Para GraphQL. La cache se normaliza por identidad de entidad, no por endpoint, aprovechando el esquema tipado del propio GraphQL.

Cada uno de esos comportamientos —revalidar al foco, al reconectar, en intervalo— es configurable y se puede apagar. La diferencia entre librerías no está en si los tienen, sino en cuánto control fino te dan sobre ellos y cuánta ceremonia cuesta ese control.

Cómo elegir sin creer que eliges bando

La pregunta correcta no es cuál es mejor, sino qué restricciones tengo. Si tu app ya vive en Redux, RTK Query te evita una segunda librería y unas segundas DevTools. Si hablas GraphQL, Apollo o urql explotan el esquema con una cache normalizada por entidad que las de REST no pueden imitar sin esfuerzo.

Si haces sobre todo lecturas y quieres la superficie más pequeña, SWR. Y si tienes mutaciones serias, invalidación fina y quieres independencia del framework, TanStack Query, que es hoy la elección por defecto fuera de esos dos casos.

Ninguna de estas decisiones es fatal, y ese es el punto liberador: como todas comparten el modelo mental de la cache, migrar de una a otra es cambiar de dialecto, no de idioma. Eliges por restricciones presentes, no por miedo a equivocarte para siempre.

Y una advertencia de sensatez: si una pantalla hace una sola lectura que nunca se revalida ni se comparte, quizá no necesites ninguna de estas librerías. La cache brilla cuando hay repetición, concurrencia de lectores o escrituras que invalidan; para un dato que se pide una vez y se muestra, un fetch en el servidor del framework puede bastar. Elegir la herramienta incluye la opción de no meter ninguna.

La frontera que se mueve: los frameworks

Hay una capa más, y es la que más está moviendo la frontera en 2026: los frameworks. Los loaders de Next.js, React Router y TanStack Start hacen parte del trabajo en el servidor, entregando al cliente una cache ya rellena —el prefetch— que hidrata sin un segundo viaje ni un spinner.

En ese mundo la librería de cache de cliente no desaparece, pero se ocupa solo de la parte de verdad interactiva: listas que se refrescan, scroll infinito, mutaciones optimistas. El modelo mental no cambia, sigue siendo una cache de una verdad remota; solo se reparte entre servidor y cliente dónde vive esa cache.

Esa división —el servidor rellena, el cliente refina— explica por qué la discusión de 2026 ya no es tanto qué librería de cache uso, sino cuánto de mi estado de servidor puedo resolver antes de que llegue al navegador. La mejor petición sigue siendo la que no se hace.

📝
SWR o TanStack Query, la regla corta

Para la mayoría de decisiones entre las dos más populares: SWR cuando dominan las lecturas, quieres lo mínimo y vives en el ecosistema de Vercel; TanStack Query cuando hay mutaciones serias, necesitas invalidación por prefijo, cache infinita o herramientas de depuración, y quieres no atarte a un framework. Ninguna es un gestor de estado de cliente: ambas son, explícitamente, caches. Elegir mal entre ellas rara vez es grave, porque comparten el modelo mental; lo caro es elegir la categoría equivocada y meter el servidor en un store.

Se reconoce el invariante, no se elige la librería

Hay una forma inmadura de vivir este ecosistema y una madura, y la diferencia es dónde pones la atención. La inmadura colecciona librerías: aprende SWR, luego TanStack Query, luego RTK Query como si fueran tres asignaturas distintas, y vive con la ansiedad de estar usando la que pasó de moda. La madura aprende la idea una vez —stale-while-revalidate, cache por clave, revalidación por eventos, deduplicación, invalidación tras escribir— y entonces ve las librerías como lo que son: encarnaciones distintas de la misma ecuación, optimizadas para restricciones distintas. Que la idea naciera como una cabecera HTTP en 2010 y reapareciera como un hook de React en 2019 no es una casualidad de mercado: es la señal de que estamos ante un invariante, no ante una moda. Los invariantes no se eligen, se reconocen; y una vez reconocido este, elegir librería deja de ser una cuestión de identidad tribal —soy de SWR, soy de TanStack— y se vuelve lo que siempre debió ser: una lectura sobria de tus restricciones. Usas Redux. Hablas GraphQL. Tu framework ya cachea en el servidor. Necesitas invalidación fina o te basta revalidar al reenfocar. Responde eso y la librería se elige casi sola, porque cada una es la misma idea con un acento distinto. Y cuando dentro de unos años aparezca la siguiente, que aparecerá con otro nombre y otra API, no tendrás que reaprender nada: reconocerás la ecuación bajo la piel nueva y evaluarás si su acento encaja mejor con tus restricciones que el anterior. Esa es la única habilidad de esta lección que no caduca.

⚔️ Reconoce la idea bajo cada librería
  1. Reescribe una misma lectura con useSWR y con useQuery, y lista qué es idéntico y qué cambia de verdad.
  2. Nombra las cinco capacidades del mínimo común de las caches de servidor y compruébalas en la librería que uses hoy.
  3. Para tu proyecto real, escribe las restricciones —Redux sí o no, GraphQL sí o no, framework con loaders— y deriva de ellas la librería.
  4. Observa en la pestaña de red el patrón stale-while-revalidate: datos servidos al instante y una petición de revalidación en paralelo.
  5. Argumenta por qué elegir entre estas librerías es una decisión de restricciones y no de gusto ni de moda.