Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Obtén datos en un Server Component y pásalos a un Client Component como datos de fallback para SWR. Así obtienes contenido renderizado en el servidor al instante con revalidación en el cliente: lo mejor de ambos mundos.
// app/users/page.tsx (Server Component)
import { UserList } from "./user-list";
async function getUsers() {
const res = await fetch("https://api.example.com/users", {
next: { revalidate: 60 },
});
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
return <UserList fallbackData={users} />;
}// app/users/user-list.tsx (Client Component)
"use client";
import useSWR from "swr";
interface User {
id: string;
name: string;
}
const fetcher = (url: string) => fetch(url).then((r) => r.json());
export function UserList({ fallbackData }: { fallbackData: User[] }) {
const { data: users } = useSWR<User[]>("/api/users", fetcher, {
fallbackData,
revalidateOnMount: true, // Volver a obtener al montar para datos actualizados
});
return (
<ul>
{users?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}// app/dashboard/page.tsx (Server Component)
import { SWRProvider } from "./swr-provider";
import { DashboardStats } from "./dashboard-stats";
import { RecentOrders } from "./recent-orders";
async function fetchDashboardData() {
const [stats, orders] = await Promise.all([
fetch("https://api.example.com/stats").then((r) => r.json()),
fetch("https://api.example.com/orders?limit=10").then((r) => r.json()),
]);
return { stats, orders };
}
export default async function DashboardPage() {
const { stats, orders } = await fetchDashboardData();
return (
<SWRProvider
fallback={{
"/api/stats": stats,
"/api/orders?limit=10": orders,
}}
>
<h1>Panel</h1>
<DashboardStats />
<RecentOrders />
</SWRProvider>
);
}// app/dashboard/swr-provider.tsx
"use client";
import { SWRConfig } from "swr";
export function SWRProvider({
children,
fallback,
}: {
children: React.ReactNode;
fallback: Record<string, any>;
}) {
return (
<SWRConfig
value={{
fallback,
fetcher: (url: string) => fetch(url).then((r) => r.json()),
}}
>
{children}
</SWRConfig>
);
}// app/dashboard/dashboard-stats.tsx
"use client";
import useSWR from "swr";
interface Stats {
revenue: number;
orders: number;
customers: number;
}
export function DashboardStats() {
// Los datos están disponibles al instante desde el fallback del servidor
const { data: stats } = useSWR<Stats>("/api/stats", {
refreshInterval: 30000, // Mantener las estadísticas actualizadas
});
return (
<div className="grid grid-cols-3 gap-4">
<div>Ingresos: ${stats?.revenue.toLocaleString()}</div>
<div>Pedidos: {stats?.orders}</div>
<div>Clientes: {stats?.customers}</div>
</div>
);
}// app/dashboard/recent-orders.tsx
"use client";
import useSWR from "swr";
interface Order {
id: string;
customer: string;
total: number;
status: string;
}
export function RecentOrders() {
const { data: orders } = useSWR<Order[]>("/api/orders?limit=10");
return (
<table>
<thead>
<tr>
<th>Pedido</th>
<th>Cliente</th>
<th>Total</th>
<th>Estado</th>
</tr>
</thead>
<tbody>
{orders?.map((order) => (
<tr key={order.id}>
<td>{order.id}</td>
<td>{order.customer}</td>
<td>${order.total}</td>
<td>{order.status}</td>
</tr>
))}
</tbody>
</table>
);
}SWRConfig.fallback.fallback de SWRConfig es un mapa de { [key]: data } que pre-pobla la caché de SWR en el cliente.useSWR se monta con datos de fallback coincidentes, renderiza de inmediato sin estado de carga.revalidateOnMount: false), asegurando que los datos se mantengan actualizados.preload también puede usarse para iniciar la obtención antes de que un componente se monte.Precarga en Server Component:
// app/products/[id]/page.tsx
import { preload } from "swr";
import { ProductDetail } from "./product-detail";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
export default function ProductPage({ params }: { params: { id: string } }) {
// Iniciar la obtención de inmediato
preload(`/api/products/${params.id}`, fetcher);
return <ProductDetail id={params.id} />;
}Fallback por página sin proveedor global:
"use client";
import useSWR from "swr";
export function UserCard({ fallbackData }: { fallbackData: User }) {
const { data } = useSWR("/api/me", fetcher, {
fallbackData, // Fallback por hook
revalidateOnMount: false, // Confiar en los datos del servidor, omitir la revalidación inicial
});
return <div>{data?.name}</div>;
}Streaming con Suspense:
// Server Component
import { Suspense } from "react";
export default function Page() {
return (
<div>
<h1>Panel</h1>
<Suspense fallback={<StatsSkeleton />}>
<StatsLoader />
</Suspense>
</div>
);
}
async function StatsLoader() {
const stats = await fetch("https://api.example.com/stats").then((r) => r.json());
return <StatsClient fallbackData={stats} />;
}fallback en SWRConfig está tipada como Record<string, any>. No hay comprobación en tiempo de compilación de que las claves coincidan con tus claves de useSWR.type FallbackMap = {
"/api/stats": Stats;
"/api/orders?limit=10": Order[];
};
function TypedSWRProvider<T extends Record<string, unknown>>({
fallback,
children,
}: {
fallback: T;
children: React.ReactNode;
}) {
return <SWRConfig value={{ fallback }}>{children}</SWRConfig>;
}fallback deben coincidir exactamente con las claves usadas en useSWR. Una discrepancia significa que los datos de fallback se ignoran silenciosamente y aparece un estado de carga.revalidateOnMount: true (el valor por defecto cuando se proporciona fallbackData y no se establece revalidateIfStale) provoca una reobtención inmediata. Establécelo en false si los datos del servidor son lo bastante recientes.useSWR directamente en Server Components. Los hooks de SWR son solo del cliente. Los Server Components deben usar fetch directamente o una capa de datos del lado del servidor.fallback en SWRConfig, los datos están disponibles para todos los hooks useSWR anidados. Ten cuidado con las colisiones de claves entre componentes no relacionados.| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Fallback de SWRConfig | Caché pre-poblada, renderizado instantáneo | Las claves deben coincidir exactamente, sin seguridad de tipos en el fallback |
| fallbackData por hook | Explícito, a nivel de componente | Hay que pasar props desde el servidor |
| Obtención solo en servidor (sin SWR) | Lo más simple, sin JS en el cliente | Sin revalidación en el cliente |
| Hidratación de React Query | dehydrate/hydrate integrados | Biblioteca distinta, más configuración |
La prop fallback es un mapa { [key]: data } que pre-pobla la caché de SWR en el cliente. Cuando un hook useSWR se monta con una clave coincidente, renderiza de inmediato sin estado de carga y luego revalida en segundo plano.
fallbackData se establece por hook useSWR como prop: useSWR(key, fetcher, { fallbackData }).fallback en SWRConfig proporciona datos para todos los hooks anidados mediante un mapa clave-valor.fallbackData para componentes individuales; usa fallback para pre-población a nivel de página.Los datos de fallback se ignoran silenciosamente. SWR muestra un estado de carga en lugar de contenido instantáneo. Asegúrate siempre de que las claves en tu mapa fallback coincidan exactamente con las claves usadas en las llamadas a useSWR.
No. Los hooks de SWR son solo del cliente. Los Server Components deben usar fetch directamente o una capa de datos del lado del servidor, y luego pasar los datos como props o mediante SWRConfig fallback a Client Components.
Por defecto, sí (revalidateOnMount: true). Esto asegura que los datos se mantengan actualizados. Establece revalidateOnMount: false si los datos del servidor son lo bastante recientes y quieres omitir la obtención inicial en el cliente.
Pasa los datos obtenidos en el servidor mediante SWRConfig fallback o fallbackData. Esto asegura que el HTML renderizado en el servidor coincida con el renderizado inicial del cliente, evitando errores de hidratación.
Los datos serializados desde Server Components deben ser compatibles con JSON. Las Dates, Maps, Sets e instancias de clases no sobreviven el límite. Conviértelos a objetos planos o cadenas antes de pasarlos.
import { preload } from "swr";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
export default function Page({ params }: { params: { id: string } }) {
preload(`/api/products/${params.id}`, fetcher);
return <ProductDetail id={params.id} />;
}La prop fallback está tipada como Record<string, any>: no hay comprobación en tiempo de compilación de que las claves coincidan con las de useSWR. Para un tipado más estricto, crea un wrapper tipado:
type FallbackMap = {
"/api/stats": Stats;
"/api/orders?limit=10": Order[];
};
function TypedSWRProvider<T extends Record<string, unknown>>({
fallback,
children,
}: {
fallback: T;
children: React.ReactNode;
}) {
return <SWRConfig value={{ fallback }}>{children}</SWRConfig>;
}Usa <Suspense> alrededor de un Server Component asíncrono que obtiene datos y los pasa como fallbackData a un Client Component con useSWR. El servidor transmite el HTML a medida que los datos están disponibles.
Todos los hooks useSWR anidados que comparten la misma clave reciben los mismos datos de fallback. Ten cuidado de que componentes no relacionados no usen accidentalmente la misma clave, lo que haría que compartan datos obsoletos o incorrectos.
Revisado por Chris St. John·Última actualización: 10 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