wandres.dev
RTK QUERY A FONDO · cache y tags

RTK Query frente a TanStack Query

El cierre del nivel enfrenta las dos caches de servidor dominantes sin fingir neutralidad ni fabricar un empate. Empieza por lo que las separa de verdad, que no son las funcionalidades sino dos ontologías distintas de la cache: RTK Query declara un catálogo cerrado de endpoints con etiquetas tipadas, TanStack Query abre un espacio de claves libres donde cualquier función asíncrona es cacheable. De ahí se deducen todas las diferencias derivadas: invalidación por grafo declarado frente a invalidación por coincidencia de prefijo, generación de hooks frente a composición manual, dependencia del store frente a independencia total, integración con DevTools frente a herramientas propias. Sigue con los costes de entrada y de salida de cada una, el peso real en el bundle y la portabilidad entre frameworks. Cierra con un criterio de decisión que se apoya en la arquitectura ya existente y no en la preferencia.

⏱ 20 min

Después de cuatro lecciones exprimiendo RTK Query toca la pregunta incómoda: en 2026, con TanStack Query como estándar de facto del data-fetching en React y con presencia ya en Vue, Svelte, Solid y Angular, ¿tiene sentido seguir eligiendo la cache de Redux? La respuesta honesta no es un empate diplomático ni una preferencia disfrazada de argumento. Ambas resuelven el mismo problema —el estado de servidor como cache y no como estado— y ambas lo resuelven bien; sus catálogos de funcionalidades convergieron hace años y comparar listas de opciones no distingue nada. Lo que sí distingue es que parten de dos ideas incompatibles sobre qué es una entrada de cache y quién decide su identidad. Todo lo demás —la ergonomía, la invalidación, el tamaño, la portabilidad— se deduce de esa raíz, y entenderla es lo que convierte la elección en una decisión de arquitectura en lugar de una preferencia de gusto.

🎯 Al terminar esta lección sabrás
  • Distinguir la ontología del catálogo cerrado de endpoints frente a la del espacio abierto de claves.
  • Comparar la invalidación por grafo de etiquetas tipadas con la invalidación por coincidencia de prefijo.
  • Evaluar los costes de entrada, de salida y de portabilidad de cada librería.
  • Decidir entre ambas a partir de la arquitectura existente y no de la preferencia personal.

Dos ontologías de la entrada de cache

En RTK Query una entrada de cache existe porque tú declaraste un endpoint. El catálogo es cerrado y conocido en tiempo de compilación: listarPosts existe, listarComentarios no existe hasta que lo añadas, y el sistema de tipos lo sabe. La identidad de una entrada es el par de nombre de endpoint y argumento serializado, y el argumento es un valor único cuyo tipo declaraste. De esa clausura salen las capacidades más distintivas de la librería: los hooks generados por nombre, la seguridad de tipos extremo a extremo sin anotaciones y las etiquetas restringidas a un vocabulario que TypeScript verifica.

En TanStack Query una entrada existe porque alguien la pidió. La identidad es un array arbitrario de valores serializables —la clave de consulta— y la obtención es cualquier función que devuelva una promesa. No hay catálogo, no hay registro previo, no hay nombre que declarar. Esto abre un espacio infinito de entradas y con él una libertad estructural: las claves son jerárquicas por convención, de modo que un array que empieza igual que otro se considera su prefijo, y sobre esa jerarquía se construye toda la invalidación.

// RTK Query: catalogo cerrado. La entrada existe porque el endpoint existe.
const { data } = useObtenerPostQuery(id);

// TanStack Query: espacio abierto. La entrada existe porque la clave se uso.
const { data } = useQuery({
  queryKey: ["posts", "detalle", id],
  queryFn: () => obtenerPost(id),
  staleTime: 30_000,
});

Esa diferencia de dos líneas contiene todo lo demás. En la primera, la librería sabe qué endpoints hay y puede generar código; en la segunda, la librería no sabe nada del dominio y a cambio acepta cualquier fuente asíncrona, venga de HTTP, de IndexedDB, de un websocket o de un cálculo caro en un worker.

La clausura del catálogo tiene una virtud que se nota poco y se agradece mucho: hace que la superficie de red de una aplicación sea enumerable. Puedes abrir un fichero y leer todo lo que tu cliente le pide a su backend, con sus tipos y sus etiquetas, sin buscar llamadas dispersas. Esa propiedad no es técnica sino organizativa, y explica por qué RTK Query encaja tan bien en equipos grandes: el contrato con el servidor deja de ser folclore repartido entre carpetas y se vuelve un documento. La apertura de TanStack Query renuncia a esa enumerabilidad por defecto, aunque un equipo disciplinado puede recuperarla imponiéndose una fábrica de claves y un módulo de funciones de obtención. La diferencia es quién garantiza el orden: la librería o la costumbre.

