Cambiar tema

Optimización de rendimiento con React Server Components: obtención de datos y caché en la práctica

Easton editorial illustration: React Server Component tree on a performance bench transforming a request waterfall into parallel streaming slabs

Si el TTFB de tus páginas RSC sigue entre 300 y 500 ms, probablemente solo estés aprovechando alrededor del 30 % del potencial de rendimiento. Datos reales muestran que una arquitectura de streaming bien configurada puede bajar el TTFB a 45 ms: no es magia, es el resultado de activar de verdad el renderizado en streaming de React Server Components.

El año pasado ayudé a un equipo de e-commerce a optimizar su página de detalle de producto y caí en la misma trampa. Usaban Next.js App Router, pero el TTFB se mantenía estable en unos 380 ms. Tras investigar, descubrimos que los componentes anidados obtenían datos por separado, formando una cascada típica: la info del producto esperaba a las reseñas, las reseñas al precio y el precio a la verificación de stock. Nueve segundos de pantalla en blanco.

Este artículo trata de cómo resolver eso. Compararé 4 enfoques para la cascada, explicaré cuándo usar cada una de las 5 APIs de caché y te daré plantillas de configuración listas para copiar. De 450 ms a 45 ms de TTFB: la diferencia puede estar solo en dónde colocas unos cuantos límites de Suspense.


El problema de cascada: el mayor enemigo del rendimiento RSC

Imagina un escenario real. Abres la ficha de un producto en una tienda online: primero aparece el nombre, tres segundos después el precio y cinco segundos más tarde los comentarios. ¿Experiencia de usuario? Un desastre.

Eso es la cascada. Componentes anidados obtienen datos por separado y se ejecutan en serie, no en paralelo. En React Server Components, la obtención de datos bloquea el renderizado de forma síncrona por defecto: cualquier petición con await detiene el render a menos que uses Suspense.

Dos formas de cascada

Primera: cascada interna en el servidor. En la misma página, el componente padre obtiene datos antes de renderizar los hijos, y cada hijo vuelve a pedir los suyos. Código típico:

// Ejemplo de cascada — código problemático
async function ProductPage({ id: string }) {
  // Primera petición: 1 s
  const product = await db.getProduct(id);

  // Estas peticiones empiezan solo tras renderizar los hijos
  return (
    <div>
      <ProductDetails product={product} />
      <ProductPrice id={id} />      {/* await getPrice(id) interno, 3 s */}
      <ProductReviews id={id} />    {/* await getReviews(id) interno, 5 s */}
    </div>
  );
}

// ProductPrice.tsx
async function ProductPrice({ id }) {
  const price = await getPrice(id);  // Se ejecuta tras el render del padre
  return <span>{price}</span>;
}

¿Tiempo total? Nueve segundos. El usuario mira una pantalla vacía durante nueve segundos.

Segunda: cascada cliente-servidor. Un componente cliente pide al servidor y el servidor consulta la base de datos. Es más difícil de ver; React DevTools Profiler la detecta. Una variante del problema N+1.

Cómo identificar la cascada

Abre React DevTools Profiler y graba una carga de página. Si en la línea de tiempo las peticiones forman escalones — cada una espera a la anterior — tienes cascada.

Otra forma más directa: en la pestaña Network del navegador, mira cuándo se lanzan las peticiones. Si están repartidas en el tiempo en lugar de dispararse juntas, casi seguro que ahí está el problema.

Muchos desarrolladores creen que usar RSC ya da rendimiento automático. No es así. Según el informe de SitePoint de 2026, la mayoría de equipos solo aprovecha alrededor del 30 % del potencial RSC. La causa habitual: no tratar la cascada.


Cuatro soluciones comparadas: de lo bruto a lo elegante

Hay cuatro enfoques habituales para la cascada. De simple a complejo, de bruto a elegante.

Solución 1: obtención paralela con Promise.all

La idea más directa: lanzar todas las peticiones a la vez y esperar con Promise.all.

// Solución 1: Promise.all en paralelo
async function ProductPage({ id: string }) {
  // Lanzar todas las peticiones a la vez
  const [product, price, reviews] = await Promise.all([
    getProduct(id),      // 1 s
    getPrice(id),        // 3 s
    getReviews(id),      // 5 s
  ]);

  return (
    <div>
      <ProductDetails product={product} />
      <ProductPriceDisplay price={price} />
      <ProductReviewsList reviews={reviews} />
    </div>
  );
}

