TechLead

Estrategias de Renderizado

Renderizado estático, dinámico, streaming y partial prerendering explicados

Renderizado en Next.js

Next.js proporciona múltiples estrategias de renderizado para optimizar el rendimiento y la experiencia del usuario. Entender cuándo usar cada estrategia es clave para construir aplicaciones rápidas y escalables.

🎨 Estrategias de Renderizado

SSG - Generación de Sitio Estático (tiempo de build)
SSR - Renderizado del Lado del Servidor (tiempo de petición)
ISR - Regeneración Estática Incremental
CSR - Renderizado del Lado del Cliente
Streaming - Renderizado progresivo
PPR - Partial Prerendering (experimental)

Generación de Sitio Estático (SSG)

SSG construye HTML en tiempo de build, haciéndolo rápido y cacheable para contenido que no cambia por usuario.

// Las páginas se renderizan en tiempo de BUILD
// Mejor para: blogs, páginas de marketing, documentación

// app/posts/page.tsx
export default async function Posts() {
  const posts = await getPosts(); // Obtenido en tiempo de build
  return (
    <ul>
      {posts.map(post => <li key={post.id}>{post.title}</li>)}
    </ul>
  );
}

// Generar páginas estáticas para rutas dinámicas
// app/posts/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getPosts();
  return posts.map(post => ({
    slug: post.slug,
  }));
}

export default async function Post({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);
  return <article>{post.content}</article>;
}

// Esto genera HTML en tiempo de build para cada post

Renderizado del Lado del Servidor (SSR)

SSR renderiza en cada petición, lo cual es mejor para contenido personalizado o que cambia frecuentemente.

// Las páginas se renderizan en CADA PETICIÓN
// Mejor para: contenido personalizado, datos en tiempo real

// Forzar renderizado dinámico
export const dynamic = 'force-dynamic';

// O usar no-store en fetch
async function getUser() {
  const res = await fetch('/api/user', { cache: 'no-store' });
  return res.json();
}

// Usar cookies/headers lo hace dinámico automáticamente
import { cookies, headers } from 'next/headers';

export default async function Dashboard() {
  const cookieStore = await cookies();
  const token = cookieStore.get('auth-token');
  
  const headersList = await headers();
  const userAgent = headersList.get('user-agent');
  
  const user = await getUser(token);
  return <div>Bienvenido, {user.name}</div>;
}

Regeneración Estática Incremental (ISR)

ISR mantiene el rendimiento de páginas estáticas mientras permite actualizaciones periódicas o bajo demanda.

// Páginas estáticas que revalidan periódicamente
// Mejor para: e-commerce, sitios de noticias, contenido actualizado frecuentemente

// Revalidación basada en tiempo
export const revalidate = 60; // Revalidar cada 60 segundos

export default async function Products() {
  const products = await fetch('https://api.example.com/products', {
    next: { revalidate: 60 }, // O por obtención
  }).then(r => r.json());
  
  return <ProductGrid products={products} />;
}

// Revalidación bajo demanda
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';

export async function POST(request: Request) {
  const { path, tag } = await request.json();
  
  if (path) revalidatePath(path);
  if (tag) revalidateTag(tag);
  
  return Response.json({ revalidated: true, now: Date.now() });
}

Streaming con Suspense

Streaming envía partes de la página tan pronto como estén listas, mejorando el tiempo al primer contenido.

import { Suspense } from 'react';

// Transmitir contenido progresivamente
export default function Page() {
  return (
    <div>
      {/* Se renderiza inmediatamente */}
      <header>
        <h1>Dashboard</h1>
      </header>

      {/* Cada sección se transmite independientemente */}
      <div className="grid grid-cols-3 gap-4">
        <Suspense fallback={<CardSkeleton />}>
          <RevenueCard /> {/* 100ms */}
        </Suspense>
        
        <Suspense fallback={<CardSkeleton />}>
          <UsersCard /> {/* 200ms */}
        </Suspense>
        
        <Suspense fallback={<CardSkeleton />}>
          <OrdersCard /> {/* 500ms */}
        </Suspense>
      </div>

      {/* Componente lento no bloquea a otros */}
      <Suspense fallback={<TableSkeleton />}>
        <DataTable /> {/* 2000ms */}
      </Suspense>
    </div>
  );
}

// Cada componente puede ser un Server Component async
async function RevenueCard() {
  const revenue = await getRevenue();
  return <Card>Ingresos: {revenue}</Card>;
}

Loading.tsx para Carga a Nivel de Ruta

Un archivo loading.tsx proporciona un fallback a nivel de ruta mientras cargan datos y componentes.

// app/dashboard/loading.tsx
// Envuelve automáticamente la página en Suspense

export default function Loading() {
  return (
    <div className="animate-pulse">
      <div className="h-8 bg-gray-200 rounded w-1/4 mb-4" />
      <div className="grid grid-cols-3 gap-4">
        <div className="h-32 bg-gray-200 rounded" />
        <div className="h-32 bg-gray-200 rounded" />
        <div className="h-32 bg-gray-200 rounded" />
      </div>
    </div>
  );
}

// Estructura de archivos
app/
├── dashboard/
│   ├── loading.tsx    # Muestra mientras carga la página
│   └── page.tsx       # La página real

Partial Prerendering (PPR)

PPR sirve una capa estática rápidamente y transmite secciones dinámicas bajo petición.

// Experimental: Capa estática + huecos dinámicos
// Habilitar en next.config.js

// next.config.js
const nextConfig = {
  experimental: {
    ppr: true,
  },
};

// app/page.tsx
import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      {/* Capa estática - prerenderizada */}
      <Header />
      <Hero />
      
      {/* Hueco dinámico - renderizado en petición */}
      <Suspense fallback={<CartSkeleton />}>
        <Cart /> {/* Usa cookies/auth */}
      </Suspense>
      
      {/* Contenido estático */}
      <FeaturedProducts />
      <Footer />
    </div>
  );
}

// PPR sirve capa estática instantáneamente desde CDN
// Luego transmite partes dinámicas

📖 Documentación de Renderizado →

Elegir una Estrategia

Caso de Uso Estrategia Ejemplo
Contenido estático SSG Blog, docs
Datos personalizados SSR Dashboard, configuración
Actualizado frecuentemente ISR E-commerce, noticias
Mixto estático/dinámico PPR Landing + carrito
Carga inicial rápida Streaming Dashboards complejos

✅ Mejores Prácticas de Renderizado

  • • Por defecto estático - agregar dinamismo solo cuando sea necesario
  • • Usar límites de Suspense para carga independiente
  • • Elegir ISR para contenido que cambia periódicamente
  • • Usar streaming para fuentes de datos lentas
  • • Agregar loading.tsx para estados de carga a nivel de ruta
  • • Considerar PPR para páginas con contenido mixto estático/dinámico