ℹ️
La convergencia funcional ya ocurrió

Deduplicación, reintentos con retroceso exponencial, refresco al recuperar el foco y al reconectar, polling con suspensión sin foco, paginación e infinito, prefetch, actualizaciones optimistas con reversión, integración con Suspense, hidratación desde servidor: las dos lo tienen. Cualquier comparativa que se apoye en la ausencia de una de estas funciones está desactualizada. Comparar por catálogo de funcionalidades no discrimina; comparar por ontología, por coste de integración y por trayectoria del proyecto sí.

Invalidación declarada frente a invalidación por prefijo

La consecuencia más visible de las dos ontologías es cómo se dice que algo caducó. RTK Query lo dice con el grafo de etiquetas que construiste en la segunda lección: la consulta declara qué provee, la mutación qué invalida, y ninguna conoce a la otra. TanStack Query lo dice invalidando claves, normalmente por prefijo, desde el cliente de consultas.

// TanStack Query: invalidacion imperativa por prefijo de clave.
const cliente = useQueryClient();

const editar = useMutation({
  mutationFn: guardarPost,
  onSuccess: (_datos, post) => {
    cliente.invalidateQueries({ queryKey: ["posts", "detalle", post.id] });
    cliente.invalidateQueries({ queryKey: ["posts", "lista"] });
  },
});

La comparación es reveladora en ambas direcciones. A favor de las etiquetas: la relación es declarativa y bidireccionalmente ciega, de modo que una consulta nueva que provea Post entra automáticamente en el grafo sin que nadie edite ninguna mutación, y el vocabulario cerrado convierte un error de escritura en un error de compilación. A favor de las claves: la jerarquía por prefijo es una intuición inmediata que no requiere aprender un segundo concepto, y permite invalidar subárboles enteros con una expresión que se lee sola. El coste de las claves es que la mutación sí conoce a las consultas —hay que enumerar qué invalidar— y que un cambio en el esquema de claves rompe en silencio invalidaciones que dejan de casar, sin que ningún tipo avise. Ese silencio es el precio de la apertura.

La mitigación estándar de ese silencio es la fábrica de claves, un módulo que centraliza la construcción de cada clave y elimina las cadenas escritas a mano. Merece adoptarse desde el primer día, porque convierte un espacio abierto en uno gobernado sin renunciar a su flexibilidad, y porque restituye la enumerabilidad que la clausura de RTK Query regala.

export const clavesPost = {
  todo: ["posts"] as const,
  listas: () => [...clavesPost.todo, "lista"] as const,
  lista: (filtro: string) => [...clavesPost.listas(), filtro] as const,
  detalle: (id: string) => [...clavesPost.todo, "detalle", id] as const,
};

// Invalidar todas las listas sin tocar los detalles ya cargados.
cliente.invalidateQueries({ queryKey: clavesPost.listas() });

Con esa fábrica, la invalidación por prefijo alcanza una expresividad que las etiquetas no tienen: puedes derribar un subárbol completo con una sola expresión, sin haber declarado por adelantado ninguna relación. Es el reflejo exacto de la ventaja contraria. Las etiquetas te obligan a nombrar el vocabulario y te pagan con verificación; las claves te dejan improvisar la jerarquía y te pagan con alcance.

flowchart TD
A[necesito una cache de servidor] --> B{ya uso Redux para estado de cliente?}
B -->|si, y de forma central| C[RTK Query]
B -->|no| D{necesito cachear fuentes no HTTP?}
D -->|si| E[TanStack Query]
D -->|no| F{comparto codigo con otro framework?}
F -->|si| E
F -->|no| G[TanStack Query por defecto]
C --> H[una sola DevTool y un solo store]
E --> I[independencia total del gestor de estado]
style C fill:#cba6f7,color:#11111b
style E fill:#f38ba8,color:#11111b
style G fill:#a6e3a1,color:#11111b
🟣

RTK Query

Catálogo cerrado, hooks generados, etiquetas tipadas, cache dentro del store. Su fuerza nace de asumir que ya hay Redux.

🔴

TanStack Query

Claves libres, cualquier función asíncrona, invalidación por prefijo, cero dependencia del gestor de estado.