¿Tiempo total? Cinco segundos. Lo marca la petición más lenta.

Ventajas: simple, pocos cambios.

Desventajas: el usuario sigue esperando la petición más lenta para ver algo. Además hay acoplamiento de datos: el padre debe saber qué necesita cada hijo, lo que rompe la independencia de componentes.

Solución 2: aislamiento con límites Suspense

Envuelve las partes que dependen de datos con Suspense para mostrar primero lo crítico.

// Solución 2: límites Suspense
async function ProductPage({ id: string }) {
  const product = await getProduct(id);  // Datos clave primero

  return (
    <div>
      <ProductDetails product={product} />  {/* Visible a 1 s */}
      
      {/* Partes no críticas con Suspense */}
      <Suspense fallback={<PriceSkeleton />}>
        <ProductPrice id={id} />
      </Suspense>
      
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews id={id} />
      </Suspense>
    </div>
  );
}

Experiencia: a 1 s ves el producto, a 3 s el precio y a 5 s las reseñas.

Ventajas: contenido clave primero, mejor percepción.

Desventajas: las peticiones siguen iniciándose en serie. Las de ProductPrice y ProductReviews arrancan tras renderizar el padre, no en paralelo real.

Solución 3: pasar Promise como props

El padre inicia todas las peticiones y pasa cada Promise como prop; el hijo hace await.

// Solución 3: patrón de paso de Promise
async function ProductPage({ id: string }) {
  // Lanzar todo sin await
  const productPromise = getProduct(id);
  const pricePromise = getPrice(id);
  const reviewsPromise = getReviews(id);

  // Solo await de lo crítico
  const product = await productPromise;

  return (
    <div>
      <ProductDetails product={product} />
      
      <Suspense fallback={<PriceSkeleton />}>
        <ProductPrice pricePromise={pricePromise} />
      </Suspense>
      
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews reviewsPromise={reviewsPromise} />
      </Suspense>
    </div>
  );
}

// ProductPrice.tsx — recibe Promise
async function ProductPrice({ pricePromise }) {
  const price = await pricePromise;  // Reutiliza la Promise del padre
  return <span>{price}</span>;
}

Las tres peticiones arrancan a la vez en el padre. Datos clave a 1 s, precio a 3 s, reseñas a 5 s.

Ventajas: peticiones en paralelo, contenido clave primero, datos desacoplados (el hijo solo recibe Promise).

Desventajas: hay que cambiar la interfaz del componente: de id a Promise.

Solución 4: React cache() + preload (recomendada)

React 19 introduce cache(). Con el patrón preload es la solución más elegante.

// Solución 4: React cache() + preload
import { cache } from 'react';

// Envolver la obtención de datos con cache
const getComments = cache(async (postId: string) => {
  return db.getComments(postId);
});

// Exportar preload con propósito explícito
export const preloadComments = (id: string) => {
  void getComments(id);  // Sin await: inicia pero no bloquea
};

// Componente padre
async function PostPage({ postId: string }) {
  preloadComments(postId);  // Precarga comentarios
  
  const post = await getPost(postId);  // Solo await de lo crítico
  
  return (
    <div>
      <PostContent post={post} />
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments postId={postId} />  {/* Sigue usando id; cache reutiliza */}
      </Suspense>
    </div>
  );
}

// Comments.tsx — interfaz sin cambios
async function Comments({ postId }) {
  const comments = await getComments(postId);  // Reutiliza la Promise del preload
  return <CommentList comments={comments} />;
}

Principio: cache() memoiza automáticamente en el mismo ciclo de render. preload dispara la petición sin esperar; el hijo reutiliza la misma Promise al hacer await.

Ventajas:

  • La interfaz del componente no cambia (sigue recibiendo id)
  • Peticiones memoizadas automáticamente, sin acoplamiento de datos
  • Si eliminas el hijo, el preload queda como código muerto fácil de detectar

Desventajas: hay que entender cache() y vigilar acoplamiento oculto (al borrar Comments, quitar también preload).

Comparativa de las cuatro soluciones

SoluciónTiempo totalContenido clave visibleAcoplamientoCoste de cambio
Secuencial9s9sNingunoNinguno
Promise.all5s5sBajo
Suspense5s1sNoBajo
Paso de Promise5s1sDesacopladoMedio
cache() + preload1s1sNoMedio

La elección depende del equipo. Migración rápida: solución 2; proyecto nuevo: solución 4.


Arquitectura de streaming: el secreto del TTFB a 45 ms

El SSR clásico espera todos los datos, renderiza el HTML completo y lo envía de una vez. El TTFB (Time to First Byte) es tiempo de datos más tiempo de render.

Cifras concretas: consulta a BD 400 ms, render 50 ms, TTFB ~450 ms. Casi medio segundo de pantalla en blanco.

450ms → 45ms
Efecto de optimización del TTFB

Cómo RSC Streaming cambia el flujo

El streaming no reduce tanto como cambia el orden en que llega el contenido. La parte estática sale al instante; la dinámica se completa en flujo.

// Ejemplo de arquitectura streaming
export default async function Dashboard() {
  return (
    <Layout>                          {/* Shell estático, sin Suspense */}
      <Nav />                         {/* Render inmediato */}
      <Sidebar />                     {/* Render inmediato */}
      
      <Suspense fallback={<ChartSkeleton />}>
        <DynamicChart />               {/* Datos dinámicos, streaming */}
      </Suspense>
      
      <Suspense fallback={<TableSkeleton />}>
        <DataTable />                  {/* Datos dinámicos, streaming */}
      </Suspense>
    </Layout>
  );
}

Desglose del flujo:

  1. T=0 ms: el shell estático (Layout, Nav, Sidebar) sale desde caché CDN al borde
  2. T=30-50 ms: el navegador renderiza el shell y muestra skeletons
  3. T=200 ms: DynamicChart termina; el contenido de ese Suspense se envía en streaming
  4. T=400 ms: DataTable termina; su contenido se envía en streaming

¿TTFB? Unos 45 ms: el momento en que sale el shell estático.

Papel de PPR (Partial Prerendering)

PPR llegó en Next.js 15; Next.js 16 lo activará por defecto. Pre-renderiza lo estático en CDN y mantiene streaming en lo dinámico.

Configuración:

// next.config.js — Next.js 15
module.exports = {
  experimental: {
    ppr: true,  // Activar PPR
  },
};

// next.config.js — Next.js 16 (preview)
module.exports = {
  experimental: {
    ppr: 'incremental',  // Activación gradual
    cacheComponents: true,  // Nuevo modelo de caché
  },
};

Con PPR, el shell estático (navegación, layout, skeletons) se pre-renderiza en CDN. Al visitar, el CDN devuelve HTML estático al instante y el servidor completa lo dinámico en streaming.

Principios de diseño de límites Suspense

Regla clave: si olvidas marcar bloques streamables con Suspense, React trata toda la app como un bloque gigante.

Buenas prácticas:

  • Estático sin Suspense: navegación, Layout, skeletons sin datos
  • Dinámico con Suspense: componentes que dependen de BD/API
// Ejemplo correcto
export default async function Page() {
  return (
    <>
      <Header />                     {/* Estático, sin envolver */}
      <main>
        <Suspense fallback={<HeroSkeleton />}>
          <HeroSection />             {/* Dinámico, envuelto */}
        </Suspense>
        
        <Suspense fallback={<ContentSkeleton />}>
          <MainContent />             {/* Dinámico, envuelto */}
        </Suspense>
      </main>
      <Footer />                      {/* Estático, sin envolver */}
    </>
  );
}

Ejemplo incorrecto (bloquea toda la página):

// Incorrecto — sin Suspense
export default async function Page() {
  const data = await fetchDashboard();  // await bloquea toda la página
  return (
    <>
      <Header />
      <Dashboard data={data} />
      <Footer />
    </>
  );
}

Sin límites Suspense, toda la página es un solo bloque streamable. El TTFB sigue en 450 ms.

Comparativa de rendimiento

Modo de renderTTFBLCPNotas
SSR clásico~450ms~500msEspera todos los datos
RSC (sin Suspense)~450ms~500msEquivalente al SSR clásico
RSC Streaming~45ms~200msShell estático al instante
RSC + PPR~30ms~150msShell estático en CDN

Fuente: informe SitePoint 2026; cifras reales pueden variar según datos y CDN.


Guía de las cinco APIs de caché

Next.js y React ofrecen cinco mecanismos de caché. Elegir bien multiplica resultados; elegir mal puede duplicar peticiones.

1. fetch cache (la más usada)

Las peticiones fetch en Server Components se memoizan solas. En el mismo ciclo de render, misma URL y parámetros = una sola petición.

// Ejemplo fetch cache
async function ProductCard({ id }) {
  // Caché automática; misma URL no repite petición
  const res = await fetch(`https://api.example.com/products/${id}`, {
    cache: 'force-cache',      // Forzar caché (por defecto)
    next: {
      revalidate: 3600,         // Revalidar cada hora
      tags: ['products'],       // Etiqueta para revalidateTag
    },
  });
  return <Card data={res.json()} />;
}

async function ProductList() {
  // Reutiliza la caché de arriba
  const res = await fetch('https://api.example.com/products', {
    next: { tags: ['products'] },
  });
  return <List data={res.json()} />;
}

Opciones:

  • cache: 'force-cache': priorizar caché (por defecto)
  • cache: 'no-store': petición nueva cada vez
  • next.revalidate: revalidación programada (segundos)
  • next.tags: etiquetas para refresco manual con revalidateTag

2. React cache() (nuevo en React 19)

Cachea resultados de funciones. Ideal para consultas a BD y obtención personalizada.

import { cache } from 'react';

// Envolver consulta a BD
export const getUser = cache(async (id: string) => {
  const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
  return user;
});

// Varios componentes; memoización automática
async function UserProfile({ id }) {
  const user = await getUser(id);
  return <Profile user={user} />;
}

async function UserStats({ id }) {
  const user = await getUser(id);  // Reutiliza el resultado anterior
  return <Stats user={user} />;
}

Nota: cache() solo vale en el mismo ciclo de render. Entre peticiones HTTP usa unstable_cache.

3. unstable_cache (Next.js 14-15)

Caché persistente entre peticiones. Para cálculos costosos y datos compartidos entre páginas.

import { unstable_cache } from 'next/cache';

// Envolver función con caché persistente
export const getPopularProducts = unstable_cache(
  async () => {
    const products = await db.getPopularProducts();
    return products;
  },
  ['popular-products'],           // Clave de caché
  {
    revalidate: 3600,              // Revalidar cada hora
    tags: ['products', 'popular'], // Varias etiquetas
  }
);

// Uso
async function HomePage() {
  const products = await getPopularProducts();
  return <ProductGrid products={products} />;
}

Refresco manual:

import { revalidateTag } from 'next/cache';

// En Server Action o API Route
async function updateProduct() {
  await db.updateProduct();
  await revalidateTag('products');  // Refresca caché con etiqueta products
}

4. use cache (nuevo en Next.js 16)

Directiva de caché a nivel de componente o función. Con ‘use cache’ al inicio, la salida se cachea sola.

// 'use cache' a nivel de función
'use cache';
export async function getRecommendations(userId: string) {
  return db.getRecommendations(userId);
}

// 'use cache' a nivel de componente
'use cache';
export async function CachedFooter() {
  const links = await getFooterLinks();
  return <Footer links={links} />;
}

Casos de uso: componentes muy visitados, contenido estático. Experimental; soporte formal en Next.js 16.

5. revalidatePath / revalidateTag

Formas de invalidar caché manualmente.

import { revalidatePath, revalidateTag } from 'next/cache';

// Por ruta
await revalidatePath('/products');      // Toda la caché de esa ruta
await revalidatePath('/products/[id]', 'page');  // Página concreta

// Por etiqueta
await revalidateTag('products');        // Toda la caché con etiqueta products

Recomendación:

  • Control fino: revalidateTag (recomendado)
  • Refresco masivo: revalidatePath

Comparativa de APIs de caché

APIAlcancePersistenteCaso de usoVersión
fetch cacheUna peticiónConfigurablePeticiones APINext.js 13+
React cache()Un ciclo de renderNoBD, funciones customReact 19
unstable_cacheEntre peticionesCálculo costoso, datos compartidosNext.js 14-15
use cacheFunción/componenteComponentes frecuentesNext.js 16
Cache ComponentsSalida de componenteCon PPRNext.js 16

Plantillas de configuración y errores habituales

Pasemos a configuración copiable.

next.config.js completo

// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    // Next.js 15: activar PPR
    ppr: true,
    
    // Next.js 16: nuevo modelo de caché
    // cacheComponents: true,  // Activar en versión estable
  },
  
  // Rendimiento
  images: {
    formats: ['image/avif', 'image/webp'],
  },
  
  // Optimización de salida
  output: 'standalone',  // Para despliegue Docker
};

export default nextConfig;

Convención para funciones preload

preload puede crear acoplamiento oculto. Al borrar un hijo profundo, el preload puede quedar huérfano.

Buena práctica: comentar encima del preload su propósito y el componente asociado.

// comments.ts
import { cache } from 'react';

const getComments = cache(async (postId: string) => {
  return db.getComments(postId);
});

/**
 * preloadComments: precarga comentarios para el componente Comments
 * Nota: al eliminar Comments, eliminar también esta función preload
 */
export const preloadComments = (id: string) => {
  void getComments(id);
};

export async function Comments({ postId }) {
  const comments = await getComments(postId);
  return <CommentList comments={comments} />;
}

Errores frecuentes

Error 1: olvidar límites Suspense

Síntoma: TTFB sigue en 450 ms, sin efecto streaming.

Causa: sin Suspense, React trata toda la página como un bloque.

Solución: envolver componentes con datos.

// Antes
async function Page() {
  const data = await getData();  // Bloquea toda la página
  return <Dashboard data={data} />;
}

// Después
async function Page() {
  return (
    <Suspense fallback={<DashboardSkeleton />}>
      <Dashboard />
    </Suspense>
  );
}

Error 2: preload sin uso

Síntoma: se lanza la petición pero no se usa; desperdicio.

Causa: borraste el hijo pero dejaste preload.

Solución: borrar preload al borrar el componente, o documentar la relación.

Error 3: conflicto de tags de caché

Síntoma: revalidateTag refresca de más.

Causa: datos distintos comparten la misma etiqueta.

Solución: etiquetas distintas por dominio de negocio.

// Incorrecto
await fetch(url, { next: { tags: ['data'] } });  // Todo con tag 'data'
await revalidateTag('data');  // Refresca toda la caché

// Correcto
await fetch(productsUrl, { next: { tags: ['products'] } });
await fetch(usersUrl, { next: { tags: ['users'] } });
await revalidateTag('products');  // Solo products

Error 4: mezclar fetch cache y React cache

Síntoma: la misma data dispara dos peticiones.

Causa: fetch con ‘no-store’ impide reutilizar React cache().

Solución: fetch con force-cache o por defecto para que React cache pueda reutilizar.

// Incorrecto
const data1 = await fetch(url, { cache: 'no-store' });  // Sin caché
const data2 = await getData();  // React cache no puede reutilizar

// Correcto
const data1 = await fetch(url);  // force-cache por defecto
const data2 = await getData();  // Puede reutilizar

Herramientas de depuración

  1. React DevTools Profiler: graba el render y detecta cascadas
  2. Análisis Next.js: next build --experimental-debug para el build
  3. Chrome DevTools: Network para tiempos de petición; Performance para render

Métricas clave:

  • TTFB: tiempo al primer byte, objetivo < 100 ms
  • LCP: Largest Contentful Paint, objetivo < 2,5 s
  • CLS: Cumulative Layout Shift, objetivo < 0,1

Recomendaciones de migración

De SSR clásico a RSC en streaming:

  1. Identificar cascadas: Profiler y peticiones secuenciales
  2. Añadir Suspense: envolver lo que depende de datos; priorizar ruta crítica
  3. Añadir preload: cache() + preload en componentes profundos
  4. Configurar caché: tags en fetch para invalidación precisa

Migra por fases, no reescribas todo de golpe. Empieza por las páginas más lentas y mide antes de extender.


Resumen

En esencia: detectar cascada, elegir solución, configurar streaming.

La cascada se reconoce fácil: componentes anidados con await secuencial y peticiones en escalera. Cuatro soluciones según contexto: migración rápida con Suspense; proyecto nuevo con React cache() + preload.

La clave del streaming está en dónde pones Suspense. Estático (nav, Layout) sin envolver; dinámico (datos) siempre envuelto. Sin ese límite, la página sigue bloqueada.

El beneficio es medible: TTFB de 450 ms a 45 ms, diez veces. A veces basta con unos Suspense y un preload.

Abre tu proyecto Next.js y busca componentes anidados que piden datos por separado. Si el TTFB supera 300 ms, prueba a envolver lo crítico con Suspense. Tus usuarios lo notarán al instante.


Referencias

Flujo de optimización de React Server Components

Pasos completos desde identificar cascadas hasta configurar arquitectura streaming

⏱️ Estimated time: 60 min

  1. 1

    Step 1: Identificar cascada

    Graba la carga con React DevTools Profiler:

    • Abre Chrome DevTools, pestaña Profiler
    • Graba, recarga la página y espera a que termine
    • Revisa la distribución de peticiones en la línea de tiempo
    • Distribución en escalones = cascada
    • Comprueba si el TTFB supera 300 ms
  2. 2

    Step 2: Elegir solución

    Según tu equipo:

    • Migración rápida: solución 2 (Suspense)
    • Proyecto nuevo: solución 4 (React cache() + preload)
    • Si toleras acoplamiento: solución 1 (Promise.all)
    • Sin cambiar interfaz: solución 4 (recomendada)
  3. 3

    Step 3: Añadir límites Suspense

    Envuelve componentes con datos:

    • Estático sin envolver (nav, Layout)
    • Dinámico siempre envuelto
    • Fallback skeleton adecuado
    • Prioriza la ruta crítica
  4. 4

    Step 4: Configurar React cache() + preload

    Usa la API cache() de React 19:

    • Envuelve funciones de obtención con cache
    • Exporta preload sin await
    • Comenta la relación componente-preload
    • Borra preload al eliminar el componente
  5. 5

    Step 5: Configurar estrategia de caché

    Elige la API adecuada:

    • fetch cache: peticiones API (más común)
    • React cache(): consultas a BD
    • unstable_cache: compartir entre peticiones
    • Añade tags para invalidación precisa
  6. 6

    Step 6: Medir el beneficio

    Valida la optimización:

    • TTFB objetivo: &lt; 100 ms
    • LCP objetivo: &lt; 2,5 s
    • CLS objetivo: &lt; 0,1
    • Compara métricas antes y después

FAQ

¿Qué es la cascada en React Server Components?
Es cuando componentes anidados obtienen datos en serie y el tiempo de carga se suma. El padre espera, renderiza hijos y cada hijo vuelve a pedir datos. El total es la suma de todas las peticiones. React DevTools Profiler muestra una distribución en escalones.
¿Cómo elegir entre las cuatro soluciones de cascada?
Según contexto:

• Promise.all: simple y directo; acoplamiento de datos
• Suspense: contenido clave primero; buena para migración rápida
• Paso de Promise: paralelo total; requiere cambiar props
• React cache() + preload: más elegante; recomendado en proyectos nuevos
¿Dónde colocar los límites Suspense?
Regla clave: estático sin Suspense, dinámico con Suspense. Estático: navegación, Layout, skeletons sin datos. Dinámico: componentes que dependen de BD o API. Sin envolver, toda la página queda bloqueada.
¿En qué se diferencian las cinco APIs de caché?
Casos distintos:

• fetch cache: peticiones API, memoización automática (Next.js 13+)
• React cache(): consultas BD, un ciclo de render (React 19)
• unstable_cache: persistente entre peticiones, cálculo costoso (Next.js 14-15)
• use cache: caché de función/componente (Next.js 16)
• revalidatePath/Tag: invalidación manual
¿Cómo medir la optimización RSC?
Graba con React DevTools Profiler y revisa cascadas. Métricas: TTFB &lt; 100 ms, LCP &lt; 2,5 s, CLS &lt; 0,1. Tras optimizar, el TTFB puede pasar de 450 ms a 45 ms.
¿Qué es PPR (Partial Prerendering)?
Función de Next.js 15; Next.js 16 la activa por defecto. Pre-renderiza lo estático en CDN y mantiene streaming en lo dinámico. El shell estático sale del CDN al instante; TTFB puede bajar a 30 ms. Config: experimental.ppr = true.
¿Errores habituales en configuración de caché?
Cuatro problemas frecuentes:

• Olvidar Suspense: página bloqueada, TTFB sin mejora
• preload huérfano: componente borrado, petición desperdiciada
• Tags conflictivos: revalidateTag invalida de más
• Mezclar fetch cache y React cache: no-store impide reutilizar

15 min de lectura · Publicado el: 13 may 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog