Descubrimiento de recursos, no transferencia: Carga previa de aplicaciones React mediante HTTP/3
Aprende por qué HTTP/3 convierte el descubrimiento tardío de recursos en el verdadero cuello de botella en las aplicaciones React, y cómo preloadModule, preinit, Early Hints y el chunking lo solucionan.
Mejorar un servidor a HTTP/3 hace que los bytes se transfieran más rápido, pero muchas aplicaciones React apenas perciben una mayor velocidad después. La razón habitual es que la parte lenta del proceso nunca fue la transferencia en sí, sino el momento en que el navegador se enteró de que existía un recurso. Esta guía separa esos dos problemas, muestra dónde QUIC realmente ayuda y explica las herramientas que aceleran el proceso de descubrimiento: las API de recursos de React 19, la precarga de módulos basada en intenciones, las indicaciones 103 Early Hints, el SSR por streaming y una estrategia de fragmentación adecuada para transportes multiplexados.
Un flujo que parece correcto pero sigue siendo lento
Imagínese un equipo analizando un panel de control de análisis a través de una conexión 4G. Cada fragmento importante ya está precargado, la estructura de la red parece ordenada, y sin embargo el tiempo hasta que se logra la interacción sigue estando significativamente por debajo del objetivo. Cada fragmento se descarga rápidamente una vez que comienza, por lo que HTTP/3 claramente está cumpliendo con su función.
Si se observa más de cerca, el patrón cambia. Las descargas son rápidas, pero las solicitudes comienzan tarde. React debe descargar, iniciar, ejecutar y renderizar hasta llegar a un límite de carga diferida, y solo en ese momento el navegador se da cuenta de que se necesita Dashboard.js. Han pasado cientos de milisegundos antes de que siquiera se solicite el primer byte de ese fragmento. La demora ocurre antes de llegar a la red, en un paso que la mayoría de las listas de verificación de rendimiento nunca mencionan por separado: el descubrimiento de recursos, en contraste con la entrega de recursos.
La entrega y el descubrimiento son dos problemas diferentes
Es útil dividir “cargar un recurso” en dos preguntas:
- Velocidad de transferencia: una vez hecha la solicitud, ¿con qué rapidez viajan los bytes desde el servidor al navegador?
- Tiempo de detección: ¿en qué momento el navegador se da cuenta de que necesita esos bytes en primer lugar?
Un tipo de fuente web referenciado desde una hoja de estilos hace que la diferencia sea evidente. La cadena se ve así:
HTML → CSS → @font-face rule → font request
Por más rápida que sea la conexión, la solicitud de fuente no puede realizarse hasta que llegue el HTML, se haya obtenido y analizado el CSS, y se haya encontrado la regla @font-face para aplicarla al texto renderizado. Eso representa un retraso en el descubrimiento. La etiqueta <link rel="preload"> no acelera la descarga de la fuente ni siquiera un milisegundo; simplemente permite al navegador enviar la solicitud antes. Casi todo lo que se explica en el resto de esta guía es una variante de esa idea.
Qué herramienta responde a qué pregunta
Cada tecnología mencionada abajo está dirigida a una etapa diferente del proceso de carga:
preconnect: ¿con qué orígenes debería comenzar a comunicarse el navegador antes de realizar cualquier solicitud?preload: ¿qué archivo específico encontrará el navegador demasiado tarde por sí solo?
preloadModule y modulepreload: ¿qué fragmentos de módulo ES deben cargarse y compilarse con antelación antes de la ejecución?preinit y preinitModule: ¿qué recursos deben no solo cargarse, sino también aplicarse o ejecutarse temprano?prefetch: ¿qué se necesitará probablemente en la próxima navegación, con baja prioridad?Solo el último punto concierne al transporte. Todo lo demás se refiere al descubrimiento, al timing o a la programación, y esa proporción ofrece una imagen precisa de dónde suelen residir las ventajas restantes.
Dónde falló la multiplexación de HTTP/2
Bajo HTTP/1.1, los navegadores lograban el paralelismo abriendo varias conexiones TCP por host, generalmente limitadas a unas seis, siendo que cada conexión transportaba un recurso a la vez. HTTP/2 reemplazó esto por una única conexión que intercala numerosos flujos:
HTTP/1.1 HTTP/2
──────────────── ────────────────────
TCP conn 1 → JS One connection
TCP conn 2 → CSS ├── Stream A: JS
TCP conn 3 → Font ├── Stream B: CSS
TCP conn 4 → Image ├── Stream C: Font
└── Stream D: Image
Eso fue un verdadero avance, pero HTTP/2 seguía basándose en TCP, y TCP promete una entrega estrictamente ordenada de un único flujo de bytes. No tiene forma de saber si algunos bytes pertenecen al flujo de JavaScript y otros al flujo de fuentes. Si falta un paquete, TCP retiene todo lo que sigue hasta que llegue la retransmisión, incluso cuando el paquete perdido formaba parte del Flujo C y los otros tres flujos no tenían nada que ver con él.
Esto se conoce como bloqueo por cabeza de línea (HOL) en TCP. En redes estables rara vez se nota; en conexiones móviles con pérdidas, este es el motivo principal por el cual el multiplexado de HTTP/2 nunca cumplió plenamente su promesa.
Qué cambios trae QUIC en HTTP/3
HTTP/3 reemplaza TCP por QUIC, que funciona sobre UDP y se encarga él mismo del cifrado:
HTTP/2 HTTP/3
──────── ────────
HTTP/2 HTTP/3
↓ ↓
TCP QUIC
↓ ↓
TLS UDP
↓ ↓
IP IP
Los flujos independientes eliminan el bloqueo HOL a nivel de transporte
Dado que QUIC gestiona los flujos dentro del protocolo de transporte, cada flujo se recupera de forma independiente. Un paquete perdido solo retrasa al flujo al que pertenece:
Stream A ──────────────────── ✓
Stream B ──────────────────── ✓
Stream C ──────── X ─ retry
Stream D ──────────────────── ✓
Los flujos A, B y D siguen fluyendo mientras C espera su retransmisión. El efecto medido es más significativo precisamente donde TCP sufrió más. Un estudio de Catchpoint publicado en julio de 2025, realizado en seis países, indicó que, en conexiones con alta tasa de pérdidas, el tiempo mediano hasta recibir el primer byte disminuyó en un 41.8%. Las pruebas internas en Wix mostraron que la configuración de la conexión fue un 33% más rápida y se observó una mejora del 20% en el p75 LCP. En una conexión de banda ancha estable, la ventaja sobre HTTP/2 se reduce a aproximadamente un 5%. Esa asimetría es en sí misma informativa: las mejoras se concentran donde antes afectaba el bloqueo HOL. Considere estas cifras como instantáneas de dichos estudios y no como garantías para su tráfico; mida usted mismo a sus usuarios.
Menos idas y venidas antes del primer byte
Con HTTP/2 sobre TCP, una nueva conexión requiere el proceso de handshake de TCP y luego una negociación TLS separada, lo que implica dos idas y venidas antes de que fluya cualquier dato de la aplicación. QUIC integra el intercambio TLS 1.3 en su configuración de conexión y completa ambos procesos en una sola ida y vuelta. En el caso de visitantes recurrentes, la reanudación 0-RTT permite que los datos encriptados de las solicitudes viajen junto con el paquete inicial. En una conexión intercontinental de 150 ms, esto ahorra entre 150 y 300 ms en cada conexión nueva. Tenga en cuenta que los datos de 0-RTT pueden ser reproducidos, por lo que los servidores generalmente solo los aceptan para solicitudes idempotentes como la obtención de recursos estáticos.
Conexiones que sobreviven un cambio de red
TCP identifica una conexión mediante la combinación de las direcciones IP de origen y destino junto con los puertos. Cuando un teléfono pasa de Wi-Fi a red celular, su dirección cambia y la conexión TCP se interrumpe. En cambio, QUIC utiliza un ID de conexión opaco, lo que permite que la sesión migre al nuevo camino. Un fragmento de ruta que se está precargando no necesita comenzar de nuevo solo porque el tren del viajero sale de la estación.
¿Hace HTTP/3 redundante la precarga?
No, y comprender por qué es el núcleo de todo este tema. HTTP/3 optimiza cómo se transmiten los recursos; la precarga optimiza cuán pronto se solicitan. Ambos actúan en diferentes etapas del proceso:
Browser
│
│ ← "I don't know I need this yet"
↓
Resource discovery ← preload operates here
│
↓
Request
│
↓
QUIC transport ← HTTP/3 operates here
│
↓
Server
Un protocolo de transporte no puede obtener algo que aún nadie ha solicitado. De hecho, un transporte más rápido hace que el descubrimiento tardío sea aún más evidente. Supongamos que el tiempo de transferencia de un bloque disminuye de 300 ms a 80 ms. Un retraso en el descubrimiento de 400 ms, que antes quedaba parcialmente oculto dentro del tiempo total, ahora constituye la mayor parte de él. El cuello de botella se ha desplazado en lugar de desaparecer.
Por qué React oculta las dependencias del navegador
Los recursos de HTML simples, como los orígenes de <img> y las hojas de estilo de <link>, se encuentran temprano, ya que el analizador del navegador (y su escáner de carga previa especulativa) los detecta al leer el documento. React, que se renderiza en el cliente, introduce una cadena mucho más larga antes de que algunas dependencias se vuelvan visibles:
HTML → main.js → React executes → render → lazy() → discover Dashboard.js → download → render
Con React.lazy(), la importación se realiza después de la ejecución del JavaScript. El navegador no puede saber que existe Dashboard.js hasta que el paquete principal se haya descargado, analizado, compilado y ejecutado, y React haya renderizado lo suficiente como para llegar al componente lazy. En una primera visita desde un teléfono lento, eso puede significar segundos antes de que comience la solicitud del chunk.
const Dashboard = lazy(() => import("./Dashboard"));
// The browser has no idea Dashboard.js exists
// until this renders. And it only renders after
// React has fully bootstrapped.
Suspense mejora el tiempo de espera, no el proceso de descubrimiento
Un error conceptual frecuente es pensar que envolver el componente en Suspense resuelve el problema. Esto no cambia el momento en que se solicita el chunk:
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
Lo que proporciona Suspense es la coordinación: mientras el componente perezoso no se ha cargado, React muestra una alternativa en lugar de bloquear todo el árbol. Esto es valioso para la calidad percibida, pero no constituye un mecanismo para predecir los recursos. La solicitud sigue iniciándose en el mismo momento tardío. HTTP/3 permitirá transmitir los datos de forma eficiente posteriormente, pero no influye en el tiempo que tardó en llegar.
Las API de recursos de React 19 y qué hace realmente cada una
React 19 incluye una serie de funciones en react-dom que permiten a los componentes enviar indicaciones sobre recursos al programador del navegador en el momento exacto de la renderización en que se conoce esa necesidad. No son simplemente envoltorios básicos de etiquetas HTML: React las deduplica y, durante la renderización en servidor, puede emitirlas al encabezado del documento para que el navegador las vea con antelación.
preconnect: calentar un origen
Utilice preconnect cuando esté seguro de que seguirá habiendo una solicitud cruzada de origen pronto. Esto inicia con antelación la resolución DNS, la conexión y el intercambio TLS.
import { preconnect } from "react-dom";
// Call this when you know a cross-origin
// request is coming - not just "might be coming."
preconnect("https://cdn.example.com");
Guárdelo para orígenes con los que definitivamente se comunicará. Cada conexión ya preparada requiere esfuerzo tanto en el cliente como en el servidor, y una que no se utilice simplemente se descarta.
preload: descargar un archivo específico con antelación
preload indica al navegador que comience a descargar un recurso conocido sin ejecutarlo ni aplicarlo. Las fuentes son el caso típico, ya que de lo contrario quedan ocultas tras el análisis del CSS:
import { preload } from "react-dom";
// Font hidden behind CSS - the browser won't
// find this until it processes @font-face.
// Preload surfaces it earlier.
preload("/fonts/inter.woff2", {
as: "font",
crossOrigin: "anonymous",
});
Tenga en cuenta la opción crossOrigin: "anonymous". Las fuentes siempre se solicitan en modo CORS, por lo que un preload de fuentes sin esta opción genera una solicitud que no coincide con la real, y el navegador termina descargando el archivo dos veces.
preloadModule: obtener y compilar un módulo ES
preloadModule tiene la misma finalidad para los módulos ES, pero va un paso más allá: el módulo se descarga, se analiza y se compila, luego se guarda en su mapa de módulos para que pueda ser evaluado en el momento en que un import() lo solicite.
import { preloadModule } from "react-dom";
// Use this for lazy route chunks you know
// are likely to be needed soon.
preloadModule("/assets/Dashboard-abc123.js");
Esto es la opción ideal para los fragmentos de rutas perezosas que probablemente se necesiten pronto.
preinit y preinitModule: obtener y poner en uso
preinit y preinitModule son las variantes más potentes. Ellos obtienen el recurso y además lo hacen efectivo: se inserta y aplica una hoja de estilo, y se ejecuta un script en cuanto llega.
import { preinit } from "react-dom";
// You don't just want this downloaded -
// you want it applied before render.
preinit("/styles/app.css", { as: "style" });
La diferencia entre las dos familias es de suma importancia en CSS. Una hoja de estilo precargada se descarga pero no se aplica. Si esa hoja de estilo es necesaria antes del primer dibujo, aunque se haya descargado antes, el renderizado sigue esperando a que algo la inserte realmente. preinit abarca ambos pasos. En el caso de los scripts, rige la precaución inversa: solo se debe usar preinit para código que sea seguro de ejecutar de inmediato.
Activación de preloadModule según la intención del usuario
Llamar a preloadModule para cada ruta al iniciar el sistema desperdicia ancho de banda. El mejor momento es cuando la intención del usuario se vuelve evidente, lo que generalmente significa que un cursor entra en un enlace de navegación o que el foco del teclado se posa sobre él justo antes del clic.
El componente a continuación conecta todo eso. Muestra un enlace normal para que siga funcionando sin JavaScript, llama a preloadModule tanto en onMouseEnter como en onFocus para que también se beneficien los usuarios que usan teclado, y realiza la navegación real mediante navigate de React Router:
import { preloadModule } from "react-dom";
import { useNavigate } from "react-router-dom";
function NavLink({ to, chunkPath, children }) {
const navigate = useNavigate();
return (
<a
href={to}
onMouseEnter={() => preloadModule(chunkPath)}
onFocus={() => preloadModule(chunkPath)}
onClick={(e) => {
e.preventDefault();
navigate(to);
}}
>
{children}
</a>
);
}
// Usage
<NavLink to="/dashboard" chunkPath="/assets/Dashboard-abc123.js">
Dashboard
</NavLink>
El intervalo entre pasar el cursor y hacer clic suele estar alrededor de 100 a 400 ms. Con HTTP/3, un archivo de tamaño medio puede completarse dentro de ese tiempo: a 10 Mbps, 150 KB tarda aproximadamente 120 ms. Para cuando se realiza el clic, el módulo ya está compilado en el mapa de módulos, el límite de carga diferida se resuelve de inmediato y nunca aparece la solución alternativa Suspense.
Lo que logra el manejador onMouseEnter es algo que ningún protocolo de transporte puede hacer: convierte una señal de intención en conocimiento sobre un recurso antes de que se solicite la navegación. Luego, HTTP/3 gestiona la transferencia de manera eficiente. Cada capa realiza su propio trabajo.
Dos observaciones prácticas. Primero, chunkPath debe ser el nombre del archivo con hash que realmente generó tu herramienta de empaquetado, por lo que en un proyecto real debería provenir del manifiesto de compilación y no ser ingresado a mano. Segundo, los dispositivos táctiles no tienen la funcionalidad de hover, así que considere usar onTouchStart o desencadenantes basados en el viewport si la navegación móvil es importante para usted.
Precarga a nivel de herramienta de empaquetado
Para una precarga masiva y de baja prioridad durante tiempos ociosos, webpack admite un comentario especial dentro de la importación dinámica:
const Dashboard = lazy(
() => import(/* webpackPrefetch: true */ "./Dashboard")
);
Tenga en cuenta que webpackPrefetch genera una indicación <link rel="prefetch">, que el navegador trata como una tarea de baja prioridad y en tiempo ocioso para una navegación futura probable. Se trata de una señal diferente al preload o modulepreload de alta prioridad, que se utiliza para recursos necesarios ahora mismo.
Vite adopta un enfoque más automático y emite enlaces modulepreload por usted. Con webpack necesita un comentario especial o un plugin. La forma nativa en HTML de esta indicación es la siguiente:
<!-- Vite generates these for lazy chunks automatically -->
<link rel="modulepreload" href="/assets/Dashboard-abc123.js">
<link rel="modulepreload" href="/assets/vendor-react-def456.js">
Estrictamente hablando, el HTML generado por Vite contiene enlaces modulepreload para el chunk de entrada y sus importaciones estáticas. En el caso de los chunks importados dinámicamente, la herramienta de tiempo de ejecución de Vite inserta enlaces de preload para sus dependencias en el momento en que se ejecuta import(), de modo que el chunk y sus importaciones se cargan en paralelo en lugar de secuencialmente. Consulte la documentación de compilación de su versión de Vite para conocer el comportamiento exacto.
En comparación con un rel="preload" genérico, modulepreload permite al navegador analizar y compilar el módulo tan pronto como llega, en lugar de esperar hasta el momento de la ejecución. A través de HTTP/3, varios de estos indicios se transmiten por flujos QUIC independientes, por lo que la pérdida de un paquete en el chunk del proveedor no afecta al chunk del panel de control.
De Server Push a 103 Early Hints
HTTP/2 intentó resolver el problema del descubrimiento en el lado del servidor mediante Server Push: el servidor enviaba recursos que el navegador aún no había solicitado. El objetivo de adelantar esta información era acertado, pero su implementación fracasó. El servidor no contaba con una forma fiable de determinar si el navegador ya tenía un recurso en caché, por lo que a menudo enviaba duplicados, consumía ancho de banda innecesariamente y competía por capacidad con recursos que el propio navegador consideraba más urgentes. Chrome finalmente dejó de soportar Server Push.
El modelo que lo reemplazó distribuye las responsabilidades de manera más sensata: el servidor proporciona la información, y el navegador decide qué descargar y cuándo.
103 Early Hints pone esto en práctica. Mientras el servidor aún está generando la respuesta principal, envía un estado provisional 103 con encabezados Link. El navegador puede comenzar a descargar esos recursos de inmediato, y para cuando llegue la respuesta final 200 OK con el HTML, algunos de ellos ya podrían estar listos.
Browser Server
│ │
│──── GET / ─────────────→ │
│ │ (generating HTML...)
│ ←─── 103 Early Hints ─── │
│ Link: </assets/main.js>; rel=modulepreload
│ Link: </assets/vendor.js>; rel=modulepreload
│ │
│ (fetching chunks now...) │ (still generating...)
│ │
│ ←─── 200 OK + HTML ───── │
│ (chunks already downloading or done)
En el momento de escribir esto, NGINX incluye soporte integrado para Early Hints desde la versión 1.29.0 en junio de 2025, y Cloudflare lo ofrece como una opción activable en su panel de control. En Node.js, el objeto de respuesta proporciona writeEarlyHints(), que se puede llamar en un servidor personalizado o middleware antes de enviar la respuesta real:
// In a custom server or middleware
res.writeEarlyHints({
link: [
"</assets/main.js>; rel=modulepreload; as=script",
"</assets/vendor.js>; rel=modulepreload; as=script",
"</assets/Dashboard.js>; rel=modulepreload; as=script",
],
});
// Then proceed with normal response
res.status(200).send(html);
El fragmento utiliza res.status().send() al estilo de Express para la respuesta final. Con un servidor http básico de Node.js, se emplearían en su lugar res.writeHead() y res.end(). Las indicaciones tempranas solo son útiles cuando existe un tiempo real de procesamiento del servidor, como consultas a la base de datos o tareas de renderizado, durante los cuales el navegador permanecería inactivo de otro modo.
Una medición publicada por corewebvitals.io, que utiliza los tiempos de Chrome DevTools, reveló que incluir un archivo CSS crítico en las indicaciones tempranas hacía que el elemento LCP apareciera aproximadamente un 35% más rápido que con una carga previa convencional dentro del HTML. En una aplicación React, la ventaja equivalente consiste en que el archivo principal se descarga mientras el servidor sigue funcionando, en lugar de esperar a que llegue el HTML.
Combinando SSR por streaming, indicaciones tempranas y HTTP/3
El renderizado en servidor con transmisión en tiempo real de React añade otra herramienta más. En lugar de esperar a que toda la página esté lista para enviar la respuesta, el servidor envía el HTML por etapas:
HTML shell → Suspense fallback → more HTML → resolved content → hydration
Durante el renderizado, el servidor sabe qué límites de Suspense están a punto de renderizarse y en qué fragmentos dependen. Esa información puede ser transmitida al navegador a través de Early Hints antes de que comience la transmisión del HTML. Un cronograma simplificado:
0ms ── Browser sends request
── Server starts rendering
1ms ── Server knows Dashboard boundary will render
── Server sends 103 Early Hints: Dashboard.js
── Browser starts fetching Dashboard.js
50ms ── Server streams HTML shell
── Browser starts parsing
120ms── Server streams Dashboard content
── Dashboard.js already downloaded
── Hydration starts immediately
Compare la misma aplicación sin Early Hints, donde el descubrimiento de los elementos espera al cliente:
0ms ── Browser sends request
50ms ── Server streams HTML shell
── Browser starts parsing
── Browser discovers <script> tags
── main.js starts downloading
180ms── React executes
── Hits Dashboard lazy boundary
── Dashboard.js request starts (now)
300ms── Dashboard.js downloads
── Hydration starts
HTTP/3 acorta cada segmento en ambos escenarios. Lo que cambian las indicaciones tempranas es el momento en que comienza la solicitud al panel de control, lo cual representa un ahorro adicional y, con frecuencia, mayor. Si el renderizado de su framework ya está cubierto por otras primitivas, el artículo sobre las primitivas SSR de React 19.2 como Activity y el pre-rendering parcial explica en mayor detalle cómo se generan los límites de transmisión por streaming.
Reconsiderar la granularidad de los bloques para el transporte multiplexado
El consejo de larga data para minimizar la cantidad de solicitudes HTTP proviene del límite de seis conexiones en HTTP/1.1 y del hecho de que el multiplexado en HTTP/2 solo es parcialmente paralelo a través de un flujo TCP. Con flujos QUIC independientes, la cantidad de solicitudes deja de ser el factor determinante que solía ser, por lo que se puede dividir el contenido de forma más agresiva sin tener que asumir las mismas penalizaciones por solicitud.
Una división predeterminada sensata sería la siguiente:
- React y ReactDOM: un chunk dedicado y estable del proveedor. Rara vez cambia, por lo que puede mantenerse en caché durante mucho tiempo.
- Biblioteca Router: su propio chunk, por la misma razón de estabilidad.
- Bibliotecas de terceros complejas como las de gráficos o editores: un chunk por biblioteca, de modo que actualizar una no invalida a las demás.
React.lazy(), de modo que la navegación carga solo lo necesario.La ventaja radica en la precisión del caché. Al editar Dashboard.tsx, se debe vaciar el caché del fragmento correspondiente a esa ruta, mientras que el bundle del proveedor permanece en caché; esto solo es posible con una división detallada. Con HTTP/3, los 8 a 15 fragmentos resultantes se cargan en flujos independientes sin bloqueos entre ellos.
Vite gestiona la mayor parte de esto sin necesidad de configuración. En webpack, splitChunks.cacheGroups expresa la misma política. La configuración a continuación crea un chunk del proveedor de React, un chunk del router y un chunk para gráficos que solo es asíncrono; los valores de priority determinan qué grupo prevalece cuando un módulo cumple con más de una condición, y chunks: "async" evita que el código de gráficos se cargue al inicio:
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
reactVendor: {
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
name: "vendor-react",
chunks: "all",
priority: 40,
},
routerVendor: {
test: /[\\/]node_modules[\\/](react-router|react-router-dom)[\\/]/,
name: "vendor-router",
chunks: "all",
priority: 30,
},
chartsVendor: {
test: /[\\/]node_modules[\\/](recharts|d3)[\\/]/,
name: "vendor-charts",
chunks: "async",
priority: 20,
},
},
},
},
};
Existe una advertencia: los fragmentos muy pequeños generan un sobrecargo adicional en el análisis y compilación de cada archivo en el navegador. Además, no todos los visitantes cuentan con HTTP/3: los firewalls corporativos que bloquean UDP en el puerto 443 son comunes, y esos usuarios deben recurrir a HTTP/2, lo que hace que vuelva a aparecer parte del costo relacionado con la cantidad de solicitudes. Mida los efectos antes de dividir el contenido en fragmentos más pequeños que el nivel de ruta. Por lo general, la granularidad adecuada es por ruta o por biblioteca pesada; no suele serlo por componente. Si utiliza Next.js, la sección sobre los controles de fragmentación de Turbopack aborda los parámetros equivalentes en ese framework.
Por qué cargar todo por adelantado sigue teniendo efectos negativos
El multiplexado permite que varios flujos compartan una conexión, pero no les otorga ancho de banda ilimitado ni los hace igualmente importantes. Veinte indicaciones modulepreload siguen compartiendo un mismo canal; la competencia simplemente se distribuye entre más flujos.
Por lo tanto, la pregunta útil no es “¿qué se podría cargar por adelantado?”, sino “¿qué recurso importante el navegador solo notará demasiado tarde?”. Como punto de partida, considere:
- Fuente crítica:
preload. - Hoja de estilo crítica:
preload, opreinitcuando debe aplicarse antes de la renderización. - Módulo JavaScript crítico:
preloadModule. - Origen importante de CDN o API entre dominios:
preconnect.
preload con fetchpriority="high".preloadModule, activado por la intención del usuario.prefetch con baja prioridad.Considere cada entrada como una opción “a tener en cuenta”, no como una regla estricta. No existe una lista universal de preload, solo recursos que son importantes y se descubren tarde en su aplicación específica.
La misma restricción aplica a fetchpriority. Los navegadores ya priorizan los recursos mediante heurísticas sofisticadas, y marcar todo como high es equivalente a no marcar nada. Solo sobrescriba el valor predeterminado cuando una medición demuestre que el navegador se equivoca.
Cinco preguntas para revisar una estrategia de carga
Al auditar cómo una aplicación React carga sus recursos, responda en orden a las siguientes preguntas:
- ¿En qué momento se descubre este recurso? Si la respuesta honesta es “después de que se ejecuta JavaScript” o “después de que React renderiza”, probablemente haya espacio para hacerlo visible antes.
- ¿En qué momento lo necesita el usuario? Algo puede ser importante sin ser necesario de inmediato. Esa distinción determina si se debe usar
preload(ahora) oprefetch(tiempo ocioso).
preconnect, luego preload o preloadModule, después preinit, seguido de las 103 indicaciones tempranas, y finalmente el SSR por streaming con indicaciones que el servidor obtiene de lo que está renderizando.Cómo se integran las capas
Si se observa en su totalidad, cargar una aplicación moderna de React implica seis capas, cada una con su propio ámbito de acción:
Application intent (React knows which routes and components are needed)
↓
Resource APIs (preconnect / preload / preloadModule / preinit)
↓
Server-side surfacing (103 Early Hints / streaming SSR)
↓
Browser resource scheduler (priority, cache, bandwidth estimation)
↓
HTTP/3 / QUIC transport (independent streams, 0-RTT, connection migration)
↓
Network
El enfoque anterior consistía en enviar recursos al cliente de la manera más intensiva posible: Server Push, precarga de todo y consolidación de paquetes para reducir el número de solicitudes. El enfoque actual consiste en proporcionar al navegador señales más detalladas en cada capa y permitirle que realice las llamadas de programación. HTTP/3 ofrece un transporte capaz de actuar eficientemente sobre esas decisiones. Los Early Hints transmiten información del servidor al navegador antes de que exista el HTML. Las API de recursos de React permiten a la aplicación indicar sus intenciones en el momento en que se resuelve un componente.
Ninguna capa sustituye a otra. HTTP/3 no puede obtener lo que aún no ha sido descubierto. Las indicaciones tempranas son inútiles cuando el servidor no sabe qué rutas se están procesando. Además, preloadModule no puede salvar un paquete monolítico de 2 MB que necesita 800 ms para compilar en un teléfono de gama media.
Puntos clave
El mayor efecto de HTTP/3 en la precarga no es que las transferencias se hayan acelerado; sino que las transferencias más rápidas han hecho que el descubrimiento tardío sea relativamente más costoso. Cuando una transferencia de 400 ms se reduce a 80 ms, el retraso en el descubrimiento que antes quedaba oculto dentro de ella se convierte en el costo dominante, y una estrategia ajustada para el antiguo cuello de botella ahora está enfocándose en lo incorrecto.
- Precargar un recurso porque de lo contrario el navegador lo encontraría demasiado tarde, y no solo porque sea importante. Los recursos importantes que se descubren a tiempo no necesitan indicación alguna, y los menos importantes que se descubren tarde tampoco la merecen.
Suspensemejora lo que ve el usuario mientras espera; no hace que los fragmentos se carguen antes.- Elija la indicación más sencilla que funcione:
preconnectpara orígenes,preloadpara archivos,preloadModulepara módulos, ypreinitcuando algo debe aplicarse o ejecutarse. - Active el precargue de fragmentos por ruta a partir de señales de intención como hover y focus, y permita que las Indicaciones Tempranas y el SSR por streaming muestren lo que el servidor ya conoce.
- Divida por ruta y por bibliotecas pesadas, recuerde la opción de fallback HTTP/2, y mida antes de seguir refinando.
HTTP/3 no hizo obsoleto el precargado. En cambio, dejó más claro para qué sirve.
Lecturas relacionadas
- React 19.2 SSR Primitivas: Activity, cacheSignal y PPR explicados — Aprenda cómo el nuevo componente Activity, cacheSignal y el procesamiento parcial de renderizado de React 19.2 brindan a los desarrolladores un control directo sobre el rendimiento del renderizado en servidor.
- Impulsando pipelines Docling a través de HTTP: desde la configuración del proyecto hasta los chunks indexados — Explore paso a paso la API REST de los pipelines Docling: inicie el servidor, descubra operadores, valide y ejecute un DAG de ingestión, y lea sus datos telemétricos de ejecución.