Costes de entrada, de salida y de portabilidad

El coste de entrada de RTK Query es cero si ya tienes Redux Toolkit —viene en el mismo paquete, comparte store, middleware y DevTools— y es alto si no lo tienes, porque adoptarlo significa adoptar Redux entero: proveedor, store, middleware y el modelo mental completo. El de TanStack Query es siempre el mismo y siempre bajo: un proveedor y un cliente, sin ninguna opinión sobre dónde vive tu estado de cliente.

Ese coste de entrada asimétrico explica una observación empírica: prácticamente nadie adopta Redux por RTK Query, mientras que mucha gente adopta RTK Query por tener ya Redux. La causalidad va siempre en el mismo sentido, y esa direccionalidad es la primera pista de que la comparación no se decide en el terreno de la cache.

El coste de salida es más interesante y casi nadie lo calcula. Salir de RTK Query implica reescribir endpoints y hooks, pero la frontera es estrecha porque los hooks generados tienen nombres previsibles y las consultas están todas en un fichero: la migración es mecánica y localizable. Salir de TanStack Query es igual de mecánico pero más disperso, porque las claves y las funciones de obtención viven allá donde se usaron, salvo que hayas impuesto tú mismo la disciplina de una fábrica de claves centralizada, que es exactamente lo que la librería te deja hacer y no te obliga a hacer.

En portabilidad no hay discusión. TanStack Query tiene adaptadores oficiales para React, Vue, Svelte, Solid y Angular sobre un núcleo común, de modo que un equipo con productos en varios frameworks reutiliza no solo el conocimiento sino las propias claves y funciones. RTK Query tiene una capa agnóstica de framework, pero su ergonomía real —los hooks generados— es de React, y en la práctica es una elección de ecosistema React con Redux.

Sobre el peso conviene ser preciso porque circulan cifras engañosas en las dos direcciones. Comparar RTK Query con TanStack Query a secas favorece al primero, que reutiliza infraestructura ya presente si Redux Toolkit está instalado. Comparar la adopción completa —store, proveedor, middleware, reducers y la cache— favorece claramente al segundo, que no arrastra nada. La comparación honesta no es entre dos paquetes sino entre dos incrementos: cuánto añade cada opción sobre lo que tu proyecto ya tiene hoy. Y ese incremento depende de una decisión que no es de cache.

💡
Existe una tercera opción y a veces es la correcta

En aplicaciones sobre frameworks con servidor —Next, Remix, TanStack Start— buena parte del estado de servidor puede no cruzar nunca al cliente: se resuelve en el servidor, se serializa al HTML y ninguna cache de cliente participa. Antes de elegir entre las dos librerías conviene preguntarse qué fracción de tus datos necesita realmente vivir en el navegador con caducidad, refresco e invalidación. Para la fracción que solo se lee una vez por navegación, la mejor cache es la que no existe. Ambas librerías conviven perfectamente con ese modelo mediante hidratación, y reducir su ámbito a los datos verdaderamente interactivos suele ser una mejora mayor que cualquier elección entre las dos.

⚠️
Correr las dos sobre los mismos datos es el único error inequívoco

Una aplicación grande puede acabar con las dos librerías por acumulación histórica, y mientras cada una gobierne recursos distintos el daño es solo de peso y de curva. Lo que nunca debe ocurrir es que ambas cacheen el mismo recurso, porque entonces hay dos copias con caducidades independientes, dos invalidaciones que no se hablan y una interfaz capaz de mostrar dos versiones del mismo dato en dos pantallas contiguas. Si te encuentras en esa situación, la prioridad no es elegir la mejor librería sino trazar una frontera por recurso y migrar hasta que cada dato tenga una sola cache. Un dato, un dueño, una cache: la misma regla que rige todo el track.

El criterio

Con las ontologías, la invalidación y los costes sobre la mesa, la decisión ya no necesita ninguna tabla comparativa: necesita mirar hacia dentro, a lo que tu proyecto es hoy.

Reducido a lo esencial, el criterio cabe en tres frases. Si tu aplicación ya usa Redux de forma central para estado de cliente y el equipo vive en sus DevTools, RTK Query es la elección obvia: aprovechas la infraestructura existente, evitas una segunda librería de cache y ganas la coherencia de que todo —cliente y servidor— sea inspeccionable en el mismo lugar. Si no usas Redux, adoptarlo entero para tener RTK Query casi nunca se justifica, porque estarías pagando el modelo mental completo de un gestor de estado global por una capacidad que otra librería te da sin condiciones. Y si trabajas con varios frameworks o necesitas cachear fuentes que no son HTTP, TanStack Query gana sin matices.

