Mejora el rendimiento de Next.js con TanStack Query (React Query)
Cómo TanStack Query mejora el rendimiento de Next.js: caché, revalidación en segundo plano, mutaciones y SSR/SSG hidratado.

Gestionar correctamente el estado del servidor es una de las palancas de rendimiento más infravaloradas en Next.js. Con patrones manuales (fetch + useEffect) aparece código repetido, condiciones de carrera y múltiples solicitudes idénticas.
TanStack Query convierte ese caos en una capa declarativa: define clave, función y política de caducidad; el framework hace el resto.
¿Por qué el fetching suele ser un cuello de botella oculto?
Patrones problemáticos comunes:
- Repetir la misma petición en componentes distintos.
- Hacer transiciones de página completas para cambios puntuales.
- Usar estado global para datos del servidor (abstracción incorrecta).
- No aprovechar un caché compartido entre navegaciones.
- Refetch agresivo al volver el foco → presión sobre la API.
Resultado: más latencia percibida y navegación menos fluida.
TanStack Query estandariza frescura, caducidad e invalidación → menos sorpresas.
Lo que obtienes con TanStack Query
- De-duplicación y caché compartido.
- Stale-While-Revalidate: pintado inmediato + actualización silenciosa.
- Recolección automática de queries no usados.
- Invalidation específico tras mutaciones.
- Hidratación SSR/SSG para mejor primer render y SEO.
Instalación
yarn add @tanstack/react-queryCompatible con App Router (Next.js 13/14). No dependemos de APIs nuevas de 15+.
Proveedor global del QueryClient
1"use client";
2import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3import { useState, ReactNode } from "react";
4
5export function QueryProviders({ children }: { children: ReactNode }) {
6 const [queryClient] = useState(() => new QueryClient());
7 return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
8}En app/layout.tsx:
1import { QueryProviders } from "@/app/query-providers";
2
3export default function RootLayout({ children }: { children: React.ReactNode }) {
4 return (
5 <html lang="es">
6 <body>
7 <QueryProviders>{children}</QueryProviders>
8 </body>
9 </html>
10 );
11}Fetch imperativo vs declarativo
Sin TanStack Query:
1const [posts, setPosts] = useState([]);
2const [loading, setLoading] = useState(true);
3useEffect(() => {
4 let cancelado = false;
5 fetch("/api/posts")
6 .then(r => r.json())
7 .then(d => { if (!cancelado) { setPosts(d); setLoading(false); } });
8 return () => { cancelado = true; };
9}, []);Con TanStack Query:
1"use client";
2import { useQuery } from "@tanstack/react-query";
3
4async function fetchPosts() {
5 const res = await fetch("https://jsonplaceholder.typicode.com/posts");
6 if (!res.ok) throw new Error("Error al obtener posts");
7 return res.json();
8}
9
10export function PostsList() {
11 const { data, isLoading, isError } = useQuery({
12 queryKey: ["posts"],
13 queryFn: fetchPosts,
14 staleTime: 60_000,
15 });
16 if (isLoading) return <p>Cargando…</p>;
17 if (isError) return <p>No se pudieron cargar los datos.</p>;
18 return <ul>{data.map((p: any) => <li key={p.id}>{p.title}</li>)}</ul>;
19}Beneficios: menos código, reutilización instantánea de caché, menos solicitudes.
Mutaciones e invalidación
1import { useMutation, useQueryClient } from "@tanstack/react-query";
2
3export function AddTodoButton() {
4 const qc = useQueryClient();
5 const mutation = useMutation({
6 mutationFn: async (nuevo: { title: string }) => {
7 await fetch("/api/todos", {
8 method: "POST",
9 headers: { "Content-Type": "application/json" },
10 body: JSON.stringify(nuevo),
11 });
12 },
13 onSuccess: () => qc.invalidateQueries({ queryKey: ["todos"] }),
14 });
15 return (
16 <button onClick={() => mutation.mutate({ title: "Aprender TanStack Query" })}>
17 {mutation.isPending ? "Guardando…" : "Añadir"}
18 </button>
19 );
20}Secuencia clara → acción usuario → mutación → invalidación dirigida → UI fresca.
Hidratación SSR / SSG (pre 15)
1// pages/posts.tsx
2import { QueryClient, dehydrate } from "@tanstack/react-query";
3import { useQuery } from "@tanstack/react-query";
4
5async function fetchPosts() {
6 const r = await fetch("https://jsonplaceholder.typicode.com/posts");
7 return r.json();
8}
9
10export async function getStaticProps() {
11 const qc = new QueryClient();
12 await qc.prefetchQuery({ queryKey: ["posts"], queryFn: fetchPosts });
13 return { props: { dehydratedState: dehydrate(qc) }, revalidate: 3600 };
14}
15
16export default function PostsPage() {
17 const { data } = useQuery({ queryKey: ["posts"], queryFn: fetchPosts });
18 return (
19 <main>
20 <h1>Todos los posts</h1>
21 <ul>{data?.map((p: any) => <li key={p.id}>{p.title}</li>)}</ul>
22 </main>
23 );
24}Consejos de rendimiento
- Ajusta
staleTimepor dominio de datos. - Desactiva
refetchOnWindowFocussi no aporta valor. - Prefetch en hover de enlaces clave.
- Invalidación granular > invalidación global.
- Centraliza claves en un helper.
- Usa actualizaciones optimistas para UX de baja latencia.
Por qué lo usamos en Aniq‑UI
- Menos boilerplate para quien compra la plantilla.
- Mejor sensación de velocidad entre rutas.
- Separa claramente estado de servidor y UI.
- Ayuda a Core Web Vitals.
Conclusión
TanStack Query convierte efectos dispersos en una capa de caché consistente y escalable. Es una apuesta segura para proyectos que deben mantenerse en el tiempo y plantillas reutilizables.
Mira ejemplos reales en plantillas Next.js listas para producción en Aniq‑UI.com.
Palabras clave SEO: rendimiento Next.js, React Query Next.js, TanStack Query, plantillas Next.js, caché Next.js, fetching datos Next.js
¿Encontró útil este artículo?
También te puede interesar

Optimización de rendimiento en Next.js: Guía práctica para acelerar tus aplicaciones

Plantilla gratuita de página de aterrizaje 3D para sitios web de jugos, bebidas y marketing de productos (Next.js + Three.js + GSAP)
