Next.js

Next.js 16 Partial Prerendering: Landing pages SaaS instantáneas

Aprende a implementar Next.js 16 Partial Prerendering estable para crear landing pages SaaS de carga instantánea que equilibran shells estáticos y datos dinámicos.

By Mohamed DjoudirMay 19, 202612 min read
Compartir:
Next.js 16 Partial Prerendering: Landing pages SaaS instantáneas
#next.js-16-partial-prerendering#cachecomponents#static-shell#suspense#tailwind-css

¿Qué son los Cache Components de Next.js 16?

Los Cache Components representan una arquitectura de renderizado que combina una salida pre-renderizada almacenable en caché con componentes dinámicos y específicos del usuario transmitidos por streaming. Este enfoque continúa la idea del Pre-renderizado Parcial (Partial Prerendering) a través del nuevo modelo de Cache Components.

Para las landing pages de SaaS, esto es importante porque los precios, las llamadas a la acción (CTA) adaptadas a la sesión, la moneda de facturación y los enlaces del panel de control a menudo necesitan el contexto del momento de la solicitud sin obligar a toda la ruta a utilizar el SSR tradicional.

Durante años, los desarrolladores que creaban landing pages de SaaS a menudo debían elegir entre generar todo de forma previa para obtener velocidad (Generación de Sitios Estáticos o SSG) o cargar todo en el servidor (Renderizado del Lado del Servidor o SSR) para obtener un contexto dinámico. Esta elección binaria obligaba a los equipos a recurrir a soluciones complejas con hooks de obtención de datos en el lado del cliente y desfases de diseño (layout shifts).

Con el lanzamiento de Next.js 16, este compromiso arquitectónico se reduce. Al aprovechar las capacidades de streaming de React y la semántica de almacenamiento en caché del App Router, el framework confía en los límites <Suspense> de React para diferir los árboles de componentes dinámicos. Las partes almacenables en caché de la ruta se pueden pre-renderizar en un shell estático, mientras que las partes no cacheadas o dinámicas se difieren detrás de límites de Suspense.

TL;DR Los Cache Components en Next.js 16 te permiten combinar salida pre-renderizada en caché con fragmentos dinámicos transmitidos por streaming. Actívalos con cacheComponents: true y luego usa la directiva use cache para cachear explícitamente páginas, componentes o funciones asíncronas. Con los Cache Components habilitados, la obtención de datos es dinámica por defecto a menos que se opte por el almacenamiento en caché. Usa cacheLife() para definir la frescura, cacheTag() para etiquetar la salida cacheada, updateTag() dentro de Server Actions para una invalidación inmediata del tipo lectura-de-tus-propias-escrituras (read-your-own-writes), y revalidateTag(tag, 'max') para actualizaciones de tipo stale-while-revalidate. La variante starting: de Tailwind CSS v4 puede animar elementos recién insertados utilizando @starting-style nativo, pero no es específica del streaming de Suspense.

Más allá de SSG: Por qué los Cache Components son el nuevo estándar

Históricamente, crear una landing page de alto rendimiento significaba adherirse estrictamente a la Generación de Sitios Estáticos (SSG). Pero las aplicaciones SaaS modernas exigen un contexto dinámico. Es posible que necesites mostrar la moneda de facturación específica de un usuario, destacar un plan corporativo personalizado o alterar una llamada a la acción principal si se detecta una sesión activa. Hacer esto exclusivamente en el cliente conduce a una experiencia de usuario compleja, mientras que hacerlo en el servidor puede ralentizar la respuesta inicial.

El modelo de Cache Components cambia la forma en que asociamos las rutas con las estrategias de renderizado. En lugar de clasificar una página entera como estática o dinámica, el motor de renderizado toma decisiones granulares a nivel de componente. Si un componente depende de datos en el momento de la solicitud, se aísla. El diseño exterior (layout) se sirve con baja latencia, lo que permite que tu aplicación logre un Tiempo hasta el Primer Byte (TTFB) bajo.