Hay un caso intermedio que el criterio anterior deja fuera y que aparece más de lo que se admite: el proyecto que tiene Redux pero apenas lo usa, con dos slices heredados de una decisión antigua y todo lo demás resuelto con estado local. Ahí la respuesta correcta no es adoptar RTK Query para aprovechar el store, sino reconocer que ese store ya no se gana su sitio. La cache de servidor no debe ser el argumento que mantenga viva una infraestructura que nadie usaría hoy; si el Redux existente solo se justifica porque RTK Query viene con él, la cola está moviendo al perro. Migrar esos dos slices a Zustand o a estado local y adoptar TanStack Query deja una arquitectura más pequeña que cualquiera de las dos mitades por separado.

Conviene añadir una observación sobre la trayectoria, no para predecir sino para calibrar. En 2026 el patrón dominante en aplicaciones nuevas de React es TanStack Query para el servidor con Zustand o Jotai para el cliente, y Redux Toolkit ha quedado como la elección deliberada de equipos grandes, dominios auditables o bases de código con años de historia. Eso no convierte a RTK Query en obsoleto —está mantenido, es excelente y sigue siendo la mejor cache posible dentro de Redux— pero sí significa que la pregunta relevante rara vez es cuál de las dos caches es mejor, sino si tu aplicación debía tener Redux en primer lugar. Contestada esa, la cache se deduce.

La elección de cache no se decide en la cache: es un corolario de una decisión que ya tomaste

Lo más instructivo de esta comparación es que, hecha con honestidad, se disuelve. Empiezas creyendo que hay que sopesar dos librerías y terminas descubriendo que la pregunta era otra y que ya la habías respondido antes de llegar aquí: si tu estado de cliente necesita o no la disciplina de Redux. Respondida esa, la cache se sigue casi mecánicamente, y todo el debate de funcionalidades era ruido sobre una decisión que se tomó un nivel más arriba. Este patrón —una comparación aparentemente técnica que resulta ser el corolario de una decisión arquitectónica previa— reaparece por todas partes en cuanto se aprende a verlo, y reconocerlo es más valioso que cualquier detalle de esta lección concreta. Ocurre porque las herramientas maduras de un mismo dominio convergen: dado tiempo suficiente y competencia mutua, todas acaban implementando lo que las otras hacen bien, y sus catálogos de funcionalidades dejan de distinguirlas. Lo que no converge nunca son sus premisas, porque la premisa no es una funcionalidad que se pueda copiar: es la forma que la herramienta le pide a tu aplicación. RTK Query da por supuesto que hay un store con acciones y reducers y construye la mejor cache imaginable dentro de ese mundo. TanStack Query da por supuesto que no hay nada y construye la mejor cache imaginable para quien no quiere comprometerse con nada. Ninguna de las dos es mejor en abstracto porque la pregunta está mal formulada en abstracto; solo hay mejor ajuste con una arquitectura concreta. Por eso la manera adulta de elegir herramientas nunca es comparar sus tablas de funcionalidades, sino identificar qué presupone cada una y comprobar cuál de esos presupuestos ya es cierto en tu proyecto. La herramienta correcta casi siempre es la que asume lo que tú ya decidiste; la incorrecta es la que te obliga a decidir de nuevo, a su favor, algo que ya estaba resuelto.

⚔️ Decide con la arquitectura, no con la preferencia
  1. Escribe en una frase por qué tu proyecto usa Redux, si lo usa. Si no encuentras la frase, la comparación de esta lección ya está resuelta.
  2. Toma un endpoint real de RTK Query y reescríbelo en TanStack Query con su clave jerárquica y su función de obtención; cuenta las líneas y los conceptos de cada versión.
  3. Reescribe también su invalidación: las etiquetas de un lado, la invalidación por prefijo del otro. Comprueba qué ocurre en cada caso si escribes mal el nombre.
  4. Añade en la versión de TanStack Query una fábrica centralizada de claves y observa cuánto de la disciplina que RTK Query impone puedes recuperar voluntariamente.
  5. Mide el peso añadido de cada opción en tu bundle contando también lo que arrastra: el store, el proveedor y el middleware cuando no los tenías.
  6. Busca en tu código algún recurso cacheado dos veces por dos mecanismos distintos y traza la frontera que deja a cada dato con una sola cache y un solo dueño.