Carga de Recursos - Precarga de fuentes, scripts y hojas de estilos con APIs de React DOM
Receta
import { prefetchDNS, preconnect, preload, preinit } from "react-dom";
function App() {
// Carga y ejecuta un script ansiosamente
preinit("https://cdn.example.com/analytics.js", { as: "script" });
// Precarga una fuente (descarga temprano, usa luego)
preload("https://fonts.example.com/inter.woff2", {
as: "font",
type: "font/woff2",
crossOrigin: "anonymous",
});
// Precarga una hoja de estilos
preload("https://cdn.example.com/theme.css", { as: "style" });
// Prefetch de DNS para un dominio que solicitarás luego
prefetchDNS("https://api.example.com");
// Preconnect (DNS + TCP + TLS) para un dominio que solicitarás pronto
preconnect("https://images.example.com");
return <main>{/* ... */}</main>;
}Cuándo usarlo: Usa estas APIs para mejorar la carga inicial de la página y el rendimiento de la navegación del lado del cliente indicándole al navegador acerca de los recursos que necesitará antes de que React renderice los componentes que los usan.
Ejemplo Funcionando
// Una página que precarga todos los recursos críticos para un rendimiento óptimo
import { Suspense } from "react";
import { preload, preinit, preconnect } from "react-dom";
// Precarga recursos críticos a nivel de módulo (se ejecuta una vez al importar)
preconnect("https://api.myapp.com");
preload("/fonts/brand-bold.woff2", { as: "font", type: "font/woff2", crossOrigin: "anonymous" });
preload("/fonts/brand-regular.woff2", { as: "font", type: "font/woff2", crossOrigin: "anonymous" });
function CriticalCSS() {
return (
<>
{/* React deduplicates y gestiona el ordenamiento de carga de hojas de estilos via precedence */}
<link rel="stylesheet" href="/styles/reset.css" precedence="reset" />
<link rel="stylesheet" href="/styles/base.css" precedence="default" />
<link rel="stylesheet" href="/styles/theme.css" precedence="high" />
</>
);
}
function HeavyWidget() {
// Preinit un script necesario por este widget
preinit("/scripts/chart-library.js", { as: "script" });
return (
<div className="widget">
<h2>Dashboard de Ventas</h2>
<div id="chart-container" />
</div>
);
}
function ImageGallery({ images }: { images: { src: string; alt: string }[] }) {
// Precarga las primeras imágenes para visualización instantánea
images.slice(0, 3).forEach((img) => {
preload(img.src, { as: "image" });
});
return (
<div className="grid grid-cols-3 gap-4">
{images.map((img, i) => (
<img
key={i}
src={img.src}
alt={img.alt}
loading={i < 3 ? "eager" : "lazy"}
/>
))}
</div>
);
}
export default function DashboardPage() {
return (
<>
<CriticalCSS />
<title>Dashboard | MyApp</title>
<header>
<h1>Dashboard</h1>
</header>
<Suspense fallback={<p>Cargando widget...</p>}>
<HeavyWidget />
</Suspense>
<Suspense fallback={<p>Cargando galería...</p>}>
<ImageGallery
images={[
{ src: "/images/product-1.jpg", alt: "Producto 1" },
{ src: "/images/product-2.jpg", alt: "Producto 2" },
{ src: "/images/product-3.jpg", alt: "Producto 3" },
{ src: "/images/product-4.jpg", alt: "Producto 4" },
{ src: "/images/product-5.jpg", alt: "Producto 5" },
{ src: "/images/product-6.jpg", alt: "Producto 6" },
]}
/>
</Suspense>
</>
);
}Lo que esto demuestra:
preconnectestableciendo conexiones tempranas a dominios de APIpreloadpara fuentes a nivel de módulo (antes de que cualquier componente se renderice)<link rel="stylesheet" precedence={}>para CSS ordenado y deduplicadopreinitcargando ansiosamente un script de terceros dentro de un componentepreloadpara imágenes para acelerar el renderizado above-the-fold- Límites Suspense alrededor de componentes pesados para streaming
Análisis Profundo
Cómo Funciona
preload(href, options)-- Inserta una etiqueta<link rel="preload">en el documento. El navegador descarga el recurso con alta prioridad pero no lo ejecuta ni lo aplica. Úsalo para fuentes, imágenes, hojas de estilos y scripts que necesitarás pronto.preinit(href, options)-- Descarga y ejecuta/aplica el recurso inmediatamente. Para scripts, esto significa que el script se ejecuta. Para hojas de estilos, esto significa que el CSS se aplica. Úsalo cuando necesites el recurso activo de inmediato.prefetchDNS(href)-- Inserta una sugerencia<link rel="dns-prefetch">. El navegador resuelve el nombre de dominio temprano. Opción más económica -- úsala cuando puedas solicitar desde este dominio.preconnect(href)-- Inserta un<link rel="preconnect">. El navegador realiza handshake de DNS + TCP + TLS temprano. Úsalo cuando definitivamente solicitarás desde este dominio pronto.- Las cuatro APIs pueden ser llamadas en cualquier lugar -- dentro de componentes, manejadores de eventos, alcance de módulo, o incluso efectos. React deduplicarán llamadas con el mismo
href. - Durante SSR, estas llamadas emiten las etiquetas
<link>apropiadas en el<head>de HTML y como Early Hints (103 Early Hints) si el servidor lo soporta. - Stylesheet Suspense --
<link rel="stylesheet" precedence={}>le dice a React que gestione la hoja de estilos. React suspenderá el renderizado del componente hasta que la hoja de estilos se cargue, evitando un flash de contenido sin estilos.
Variaciones
Precargando al pasar el ratón (predictivo):
"use client";
import { preload } from "react-dom";
import Link from "next/link";
function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
function handleMouseEnter() {
// Precarga el CSS y la imagen hero de la página cuando el usuario pasa el ratón
preload(`/styles${href}.css`, { as: "style" });
preload(`/images${href}-hero.jpg`, { as: "image" });
}
return (
<Link href={href} onMouseEnter={handleMouseEnter}>
{children}
</Link>
);
}Precarga condicional basada en viewport:
"use client";
import { useEffect } from "react";
import { preload } from "react-dom";
function AdaptiveLoader() {
useEffect(() => {
const connection = (navigator as any).connection;
if (connection?.effectiveType === "4g") {
// Solo precarga recursos pesados en conexiones rápidas
preload("/videos/hero.mp4", { as: "video" });
preload("/fonts/display.woff2", { as: "font", crossOrigin: "anonymous" });
}
}, []);
return null;
}Precarga a nivel de módulo (se ejecuta al importar):
import { preconnect, preload } from "react-dom";
// Estas se ejecutan cuando el módulo se importa, antes de que cualquier componente se renderice
preconnect("https://cdn.example.com");
preload("https://cdn.example.com/critical.css", { as: "style" });
export default function Page() {
return <div>Contenido que necesita recursos de cdn.example.com</div>;
}Notas de TypeScript
- Las cuatro funciones se importan de
"react-dom". preload(href: string, options: { as: string; crossOrigin?: string; type?: string; fetchPriority?: "high" | "low" | "auto" }).preinit(href: string, options: { as: "script" | "style"; crossOrigin?: string; fetchPriority?: "high" | "low" | "auto"; nonce?: string }).prefetchDNS(href: string)ypreconnect(href: string, options?: { crossOrigin?: string })tienen opciones mínimas.- El atributo
precedenceen<link rel="stylesheet">se tipifica comostring.
Trampas
- Precargando demasiados recursos -- Los navegadores tienen límites en conexiones concurrentes. Precargar todo es contraproducente. Solución: Solo precarga recursos que son críticos para la experiencia above-the-fold.
preinitejecuta scripts inmediatamente -- Si el script tiene efectos secundarios o depende de elementos DOM que aún no se han renderizado, puede fallar. Solución: Usapreloaden su lugar y añade una etiqueta<script>cuando el DOM esté listo, o asegúrate de que el script maneja elementos DOM faltantes correctamente.- Precarga de fuentes sin crossOrigin -- Las fuentes cargadas a través de
preloaddeben especificarcrossOrigin: "anonymous"incluso para fuentes same-origin, según la especificación de Fetch. Solución: Siempre incluyecrossOrigin: "anonymous"para precargas de fuentes. - Hoja de estilos sin
precedenceno es gestionada -- Si omitesprecedencede un<link rel="stylesheet">, React no lo deduplicará ni suspenderá para él. Solución: Añadeprecedencea todos los enlaces de hojas de estilos que quieras que React gestione. - Timing de precarga SSR vs cliente -- Durante SSR, las sugerencias de precarga aparecen en el HTML inicial. Durante la navegación del cliente, se inyectan dinámicamente. La diferencia de timing puede afectar características de rendimiento. Solución: Prueba tanto rutas SSR como de navegación del cliente.
Alternativas
| Enfoque | Cuándo elegir |
|---|---|
| APIs de precarga de React DOM | React 19 integrado, funciona con SSR y renderizado del cliente |
HTML <link> en template <head> | Recursos estáticos conocidos en tiempo de construcción |
Componente <Script> de Next.js | Proyectos Next.js que necesitan estrategias de carga de scripts |
@next/font / next/font | Optimización de fuentes de Next.js con subsetting automático |
document.createElement("link") manual | Casos raros que necesitan control programático fuera de React |
| Resource hints vía HTTP headers | Optimización a nivel de servidor (103 Early Hints) |
FAQs
¿Cuál es la diferencia entre preload y preinit?
preloaddescarga el recurso con alta prioridad pero no lo ejecuta ni lo aplicapreinitdescarga y ejecuta/aplica el recurso inmediatamente (los scripts se ejecutan, el CSS se aplica)- Usa
preloadpara recursos que necesitarás pronto; usapreinitcuando los necesites activos de inmediato
¿Cuál es la diferencia entre prefetchDNS y preconnect?
prefetchDNSsolo resuelve el nombre de dominio (búsqueda de DNS) -- opción más económicapreconnectrealiza handshake de DNS + TCP + TLS -- más caro pero ahorra más tiempo- Usa
prefetchDNScuando puedas solicitar de un dominio; usapreconnectcuando definitivamente lo harás pronto
¿Dónde pueden ser llamadas estas APIs de carga de recursos?
- En cualquier lugar: dentro de componentes, manejadores de eventos, alcance de módulo, o efectos
- React deduplicarán llamadas con el mismo
href - Las llamadas a nivel de módulo se ejecutan cuando el módulo se importa, antes de que cualquier componente se renderice
¿Cómo precargas fuentes correctamente?
preload("/fonts/brand.woff2", {
as: "font",
type: "font/woff2",
crossOrigin: "anonymous", // requerido para todas las fuentes
});Debes incluir crossOrigin: "anonymous" incluso para fuentes same-origin, según la especificación de Fetch.
¿Cómo precargas recursos al pasar el ratón para navegación más rápida?
function NavLink({ href, children }) {
function handleMouseEnter() {
preload(`/styles${href}.css`, { as: "style" });
preload(`/images${href}-hero.jpg`, { as: "image" });
}
return (
<a href={href} onMouseEnter={handleMouseEnter}>
{children}
</a>
);
}¿Cómo funciona Stylesheet Suspense con el atributo precedence?
<link rel="stylesheet" precedence="default">le dice a React que gestione la hoja de estilos- React suspenderá el renderizado del componente hasta que la hoja de estilos se cargue, evitando un flash de contenido sin estilos
- Las hojas de estilos con el mismo valor de
precedencese agrupan juntas en<head>
¿Cómo se comportan estas APIs durante SSR vs navegación del lado del cliente?
- Durante SSR, las llamadas emiten etiquetas
<link>en el<head>de HTML y como 103 Early Hints si el servidor lo soporta - Durante la navegación del cliente, se inyectan dinámicamente en el DOM
- Prueba tanto rutas SSR como de navegación del cliente ya que las diferencias de timing pueden afectar el rendimiento
¿Puedes precargar condicionalmente basándote en la velocidad de la red?
useEffect(() => {
const conn = (navigator as any).connection;
if (conn?.effectiveType === "4g") {
preload("/videos/hero.mp4", { as: "video" });
preload("/fonts/display.woff2", {
as: "font",
crossOrigin: "anonymous",
});
}
}, []);Trampa: ¿Por qué precargar demasiados recursos es contraproducente?
- Los navegadores tienen límites en conexiones concurrentes por dominio
- Precargar todo compite por ancho de banda y puede ralentizar realmente los recursos críticos
- Solo precarga recursos que son críticos para la experiencia above-the-fold
Trampa: Mi script preinit falla porque el DOM no está listo. ¿Qué debo hacer?
preinitejecuta scripts inmediatamente, lo que puede fallar si el script depende de elementos DOM que no se han renderizado- Usa
preloaden su lugar y añade una etiqueta<script>cuando el DOM esté listo - O asegúrate de que el script maneja elementos DOM faltantes correctamente
¿Cómo se tipifican las APIs de carga de recursos en TypeScript?
preload(href: string, options: {
as: string;
crossOrigin?: string;
type?: string;
fetchPriority?: "high" | "low" | "auto";
}): void;
preinit(href: string, options: {
as: "script" | "style";
crossOrigin?: string;
fetchPriority?: "high" | "low" | "auto";
nonce?: string;
}): void;Las cuatro funciones se importan de "react-dom".
¿Cuál es el tipo de TypeScript para el atributo precedence en enlaces de hojas de estilos?
- El atributo
precedencese tipifica comostringen<link rel="stylesheet"> - Es una extensión específica de React no presente en tipificaciones HTML estándar
- Cualquier valor de string funciona; las opciones comunes son
"reset","default", e"high"
Relacionado
- Metadata -- Renderización de etiquetas title, meta y link en componentes
- Server Components -- Precargando recursos durante renderizado del servidor
- Overview -- Lista completa de características de React 19