Al depender únicamente del SSR tradicional, Next.js debe esperar a la obtención de datos más lenta antes de enviar el primer byte de HTML al navegador, lo que retrasa la carga de fuentes y el descubrimiento de imágenes. Con los Cache Components de Next.js 16, el navegador recibe el shell estático rápidamente, lo que desbloquea el descubrimiento de recursos mientras el servidor procesa la lógica de precios personalizados.

Estrategia de renderizado Carga inicial (TTFB) Impacto en SEO Personalización dinámica Granularidad de caché
SSG tradicional Rápido Excelente Requiere obtención en el cliente A nivel de ruta
SSR tradicional Depende de la obtención más lenta Excelente Nativa A nivel de ruta
Cache Components de Next.js 16 Rápido cuando el shell está pre-renderizado Excelente cuando el contenido crítico está en el shell Streaming A nivel de componente/función

Habilitar cacheComponents en tu landing page de SaaS

Actualizar a Next.js 16 introduce varias mejoras estructurales en la forma en que se coordinan el almacenamiento en caché y el pre-renderizado. En versiones experimentales anteriores, los desarrolladores dependían de la opción experimental.ppr para probar el renderizado híbrido. Esta opción se ha eliminado en favor de un modelo de almacenamiento en caché unificado.

Para habilitar los Cache Components en Next.js 16, debes modificar la configuración de tu framework:

  1. Actualiza a Next.js 16 y React 19.
  2. Elimina todas las opciones experimental.ppr de tu configuración.
  3. Habilita la característica cacheComponents para formalizar el análisis del shell estático.
// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  // Habilita Cache Components y la directiva "use cache"
  cacheComponents: true,
  
  // Recomendado: exigir tipos estrictos y advertencias de React 19
  reactStrictMode: true,
};

export default nextConfig;

Por separado, Next.js 16 renombra la convención middleware.ts a proxy.ts. La funcionalidad sigue siendo similar, pero el nuevo nombre refleja mejor su función en el límite de la red/solicitud. Al adoptar la convención proxy.ts, defines una separación más clara entre tus operaciones de solicitud y la lógica de renderizado de tus componentes. También ten en cuenta que proxy.ts utiliza el entorno de ejecución de Node.js, no el de Edge.

El patrón de shell estático: Arquitectura para un TTFB bajo

El mecanismo central detrás de los Cache Components es el patrón de shell estático. Los componentes fuera del límite dinámico de Suspense se pueden incluir en el shell estático pre-renderizado. Cuando la ruta es apta para el pre-renderizado, el shell exterior se puede servir como una salida pre-renderizada estática mientras las partes dinámicas se transmiten por streaming más tarde.

Para implementar esto de manera efectiva en una landing page de SaaS, debes auditar cuidadosamente dónde invocas funciones dinámicas. Las restricciones oficiales al usar Next.js 16 incluyen:

  1. Restricciones de datos: Las funciones cacheadas no pueden acceder directamente a cookies(), headers() o searchParams; léelos fuera del entorno cacheado y pásalos como argumentos.
  2. Restricciones de renderizado: Por separado, si usas el hook de lado del cliente useSearchParams() sin envolver el componente que lo consume en un límite <Suspense>, toda la ruta optará por el renderizado en el lado del cliente.

Nota: Next.js también proporciona 'use cache: private', que puede acceder a APIs específicas de la solicitud como cookies(), headers() y searchParams dentro de un entorno cacheado. Sin embargo, no se recomienda para producción y los resultados solo se guardan en la caché de la memoria del navegador. Para landing pages de SaaS en producción, el patrón más seguro sigue siendo leer los datos de la solicitud fuera de los entornos normales de 'use cache' y pasar valores serializables a las funciones cacheadas.

Construir el límite de Suspense

A continuación, se muestra cómo construir un shell estático robusto que proteja tu TTFB mientras carga enlaces de paneles personalizados de forma dinámica:

// app/page.tsx
import { Suspense } from 'react';
import { HeroSection } from '@/components/hero';
import { FeatureGrid } from '@/components/features';
import { UserNavigation } from '@/components/user-navigation';
import { NavigationSkeleton } from '@/components/skeletons';

