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
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
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