Renderizado Estático vs Dinámico
Entiende cuándo Next.js renderiza rutas en tiempo de compilación (estático) versus tiempo de solicitud (dinámico), y cómo controlarlo.
Busca en todas las páginas de la documentación
Entiende cuándo Next.js renderiza rutas en tiempo de compilación (estático) versus tiempo de solicitud (dinámico), y cómo controlarlo.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tarjeta de referencia rápida -- lista para copiar y pegar.
// Estático de forma predeterminada -- sin funciones dinámicas, sin búsquedas sin caché
export default async function AboutPage() {
return <h1>About Us</h1>; // Construido en tiempo de despliegue
}
// Se vuelve dinámico automáticamente al usar funciones dinámicas
import { cookies } from "next/headers";
export default async function DashboardPage() {
const cookieStore = await cookies(); // Esto opta al renderizado dinámico
return <h1>Dashboard</h1>;
}
// Fuerza estático o dinámico con configuración de segmento
export const dynamic = "force-dynamic"; // Siempre renderiza en tiempo de solicitud
export const dynamic = "force-static"; // Siempre renderiza en tiempo de compilación
// Pre-renderiza rutas dinámicas en tiempo de compilación
export async function generateStaticParams() {
const posts = await fetchAllPosts();
return posts.map((p) => ({ slug: p.slug }));
}Cuándo usarlo: Necesitas entender por qué una ruta es estática o dinámica, o quieres controlar explícitamente el comportamiento de renderizado.
// app/blog/[slug]/page.tsx -- Generación estática con generateStaticParams
import { notFound } from "next/navigation";
type Post = { slug: string; title: string; content: string; date: string };
async function getPost(slug: string): Promise<Post | null> {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { tags: [`post-${slug}`] },
});
if (!res.ok) return null;
return res.json();
}
// Pre-renderiza estas rutas en tiempo de compilación
export async function generateStaticParams() {
const res = await fetch("https://api.example.com/posts");
const posts: Post[] = await res.json();
return posts.map((post) => ({
slug: post.slug,
}));
}
// Genera metadatos para cada publicación
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) return { title: "Not Found" };
return { title: post.title };
}
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return (
<article className="prose max-w-2xl mx-auto p-6">
<h1>{post.title}</h1>
<time className="text-gray-500">{post.date}</time>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}// app/dashboard/page.tsx -- Renderizado dinámico (usa cookies)
import { cookies } from "next/headers";
import { db } from "@/lib/db";
import { verifySession } from "@/lib/auth";
export default async function DashboardPage() {
const cookieStore = await cookies();
const token = cookieStore.get("session")?.value;
const session = await verifySession(token);
if (!session) {
return <p>Please sign in</p>;
}
const stats = await db.userStats.findUnique({
where: { userId: session.userId },
});
return (
<main className="p-6">
<h1 className="text-2xl font-bold">Welcome, {session.user.name}</h1>
<div className="grid grid-cols-3 gap-4 mt-6">
<StatCard label="Orders" value={stats?.orders ?? 0} />
<StatCard label="Revenue" value={`$${stats?.revenue ?? 0}`} />
<StatCard label="Customers" value={stats?.customers ?? 0} />
</div>
</main>
);
}
function StatCard({ label, value }: { label: string; value: string | number }) {
return (
<div className="border rounded p-4">
<p className="text-sm text-gray-500">{label}</p>
<p className="text-2xl font-bold">{value}</p>
</div>
);
}Lo que esto demuestra:
generateStaticParamscookies()cookies(), headers(), searchParams)cache: "no-store" en ninguna búsquedadynamic = "force-dynamic"cookies(), headers(), searchParams (la propiedad page), useSearchParams() (sin Suspense), y connection().generateStaticParams le dice a Next.js qué segmentos de ruta dinámica pre-renderizar en tiempo de compilación. Las rutas no devueltas por esta función se renderizam bajo demanda (y se almacenan en caché) o devuelven 404, dependiendo de dynamicParams.revalidate para reconstruir páginas estáticas en segundo plano después de un intervalo de tiempo. La primera solicitud después del intervalo obtiene la página obsoleta; las solicitudes posteriores obtienen la versión nueva.Controlando el comportamiento para rutas no en generateStaticParams:
// Permite renderizado de rutas no pre-generadas (comportamiento predeterminado)
export const dynamicParams = true;
// Devuelve 404 para rutas no pre-generadas
export const dynamicParams = false;Revalidación a nivel de segmento:
// Revalida todas las páginas bajo este layout cada 60 segundos
export const revalidate = 60;
// Nunca revalida (completamente estático hasta el próximo despliegue)
export const revalidate = false;Mezclando estático y dinámico en la misma página con Suspense:
import { Suspense } from "react";
import { cookies } from "next/headers";
// El shell estático se renderiza en tiempo de compilación
export default function Page() {
return (
<main>
<h1>Product Page</h1> {/* Estático */}
<StaticProductInfo /> {/* Estático */}
<Suspense fallback={<p>Loading cart...</p>}>
<DynamicCartPreview /> {/* Dinámico -- llama a cookies() */}
</Suspense>
</main>
);
}
async function DynamicCartPreview() {
const cookieStore = await cookies();
const cartId = cookieStore.get("cart-id")?.value;
// ...
}// Tipo de retorno de generateStaticParams
export async function generateStaticParams(): Promise<{ slug: string }[]> {
// ...
}
// Para rutas dinámicas anidadas
export async function generateStaticParams(): Promise<{
category: string;
slug: string;
}[]> {
// ...
}
// Tipos de configuración de segmento
export const dynamic: "auto" | "force-dynamic" | "force-static" | "error" = "auto";
export const dynamicParams: boolean = true;
export const revalidate: number | false = false;
export const runtime: "nodejs" | "edge" = "nodejs";Una única llamada a cookies() hace que toda la ruta sea dinámica -- Incluso si el 99% de la página es contenido estático, una función dinámica opta toda la ruta al renderizado dinámico. Solución: Usa Partial Prerendering (PPR) o aísla la parte dinámica en un boundary de Suspense.
generateStaticParams se ejecuta solo en tiempo de compilación -- El contenido nuevo agregado después de la compilación no se pre-renderiza hasta la próxima compilación o a menos que dynamicParams sea true (que renderiza bajo demanda). Solución: Mantén dynamicParams: true (el predeterminado) para que las nuevas rutas se rendericen en la primera solicitud y luego se almacenen en caché.
force-static con funciones dinámicas lanza un error -- Si exportas dynamic = "force-static" pero la página llama a cookies(), la compilación falla. Solución: Elimina la función dinámica o cambia a dynamic = "auto".
revalidate: 0 no significa "revalidar inmediatamente" -- Significa "siempre dinámico." Solución: Usa un número positivo para el comportamiento de ISR.
Páginas estáticas con búsqueda de datos del lado del cliente -- Una página puede generarse estáticamente pero aún así obtener datos en el cliente con SWR o React Query. El HTML estático sirve como el shell. Solución: Este es un patrón válido, no un error -- solo sé consciente del estado inicial vacío.
| Enfoque | Úsalo Cuando | No lo Uses Cuando |
|---|---|---|
| Generación Estática (predeterminada) | El contenido cambia con poca frecuencia, se puede compilar en tiempo de despliegue | Los datos son específicos del usuario o en tiempo real |
ISR (revalidate: N) | El contenido cambia periódicamente, quieres velocidad de CDN con frescura | Los datos deben estar frescos en cada solicitud |
| Renderizado Dinámico | Los datos son específicos del usuario, usa cookies o headers | El contenido es igual para todos los usuarios |
| Partial Prerendering (PPR) | Quieres un shell estático con agujeros dinámicos | La página completa es completamente estática o completamente dinámica |
| Búsqueda del lado del cliente | Necesitas actualizaciones en tiempo real después de que carga la página | Los datos se pueden obtener completamente en el servidor |
force-static | Quieres garantizar que una ruta nunca sea dinámica | La ruta genuinamente necesita datos en tiempo de solicitud |
Una ruta es estática de forma predeterminada. Se vuelve dinámica si:
cookies(), headers(), searchParams, connection())cache: "no-store" en una búsquedadynamic = "force-dynamic"generateStaticParams le dice a Next.js qué segmentos de ruta dinámica pre-renderizar en tiempo de compilación.dynamicParams es true) o devuelven 404.revalidate: N): las páginas se reconstruyen en segundo plano después de N segundos. La primera solicitud después del intervalo obtiene la página obsoleta; las solicitudes posteriores obtienen la versión nueva.<Suspense> para que solo esa porción sea dinámica.La compilación fallará con un error. No puedes forzar una ruta a ser estática cuando usa funciones dinámicas. Elimina la función dinámica o cambia a dynamic = "auto".
Significa siempre renderiza dinámicamente (equivalente a force-dynamic). No significa "revalidar inmediatamente." Usa un número positivo (por ejemplo, revalidate: 60) para el comportamiento de ISR.
Sí. Una página puede generarse estáticamente pero aún así obtener datos en el cliente usando SWR o React Query. El HTML estático sirve como el shell, y los datos del lado del cliente se rellenan después de la hidratación.
// Permite renderizado bajo demanda para rutas desconocidas (predeterminado)
export const dynamicParams = true;
// Devuelve 404 para rutas desconocidas
export const dynamicParams = false;export async function generateStaticParams(): Promise<
{ slug: string }[]
> {
const posts = await fetchAllPosts();
return posts.map((p) => ({ slug: p.slug }));
}Para rutas dinámicas anidadas, incluye todos los parámetros de segmento en el tipo de retorno.
export const dynamic: "auto" | "force-dynamic" | "force-static" | "error" = "auto";
export const dynamicParams: boolean = true;
export const revalidate: number | false = false;
export const runtime: "nodejs" | "edge" = "nodejs";Envuelve la parte dinámica en un boundary de <Suspense>. El shell estático se renderiza en tiempo de compilación, y el componente dinámico (por ejemplo, uno que llama a cookies()) hace streaming en tiempo de solicitud.
Revisado por Chris St. John·Última actualización: 7 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