export default function LandingPage() {
  return (
    <main className="flex min-h-screen flex-col">
      {/* SHELL ESTÁTICO: Apto para pre-renderizado */}
      <header className="flex w-full items-center justify-between p-6">
        <div className="text-xl font-bold">SaaS Co</div>
        
        {/* SEGMENTO DINÁMICO: Transmitido por streaming en el momento de la solicitud */}
        <Suspense fallback={<NavigationSkeleton />}>
          <UserNavigation />
        </Suspense>
      </header>

      {/* SHELL ESTÁTICO: Apto para pre-renderizado */}
      <HeroSection />
      <FeatureGrid />
    </main>
  );
}

En esta referencia arquitectónica, el componente UserNavigation es el único segmento que lee datos dinámicos. Debido a que se encuentra dentro de un límite Suspense, la ruta LandingPage compila con éxito un shell estático. El contenido inicial llega al navegador rápidamente, presentando una página de marketing completa mientras el segmento de navegación personalizado se transmite por streaming de forma independiente.

Uso de "use cache" para gestionar precios dinámicos de SaaS

La directiva use cache opta explícitamente por almacenar en caché páginas, componentes o funciones asíncronas. Con cacheComponents: true, la obtención de datos es dinámica por defecto a menos que marques tareas específicas como almacenables en caché.

Al crear una página de precios de SaaS con Cache Components, a menudo extraes datos de planes de un CMS o base de datos externa. Quieres que estos datos formen parte del shell estático, pero también necesitas control sobre su vida útil.

Definir perfiles de caché explícitos

Al aplicar use cache junto con un perfil cacheLife y un cacheTag, puedes definir explícitamente cuánto tiempo sigue siendo válido el componente de precios antes de requerir una revalidación en segundo plano, y asociarlo con una etiqueta para su posterior invalidación.

// components/pricing-tier.tsx
import { cacheLife, cacheTag } from 'next/cache';
import { db } from '@/lib/db';

interface PricingData {
  id: string;
  name: string;
  price: number;
}

async function getPricing(tierId: string): Promise<PricingData | null> {
  // Indica al framework que almacene en caché el resultado de esta Server Function
  'use cache';

  cacheTag(`pricing-${tierId}`);

  // Define explícitamente el comportamiento de la caché
  cacheLife({
    stale: 3600,      // Servir contenido obsoleto durante 1 hora
    revalidate: 86400, // Actualización en segundo plano cada 24 horas
    expire: 604800,   // Expirar por completo después de 1 semana
  });
  
  return db.pricing.findUnique({ where: { id: tierId } });
}

export async function PricingTier({ tierId }: { tierId: string }) {
  const tier = await getPricing(tierId);
  
  if (!tier) return null;
  
  return (
    <div className="rounded-2xl border border-gray-200 p-8 shadow-sm">
      <h3 className="text-lg font-semibold">{tier.name}</h3>
      <p className="mt-4 text-4xl font-bold">${tier.price}</p>
      <button className="mt-6 w-full rounded-md bg-blue-600 px-4 py-2 text-white">
        Subscribe Now
      </button>
    </div>
  );
}

Usar cacheLife("hours") es válido, pero para precios críticos del negocio o contenido de páginas de destino, un objeto explícito puede hacer que la política de almacenamiento en caché sea más clara para tu equipo. Los datos de precios se integran en el shell estático, manteniendo la velocidad de entrega y garantizando al mismo tiempo que los valores de los precios se actualicen de manera sistemática.

Invalidación de caché: Uso eficaz de updateTag y revalidateTag

Incluso con configuraciones de caché robustas, hay momentos en los que debes vaciar la caché manualmente. Un escenario común en SaaS es cuando un administrador actualiza una lista de características del producto y necesita que la landing page refleje el cambio de inmediato.

Es fundamental distinguir entre las APIs de invalidación de caché disponibles en Next.js 16:

  • updateTag es exclusivo de Server Actions y expira de inmediato los datos almacenados en caché. No llames a updateTag() desde Route Handlers o el Proxy.
  • revalidateTag(tag, 'max') es la forma recomendada para stale-while-revalidate.
  • La forma de un solo argumento revalidateTag(tag) es un patrón heredado. Para lectura-de-tus-propias-escrituras (read-your-own-writes), usa updateTag(tag) dentro de Server Actions. Para un comportamiento de tipo stale-while-revalidate, usa revalidateTag(tag, 'max').

Etiquetar lecturas cacheadas

Primero, asegúrate de que tu lectura cacheada asocie los datos con una etiqueta usando cacheTag():

// lib/features.ts
import { cacheLife, cacheTag } from 'next/cache';
import { db } from '@/lib/db';

export async function getLandingFeatures() {
  'use cache';

  cacheTag('landing-features');
  
  // Se prefiere la configuración explícita frente a cacheLife('hours')
  cacheLife({
    stale: 3600,
    revalidate: 86400,
    expire: 604800,
  });

  return db.features.findMany();
}

Invalidación en segundo plano con revalidateTag

Usa revalidateTag(tag, 'max') en Server Actions o Route Handlers cuando un ligero retraso sea aceptable, lo que activará una actualización stale-while-revalidate en segundo plano.

// app/api/revalidate-products/route.ts
import { revalidateTag } from 'next/cache';

export async function POST() {
  revalidateTag('landing-features', 'max');

  return Response.json({ revalidated: true });
}

Invalidación en primer plano con updateTag

Sin embargo, si estás ejecutando una Server Action y necesitas que el usuario vea el contenido actualizado de inmediato tras una mutación exitosa (lectura-de-tus-propias-escrituras), Next.js 16 utiliza updateTag(tag).

// app/actions.ts
'use server';

import { updateTag } from 'next/cache';
import { db } from '@/lib/db';

export async function updateFeatureFlag(featureId: string, enabled: boolean) {
  // 1. Mutar la base de datos
  await db.features.update({
    where: { id: featureId },
    data: { enabled },
  });

  // 2. Vaciar inmediatamente los datos cacheados bajo demanda
  updateTag('landing-features');
  
  return { success: true };
}

El uso de updateTag obliga a Next.js a descartar los datos almacenados en caché de inmediato. Combinado con los Cache Components, esto garantiza que tus componentes dinámicos reflejen con precisión el estado de la base de datos sin sacrificar el rendimiento del shell estático circundante.

Estilizar los "huecos": Skeletons en Tailwind CSS v4 y @starting-style

Cuando una landing page de SaaS utiliza el streaming de Suspense para entregar fragmentos dinámicos dentro de un shell estático, gestionar la transición visual es crucial. Un cambio de diseño (layout shift) interrumpe la experiencia del usuario de la aplicación.

Históricamente, los desarrolladores recurrían a librerías de animación de JavaScript para gestionar las secuencias de montaje de los componentes. Con Next.js 16 y Tailwind CSS v4, esto no es necesario. La variante starting: de Tailwind CSS v4 es compatible con la característica nativa CSS @starting-style. Esto se aplica en Tailwind CSS v4, que utiliza un modelo basado primero en CSS. Esto es útil para interfaces de usuario transmitidas por streaming o recién insertadas, lo que permite realizar transiciones de elementos a medida que aparecen sin necesidad de JavaScript, aunque se trata de una característica de CSS, no específica de Suspense.

// components/user-profile.tsx
export function UserProfile({ username }: { username: string }) {
  return (
    // La variante starting: gestiona el estado del DOM previo al montaje de forma nativa
    <div className="transition-all duration-500 ease-out starting:scale-95 starting:opacity-0 scale-100 opacity-100">
      <span className="text-sm font-medium text-slate-700">
        Welcome back, {username}
      </span>
    </div>
  );
}

Debido a que la barra de navegación exterior es parte del shell estático, se dibuja rápidamente en la pantalla. Momentos después, el componente UserProfile se resuelve en el servidor y se transmite al navegador por streaming. Tailwind CSS v4 interpreta la variante starting:, lo que activa un efecto de desvanecimiento y escalado a través de transiciones CSS nativas. Este enfoque reduce la carga de JavaScript en el hilo principal, mejorando el rendimiento.

Errores comunes

Al adoptar los Cache Components de Next.js 16, los equipos de desarrollo a menudo se encuentran con algunos errores estructurales:

  • Leer APIs dinámicas directamente dentro de entornos cacheados → Las funciones cacheadas no pueden acceder directamente a cookies(), headers() o searchParams; léelos fuera del entorno cacheado y pásalos como argumentos. El acceso directo provoca un fallo debido a que estas APIs no se pueden serializar.
  • Usar useSearchParams() sin <Suspense> → Opta por el renderizado en el lado del cliente para toda la ruta. Solución: Envuelve el componente específico que consume el hook en un límite <Suspense>.
  • Usar revalidateTag('my-tag') con un solo argumento → Este formato es un patrón heredado. Solución: Para lectura-de-tus-propias-escrituras (read-your-own-writes), usa updateTag('my-tag') dentro de Server Actions. Para un comportamiento de tipo stale-while-revalidate, usa revalidateTag('my-tag', 'max').
  • Depender completamente de cacheLife("hours") basado en cadenas de texto → El uso de cacheLife("hours") es válido, pero un objeto de configuración explícito cacheLife({ stale, revalidate, expire }) puede hacer que la política de almacenamiento en caché sea más clara para tu equipo.
  • Usar middleware.ts para la lógica de enrutamiento → Esta convención de archivos ha sido reemplazada por proxy.ts en la arquitectura de Next.js 16. Solución: Migra la interceptación de rutas basadas en hosts o multi-inquilino (multi-tenant) a la nueva convención de archivo proxy.ts.

FAQ

¿Cómo habilito los Cache Components en Next.js 16?

Habilitas la arquitectura estableciendo cacheComponents: true dentro de tu archivo next.config.ts. Esto reemplaza la antigua ruta de configuración experimental.ppr con el modelo de Cache Components y permite que Next.js identifique la salida pre-renderizada almacenable en caché y los límites dinámicos.

¿Cuál es la diferencia entre los Cache Components y el streaming de Suspense?

El streaming con Suspense es un mecanismo de React que Next.js utiliza junto con los Cache Components para combinar la salida pre-renderizada con fragmentos dinámicos transmitidos por streaming.

¿Mejora este enfoque las Core Web Vitals?

Puede mejorar el TTFB y el LCP cuando el shell estático contiene el contenido crítico de la parte superior de la página (above-the-fold) y las tareas dinámicas se aíslan detrás de límites de Suspense. Debido a que el shell HTML estático se sirve rápidamente, el navegador puede descubrir y comenzar a descargar recursos críticos, como las imágenes principales (hero) y las hojas de estilo principales, mucho antes de que el servidor finalice las operaciones de datos dinámicos.

¿Puedo usar cookies() dentro de un componente cacheado?

No. Las funciones cacheadas no pueden acceder directamente a cookies(), headers() o searchParams; léelos fuera del entorno cacheado y pásalos como argumentos. El acceso directo dentro de un límite use cache lanzará un error. Además, si usas el hook de lado del cliente useSearchParams() sin un límite <Suspense>, la ruta optará por el renderizado en el lado del cliente.

Conclusión

Los Cache Components de Next.js 16 redefinen cómo los equipos crean aplicaciones web optimizadas para la conversión. Al dominar el patrón de shell estático, implementar estratégicamente la directiva use cache e aislar los datos dinámicos de la sesión dentro de límites de Suspense, puedes lograr tiempos de respuesta bajos al mismo tiempo que conservas la personalización crucial de tu SaaS.

El paradigma de elegir exclusivamente entre la velocidad estática y la utilidad dinámica está cambiando. Si buscas lanzar proyectos de alto rendimiento de manera confiable, utilizar plantillas de inicio listas para producción que ya incorporen estos patrones de almacenamiento en caché es una excelente opción. Explora las plantillas premium de Next.js en Aniq UI para comenzar tu próxima landing page de SaaS.

¿Encontró útil este artículo?

Compartir:
world map

Comunidad Global de Usuarios

Únete a miles de desarrolladores en todo el mundo que confían en Aniq-UI para sus proyectos. Nuestras plantillas se utilizan en todo el mundo para crear experiencias web impresionantes.

Contáctanos

Need custom work or reskin? Get in touch with us

Aniq-uiAniq-uiAniq-ui