🚀 ¡Transforma tu idea en un proyecto exitoso!
Desarrollo moderno, creativo, eficiente y escalable.
Comunicación clara, entregas puntuales y soluciones que realmente funcionan.

¡Conversemos!

Hook useSWR en Next.js: Data Fetching Optimizado

useSWR es el hook de React más potente para data fetching creado por Vercel. Implementa la estrategia Stale-While-Revalidate que revoluciona cómo manejamos datos en aplicaciones React y Next.js.

1¿Stale-While-Revalidate (SWR)?

SWR significa stale-while-revalidate: sirve datos en caché (stale) inmediatamente y revalida en background para traer lo último.

  Stale-While-Revalidate (SWR)

Es una estrategia de caché que significa literalmente: 👉 “Usar lo viejo (stale) mientras traigo lo nuevo (revalidate)”.

  Ventajas y Conceptos clave de SWR

🚀 Ventajas principales:

  • Rápido: muestra datos al instante desde caché.
  • Actualizado: refresca los datos en segundo plano.
  • Mejor UX: el usuario no ve pantallas en blanco mientras carga.
  • Performance: Respuesta inmediata con datos en caché
  • Sincronización: Datos actualizados automáticamente entre componentes

🔑 Conceptos clave:

  • Fetcher: función que hace la petición y devuelve data (ej: fetch o axios).
  • Key: string/array/función que identifica la caché (ej: endpoint).
  • mutate: fuerza o actualiza la caché manualmente.
  • revalidateOnFocus: revalida al volver al tab.
  • dedupingInterval: evita requests duplicados en X ms.

🚀 ¿Cómo funciona?

  1. Si ya tienes datos guardados en caché 👉 React los muestra al instante (aunque estén desactualizados).
  2. Al mismo tiempo, en segundo plano, hace una nueva petición para traer los datos frescos.
  3. Cuando llegan los nuevos datos 👉 React actualiza el componente automáticamente.
2Instalación de SWR
jsx
"use client";
import useSWR from 'swr';

const fetcher = (...args) => fetch(...args).then(r => r.json());

export default function Profile({ id }){
const { data, error, isLoading } = useSWR(id ? `/api/users/${id}` : null, fetcher);
if (error) return <p>Error</p>;
if (isLoading) return <p>Cargando...</p>;
return <div>Hola, {data.name}</div>;
}

Notas: usar null como key evita que la petición se ejecute (fetch condicional).

Ejemplo en React con SWR

jsx
import useSWR from "swr";

const fetcher = (url) => fetch(url).then((res) => res.json());

export default function Users() {
const { data, error, isLoading } = useSWR("/api/users", fetcher);

if (isLoading) return <p>Cargando...</p>;
if (error) return <p>Error al cargar datos</p>;

return (
  <ul>
    {data.map((user) => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);
}
5Next.js 15: App Router (SSR/SSG)

App Router — componente cliente

  • En Next.js 15 con app/, para usar useSWR el componente debe ser cliente ("use client").
jsx
// app/profile/[id]/page.jsx  (Server component)
import ProfileClient from './ProfileClient';

export default function Page({ params }){
return <ProfileClient id={params.id} />; // ProfileClient contiene useSWR y "use client"
}

Pages Router (getServerSideProps / getStaticProps)

  • Prefetch server-side y pasar fallback a SWRConfig para hidratar la caché:
js
// pages/profile/[id].js
import { SWRConfig } from 'swr';

export async function getServerSideProps({ params }){
const res = await fetch(`https://.../users/${params.id}`);
const user = await res.json();
return { props: { fallback: { [`/api/users/${params.id}`]: user } } };
}

export default function Page({ fallback, params }){
return (
  <SWRConfig value={{ fallback }}>
    <ProfileClient id={params.id} />
  </SWRConfig>
);
}

Resultado: el cliente ya tiene datos, evita parpadeos.

6Fetchers: patrones comunes
  • fetch (básico):
js
const fetcher = (...args) => fetch(...args).then(res => res.json());
  • axios:
js
import axios from 'axios';
const fetcher = url => axios.get(url).then(r => r.data);
  • GraphQL (graphql-request):
js
import { request } from 'graphql-request';
const fetcher = (query) => request('/graphql', query);
7Configuración global (SWRConfig)
jsx
import { SWRConfig } from 'swr';

export default function App({ Component, pageProps }){
return (
  <SWRConfig value={{
    fetcher: (...args) => fetch(...args).then(r => r.json()),
    dedupingInterval: 2000,
    errorRetryCount: 3,
  }}>
    <Component {...pageProps} />
  </SWRConfig>
);
}

Usa esto para centralizar fetcher y opciones por defecto.

8Revalidación y opciones importantes (cheta rápida)
  • revalidateOnFocus (default true) — revalida al volver al tab.
  • revalidateOnReconnect — revalida al reconectar la red.
  • refreshInterval — polling (ms).
  • dedupingInterval — evita requests duplicados (ms).
  • revalidateIfStale — revalida aunque los datos estén stale.
  • revalidateOnMount — revalidar al montar.

Ejemplo con opciones locales:

js
const { data } = useSWR('/api/data', fetcher, {
refreshInterval: 5000,
revalidateOnFocus: true,
dedupingInterval: 1500,
});
9Mutaciones y UI optimista (patrón)
jsx
import useSWR, { mutate } from 'swr';

function useTodos(){
const { data: todos } = useSWR('/api/todos');
return { todos };
}

async function addTodoOptimistic(text){
const newTodo = { id: Date.now(), text };
// 1) Actualiza cache local inmediatamente (optimistic)
mutate('/api/todos', current => [...(current||[]), newTodo], false);

// 2) Persiste al servidor
try{
  await fetch('/api/todos', { method: 'POST', body: JSON.stringify(newTodo) });
  // 3) Revalida y trae datos reales
  mutate('/api/todos');
}catch(err){
  // revertir: revalidar para recuperar estado real
  mutate('/api/todos');
  throw err;
}
}

Nota: mutate(key, updater, false) actualiza sin revalidar. Luego mutate(key) revalida.

10Paginación / Infinite Loading
jsx
import useSWRInfinite from 'swr/infinite';
const PAGE_SIZE = 10;
const getKey = (pageIndex, previousPageData) => {
if (previousPageData && previousPageData.length === 0) return null; // no más
return `/api/posts?page=${pageIndex + 1}&limit=${PAGE_SIZE}`;
};

export default function Posts(){
const { data, size, setSize, isLoading } = useSWRInfinite(getKey, fetcher);
const posts = (data || []).flat();
return (
  <div>
    {posts.map(p => <Post key={p.id} {...p} />)}
    <button onClick={() => setSize(size + 1)} disabled={isLoading}>Cargar más</button>
  </div>
);
}
11Conditional / Dependent fetching
  • Conditional: useSWR(shouldFetch ? '/api/x' : null, fetcher)
  • Dependent: declarar key como función:
js
const { data: user } = useSWR(userId ? `/api/user/${userId}` : null, fetcher);
const { data: profile } = useSWR(() => user ? `/api/profile/${user.id}` : null, fetcher);
12Suspense
  • SWR soporta suspense: true si usas React Suspense.
jsx
// App-level
<SWRConfig value={{ suspense: true }}>
<Suspense fallback={<Spinner/>}>
  <Profile id={id} />
</Suspense>
</SWRConfig>

Importante: con suspense cambian las formas de manejar loading/error.

13Prefetching y cache manual
  • Prefetch con mutate:
js
// en hover o en onMouseEnter
mutate('/api/user/123', fetcher('/api/user/123'));
  • Cambiar provider de cache:
js
import { SWRConfig } from 'swr';
<SWRConfig value={{ provider: () => new Map() }}>
<App />
</SWRConfig>

O puedes implementar provider que use localStorage para persistir entre reloads.

14Errores comunes y debugging rápido
  Advertencia

  • No usar “use client” en componentes que usan hooks -> error en App Router.
  • Key indefinida -> pasala como null si no quieres fetchear.
  • Mutate sin revalidar -> recuerda llamar mutate(key) después si quieres confirmar.
  • Requests duplicados -> ajustar dedupingInterval.
  • CORS / 401 -> revisar cabeceras y auth (SWR no maneja auth out-of-the-box).

15Cheatsheet de opciones (rápido)
  Cheatsheet de opciones (rápido)

  • fetcher — función para obtener datos
  • fallbackData — datos iniciales
  • revalidateOnFocus — boolean
  • revalidateOnReconnect — boolean
  • refreshInterval — ms polling
  • dedupingInterval — ms dedupe
  • suspense — boolean
  • onErrorRetry — función para controlar reintentos

  Recursos

  • Docs oficiales SWR — https://swr.vercel.app/
  • Artículo explicativo (LogRocket) — buen repaso de patrones
  • Artículo con tips y errores comunes (refine.dev)
  • Artículos y cheatsheets para useSWRInfinite y mutate