El streaming en Next.js permite que el servidor, en vez de esperar a que todos los datos estén preparador para ser servidos, envíe cada página en trozos (chunks) a medida que la va tiendo listo cada uno. Esto resulta en que el usuario va a tener el contenido relevante más rápido, el proceso de transformar contenido del servidor en una interfaz interactiva del cliente (hidratación) puede empezar antes y la velocidad de carga que el usuario percibe es mucho más rápida.
Índice de contenidos
¿En qué consiste el streaming y cómo lo implementa Next.js?
El streaming en Next.js, es la entrega progresiva de HTML desde el servidor al cliente. Si entramos en términos un poco más técnicos que lo comentado en la introducción, esto mejora el Time to First Byte (TTFB) y el Time to Interactive (TTI).
En Next.js (a partir del App Router, en la versión 13), esto se implementa con características del propio React, ya que con React 18, se implementó el renderizado concurrente y soporte nativo para streaming con el componente <Suspense>
.
- División en segmentos (chunks): streaming encaja al dedillo con el modelo de componentes de React y es que cada componente puede considerarse un chunk. Haciendo que layouts, páginas, rutas y componentes del servidor puedan renderizarse en fragmentos independientes.
- Suspense y fallback:
<Suspense>
permite elegir qué mostrar al usuario mientras se carga un componente o segmento. Este contenido se carga a través de la propfallback
y una vez que el contenido se ha generado, ese fallback se sustituye por el contenido. - Streaming automático: Next.js trae esta opción habilitada por defecto, de manera que cargará primero el layout.tsx, ya que suele ser un componente ligero, de manera que da al usuario ya un aspecto de la página, irá cargando el resto de componentes según los vaya teniendo listo. Así que los que no requieren datos asíncronos suelen mostrarse antes, y los que sí dependen de llamadas a APIs o procesos más lentos aparecen después, reemplazando progresivamente a sus fallbacks.
- Control granular: el desarrollador puede decidir qué parte de la interfaz debe esperar y cual no, de esta manera puedes con
<Suspense>
seleccionar componentes que requieran de obtención de datos por ejemplo.
Modalidades de streaming: automático y manual
Como hemos explicado arriba, el streaming automático, viene por defecto activado en Next.js. Bastaría con crear un archivo loading.tsx
dentro de la carpeta de una ruta específica. Este contenido es el que se mostrará mientras se cargan los datos reales. Next se encarga de envolver la página en un <Suspense>
de React y usará como fallback lo que esté en el loading.tsx.
El enfoque automático está muy bien, porque resuelve problemas como hacer clic en un botón antes de tiempo. No obstante, tiene una limitación: y es que todos los elementos de la página aparecen a la vez aunque algunos estén listos antes que otros.
Para pasar a streaming manual puedes eliminar (si quieres) el loading.js
/loading.tsx
porque ya no quieres un fallback global a nivel de página; en su lugar, envuelves cada tarjeta en su propio <Suspense
> para que puedan aparecer de forma independiente. En este flujo no usas await
para resolver toda la lista completa de una sola vez: en vez de hacer:
const res = await fetch('/api/tools');
const data = await res.json();
primero pides algo ligero (por ejemplo, una lista de ids o metadatos) con await
, y a partir de ahí creas promesas por cada item (sin await
). Esas promesas las pasas a componentes hijos que usan use()
para resolver su promesa individualmente; hasta que se resuelven, cada <Suspense
> muestra su propio Skeleton
o lo que sea que esté en el fallback.
Beneficios clave (UX, rendimiento y SEO)
- Mejor percepción de velocidad: como el navegador mostrará cierta parte de la página o aplicación desde un primer momento, antes de que el back-end responda, el usuario percibe que la carga del sitio es más rápida.
- Menor bloqueo en la hidratación: puede empezar a servir parte del contenido según va llegando, como decíamos antes mejorando el TTBF.
- SEO: Next.js esperará a que se complete la obtención de datos dentro de
generateMetadata
antes de transmitir la interfaz de usuario al cliente. Esto garantiza que ya el primer chunk incluya las etiquetas principales del<head>
(metatags). Además, como el streaming se renderiza en el servidor, no impacta negativamente en el SEO, ya que los crawlers, reciben el contenido ya renderizado. Y, si tienes dudas, siempre puedes buscar expertos que te asesoren con el tema.
Ejemplos prácticos (código)
Vamos a hacer un ejemplo práctico para que veamos más claro cómo funciona esto. Lo voy a dividir en dos partes:
- Streaming automático con
loading.tsx
- Streaming manual con
<Suspense>
yuse()
La estructura de archivos de los dos ejemplos va a ser la misma en este caso, solo que la carga automática tendrá un componente loading.tsx y la manual cargará el componente que de forma en la UI al que se cargará posteriormente.
app/
├─ products/
├─ page.tsx
├─ ProductCard.tsx
└─ ProductList.tsx
el page.tsx podría ser algo así:
// app/products/page.tsx
import ProductList from "./ProductList";
export const metadata = {
title: "Productos",
};
export default function ProductsPage() {
return (
<section>
<h1 className="text-3xl font-bold mb-6 text-gray-800">
Catálogo de productos
</h1>
<ProductList />
</section>
);
}
Streaming automático con loading.tsx
La idea es que con el streaming automático, Next utilice el loading.tsx
solo con que comparta directorio con el archivo page.tsx. Entonces se mostrará el archivo loading.tsx
mientras se espera a que se resuelva el componente productList.tsx
.
Este sería productList.tsx
:
import ProductCard from "./ProductCard";
async function getProducts() {
const res = await fetch("https://fakestoreapi.com/products", {
cache: "no-store", // siempre pedir datos frescos
});
return res.json();
}
export default async function ProductList() {
const products = await getProducts();
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{products.map((product: any) => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
Streaming manual con <Suspense>
y use()
El enfoque anterior (automática) espera a que la página entera esté lista para mostrarlo todo, pero, ¿no sería más interesante mostrar cada producto de manera independiente, si estos ya se han cargado en vez de esperar a todos?
Aquí entra el streaming granular con <Suspense>
, en este caso en el componente ProductList
, solo pedimos los ids de los productos, y no necesitamos esperar por todo, porque va a ser el componente hijo, mediante los ids, el que pida el resto de la información.
ProductList.tsx
(manual)
import { Suspense } from "react";
import ProductCard from "./ProductCard";
import ProductSkeleton from "./ProductSkeleton";
async function getProductIds() {
const res = await fetch("https://fakestoreapi.com/products", {
cache: "no-store",
});
const data = await res.json();
return data.map((p: any) => p.id);
}
export default async function ProductList() {
const ids = await getProductIds();
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{ids.map((id: number) => (
<Suspense key={id} fallback={<ProductSkeleton />}>
<ProductCard id={id} />
</Suspense>
))}
</div>
);
}
Y añadimos la función use()
al componente ProductCard para para resolver las promesas por separado. Con use()
simplificamos la obtención de datos asíncronos, y React maneja automáticamente la suspensión mientras espera los datos:
ProductCard.tsx
import { use } from "react";
async function getProduct(id: number) {
const res = await fetch(`https://fakestoreapi.com/products/${id}`, {
cache: "no-store",
});
return res.json();
}
export default function ProductItem({ id }: { id: number }) {
const product = use(getProduct(id));
return (
<li className="border rounded p-4">
<h2 className="font-semibold">{product.title}</h2>
<p className="text-sm">{product.description}</p>
</li>
);
}

Buenas prácticas para sacar el máximo partido al streaming
Coloca los metadatos importantes en generateMetadata
para que las etiquetas <head>
salgan en el primer chunk y no afecten el SEO.
Crea unos componentes loading.jsx/tsx
para una experiencia coherente y simple; reserva Suspense
para un control más fino usando el streaming manual.
Priorización de contenido: primero haz que se rendericen los bloques más críticos (header, información principal, productos…), y deja elementos secundarios (reviews, recomendaciones) para después.
Cierre
Gracias al soporte de React 18 y las novedades de React 19 con la API use()
, hoy es posible crear experiencias donde el contenido principal llega antes y los datos secundarios se van incorporando sin bloquear la interacción. Esta técnica de streaming en Next viene muy bien para generar una sensación en el usuario de carga dinámica y sin sacrificar el SEO además.
La mejor forma de entender y aprender es experimentarlo: arranca un proyecto con create-next-app
, añade un loading.js
en tus rutas críticas y prueba haz con use()
, <suspense> …
¡Prepara tus proyecto para que tengan un rendimiento web optimo!
Deja una respuesta
Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *