Skill de Estrategias de Renderizado en Next.js - Una receta de skill de Claude Code para dominar SSR, SSG, ISR, CSR y PPR
Estas recetas de skills están diseñadas para Claude Code, pero también funcionan con otros agentes de código con IA que admitan archivos de skill/instrucciones.
Receta
El contenido completo de SKILL.md que puedes copiar en .claude/skills/nextjs-rendering/SKILL.md:
---
name: nextjs-rendering-strategies
description: "Experiencia profunda en decisiones de SSR, SSG, ISR, CSR y Partial Prerendering para Next.js. Úsalo cuando te pregunten: estrategia de renderizado, SSR vs SSG, estático vs dinámico, PPR, partial prerendering, generateStaticParams, ISR, CSR."
allowed-tools: "Read, Write, Edit, Glob, Grep, Bash(npm:*), Bash(npx:*), Agent"
---
# Estrategias de Renderizado en Next.js
Eres un experto en renderizado de Next.js. Ayuda a los desarrolladores a elegir e implementar la estrategia de renderizado óptima para cada página.
## Diagrama de Flujo de Decisión de Renderizado
Sigue este árbol de decisiones para cada página:
1. **¿El contenido cambia por usuario o por solicitud?**
- No -> Ve al paso 2
- Sí -> Ve al paso 4
2. **¿El contenido cambia en absoluto después del build?**
- No -> **SSG** (Static Site Generation)
- Sí -> Ve al paso 3
3. **¿Con qué frecuencia cambia?**
- Intervalo predecible -> **ISR** (Incremental Static Regeneration)
- Impredecible (impulsado por eventos) -> **ISR con revalidación bajo demanda**
- Tiempo real -> **SSR** o **CSR** con polling
4. **¿La página tiene partes estáticas y dinámicas?**
- Sí -> **PPR** (Partial Prerendering) con Suspense
- No, completamente dinámica -> **SSR** (Server-Side Rendering)
- Solo interactiva en el cliente -> **CSR** (Client-Side Rendering)
## Referencia de Estrategias
### SSG - Static Site Generation
**Cuándo:** El contenido es el mismo para todos los usuarios y no cambia después del build (o cambia muy raramente).
**Ejemplos:** Páginas de marketing, documentación, entradas de blog, páginas legales.
```tsx
// app/about/page.tsx
// Esto es automáticamente estático porque no tiene funciones dinámicas
export default function AboutPage() \{
return <div>Contenido sobre nosotros...</div>;
\}
// Rutas dinámicas con generateStaticParams
// app/blog/[slug]/page.tsx
export async function generateStaticParams() \{
const posts = await getAllPosts();
return posts.map((post) => (\{ slug: post.slug \}));
\}
export default async function BlogPost(\{
params,
\}: \{
params: Promise<\{ slug: string \}>;
\}) \{
const \{ slug \} = await params;
const post = await getPost(slug);
return <Article post=\{post\} />;
\}Rendimiento: El mejor. Servido desde el borde CDN. Cero cómputo del servidor en tiempo de solicitud.
ISR - Incremental Static Regeneration
Cuándo: El contenido cambia periódicamente pero no necesita ser en tiempo real.
Ejemplos: Catálogo de productos, artículos de noticias, páginas de precios, blog con actualizaciones.
// ISR basado en tiempo
// app/products/page.tsx
export const revalidate = 3600; // revalidar cada hora
export default async function ProductsPage() \{
const products = await getProducts();
return <ProductGrid products=\{products\} />;
\}
// ISR bajo demanda vía Server Action
// app/actions.ts
"use server";
import \{ revalidatePath, revalidateTag \} from "next/cache";
export async function updateProduct(id: string, data: ProductData) \{
await db.product.update(\{ where: \{ id \}, data \});
revalidateTag("products"); // invalidar todas las páginas de productos
\}Rendimiento: Casi estático. La primera solicitud después de la revalidación provoca una reconstrucción; las solicitudes posteriores sirven la versión en caché.
SSR - Server-Side Rendering
Cuándo: El contenido es diferente por solicitud (específico del usuario, datos en tiempo real, autenticación).
Ejemplos: Dashboards, perfiles de usuario, carritos de compra, resultados de búsqueda.
// app/dashboard/page.tsx
import \{ cookies \} from "next/headers";
export default async function DashboardPage() \{
// Usar cookies() hace que esta ruta sea dinámica (SSR)
const cookieStore = await cookies();
const session = cookieStore.get("session");
const user = await getUser(session?.value);
const data = await getDashboardData(user.id);
return <Dashboard user=\{user\} data=\{data\} />;
\}Rendimiento: Más lento que el estático. Cada solicitud llega al servidor. Mitígalo con streaming.
CSR - Client-Side Rendering
Cuándo: Contenido altamente interactivo, datos en tiempo real o funciones que no necesitan SEO.
Ejemplos: Paneles de administración, herramientas de visualización de datos, editores colaborativos, mapas.
"use client";
import useSWR from "swr";
export default function LiveDashboard() \{
const \{ data, error, isLoading \} = useSWR(
"/api/metrics",
fetcher,
\{ refreshInterval: 5000 \} // polling cada 5 segundos
);
if (isLoading) return <DashboardSkeleton />;
if (error) return <ErrorMessage error=\{error\} />;
return <MetricsDisplay data=\{data\} />;
\}Rendimiento: Shell inicial más rápido (si se combina con shell SSR), pero el contenido aparece después de que cargue el JS y se completen los fetch.
PPR - Partial Prerendering
Cuándo: Una página tiene contenido estático y dinámico. El shell estático debe ser instantáneo, con las partes dinámicas transmitiéndose.
Ejemplos: Páginas de producto (descripción estática, precio/stock dinámico), feeds sociales (layout estático, contenido dinámico), dashboards (navegación estática, datos dinámicos).
// next.config.ts
const config = \{ experimental: \{ ppr: true \} \};
// app/product/[id]/page.tsx
import \{ Suspense \} from "react";
export default async function ProductPage(\{
params,
\}: \{
params: Promise<\{ id: string \}>;
\}) \{
const \{ id \} = await params;
const product = await getCachedProduct(id); // estático, en caché
return (
<main>
\{/* Shell estático - prerrenderizado en tiempo de compilación */\}
<h1>\{product.name\}</h1>
<p>\{product.description\}</p>
<StaticImages images=\{product.images\} />
\{/* Agujeros dinámicos - transmitidos en tiempo de solicitud */\}
<Suspense fallback=\{<PriceSkeleton />\}>
<LivePrice productId=\{id\} />
</Suspense>
<Suspense fallback=\{<StockSkeleton />\}>
<StockStatus productId=\{id\} />
</Suspense>
<Suspense fallback=\{<ReviewsSkeleton />\}>
<Reviews productId=\{id\} />
</Suspense>
</main>
);
\}Rendimiento: Lo mejor de ambos mundos. Shell estático servido desde CDN al instante, contenido dinámico transmitido.
Compensaciones de Rendimiento
| Estrategia | TTFB | FCP | SEO | Actualización | Coste del servidor |
|---|---|---|---|---|---|
| SSG | El más rápido | El más rápido | Excelente | Solo en tiempo de compilación | Ninguno |
| ISR | Rápido | Rápido | Excelente | Periódica o bajo demanda | Bajo |
| SSR | Moderado | Moderado | Excelente | Tiempo real | Alto |
| CSR | Rápido (shell) | Lento (contenido) | Pobre (sin shell SSR) | Tiempo real | Bajo (solo API) |
| PPR | El más rápido (shell) | Rápido (shell) + streaming | Excelente | Híbrido | Moderado |
Detección Dinámica
Estas funciones y APIs hacen que una ruta sea dinámica (SSR en lugar de SSG):
cookies()- Leer cookiesheaders()- Leer cabeceras de solicitudsearchParams- Usar parámetros de búsquedafetch(url, { cache: "no-store" })- Fetch sin cachéexport const dynamic = "force-dynamic"- Opt-in explícito
Errores Comunes por Estrategia
Errores comunes de SSG
- Olvidar generateStaticParams para rutas dinámicas (provoca SSR bajo demanda)
- Los tiempos de compilación crecen linealmente con el número de páginas (usa generación parcial)
Errores comunes de ISR
- Configurar revalidate demasiado bajo (efectivamente se convierte en SSR)
- No usar tags para revalidación bajo demanda (confiar solo en la basada en tiempo)
Errores comunes de SSR
- Fetch de datos secuenciales que crean cascadas (usa Promise.all o streaming)
- No usar Suspense para transmitir secciones lentas
- Acceder a cookies/headers cuando no es necesario (fuerza que toda la ruta sea dinámica)
Errores comunes de CSR
- Sin estados de carga (pantalla en blanco mientras carga el JS)
- Contenido crítico para SEO renderizado solo en el cliente
- Ignorar error boundaries
Errores comunes de PPR
- Límites Suspense demasiado granulares (demasiados agujeros de streaming)
- Límites Suspense demasiado amplios (secciones dinámicas grandes anulan el beneficio estático)
- No proporcionar fallbacks de skeleton significativos
## Ejemplo Funcional
### Ejemplo 1: El usuario pregunta "¿Esta página debería ser SSR o SSG?"
**Prompt del usuario:** "Tengo una página de detalle de producto en /products/[slug]. La información del producto cambia raramente pero el precio se actualiza varias veces al día."
**Respuesta guiada por el skill:**
La página de producto debería usar **ISR con revalidación bajo demanda** para la información del producto, combinado con **streaming** para el precio dinámico:
```tsx
// app/products/[slug]/page.tsx
import { Suspense } from "react";
export async function generateStaticParams() {
const products = await getTopProducts(100); // Pre-compilar los 100 principales
return products.map((p) => ({ slug: p.slug }));
}
export const revalidate = 86400; // Reconstruir la información del producto diariamente
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const product = await getProduct(slug);
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
<Suspense fallback={<span>Cargando precio...</span>}>
<LivePrice slug={slug} />
</Suspense>
</main>
);
}
Además, una Server Action para revalidación bajo demanda cuando se editen productos en el CMS.
Ejemplo 2: El usuario pregunta "¿Cómo configuro PPR?"
La respuesta guiada por el skill incluiría:
- Habilitar
ppr: trueen next.config.ts - Identificar qué partes de cada página son estáticas vs dinámicas
- Envolver cada sección dinámica en Suspense con un fallback de skeleton
- Mostrar la implementación completa de la página con límites PPR
Profundización
Cómo Funciona el Skill
Este skill le da a Claude un marco de decisión estructurado:
- Diagrama de flujo de decisión - Lógica paso a paso para determinar la estrategia correcta
- Referencia de estrategias - Patrones de implementación completos para cada modo de renderizado
- Compensaciones de rendimiento - Comparación cuantitativa para justificar recomendaciones
- Conciencia de errores comunes - Errores específicos de cada estrategia para advertir de forma proactiva
Personalización
- Añade tus presupuestos de rendimiento (p. ej., "TTFB debe estar por debajo de 200 ms para todas las páginas de marketing")
- Especifica estrategias predeterminadas por grupo de rutas (p. ej., "(marketing) = SSG, (app) = SSR")
- Incluye tus convenciones de monitorización/alertas para el rendimiento de renderizado
Cómo Instalar
mkdir -p .claude/skills/nextjs-rendering
# Pega el contenido de Receta en .claude/skills/nextjs-rendering/SKILL.mdErrores comunes
- Una sola llamada dinámica hace que toda la ruta sea dinámica - Una llamada a
cookies()en un componente profundamente anidado fuerza toda la ruta a SSR. Usa límites Suspense y PPR para aislar las partes dinámicas. - generateStaticParams no garantiza SSG - Si el componente de página usa APIs dinámicas, las páginas generadas seguirán siendo SSR.
- La revalidación ISR es por página, no global - Cada página tiene su propio temporizador de revalidación.
- PPR es experimental - A partir de Next.js 15, PPR requiere el flag
experimental.ppry puede cambiar en versiones futuras.
Alternativas
| Enfoque | Cuándo usarlo |
|---|---|
| Astro | Sitios con mucho contenido y mínima interactividad |
| Remix | SSR completo con carga de datos anidada y mutaciones |
| Gatsby | Generación estática en tiempo de compilación con capa de datos GraphQL |
| Nuxt.js | Equivalente en Vue.js con estrategias de renderizado similares |
Preguntas frecuentes
¿Cuál es la primera pregunta que debes hacer al elegir una estrategia de renderizado para una página?
- ¿El contenido cambia por usuario o por solicitud?
- Si no, la página es candidata para SSG o ISR
- Si sí, considera si la página tiene partes estáticas y dinámicas (PPR) o es completamente dinámica (SSR/CSR)
¿Cuándo deberías usar ISR con revalidación bajo demanda en lugar de ISR basado en tiempo?
- Cuando los cambios de contenido son impredecibles e impulsados por eventos (p. ej., publicación en CMS, actualización de admin)
- El ISR basado en tiempo es mejor cuando el contenido cambia en un intervalo predecible
- El ISR bajo demanda usa
revalidateTagorevalidatePathactivado por una Server Action o webhook
¿Qué APIs y funciones hacen que una ruta de Next.js sea dinámica?
cookies()-- leer cookiesheaders()-- leer cabeceras de solicitudsearchParams-- usar parámetros de búsquedafetch(url, { cache: "no-store" })-- fetch sin cachéexport const dynamic = "force-dynamic"-- opt-in explícito
Trampa: ¿generateStaticParams garantiza que una página se genere de forma estática?
- No. Si el componente de página usa APIs dinámicas (cookies, headers, fetch sin caché), las páginas generadas seguirán siendo SSR
generateStaticParamssolo pre-genera los params; la estrategia de renderizado depende de lo que haga el componente
¿Qué es Partial Prerendering (PPR) y cuándo deberías usarlo?
- PPR combina un shell estático (prerrenderizado en tiempo de compilación, servido desde CDN) con agujeros dinámicos que se transmiten en tiempo de solicitud
- Úsalo cuando una página tiene contenido estático (descripción de producto, layout) y contenido dinámico (precio, stock, datos específicos del usuario)
- Requiere
experimental: { ppr: true }en next.config.ts
¿Cómo tipas la prop params para una página de ruta dinámica en Next.js 15?
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
// ...
}- En Next.js 15+,
paramses unaPromisey debe ser awaited
¿Cuáles son los principales errores comunes de SSR listados en esta página?
- Fetch de datos secuenciales que crean cascadas (usa
Promise.allo streaming) - No usar Suspense para transmitir secciones lentas
- Acceder a
cookies()oheaders()cuando no es necesario, forzando que toda la ruta sea dinámica
¿Por qué CSR es una mala opción para contenido crítico para SEO?
- El contenido CSR solo aparece después de que cargue JavaScript y se complete el fetch del lado del cliente
- Los rastreadores de motores de búsqueda pueden no ejecutar JavaScript o ver un shell vacío
- Usa SSR o SSG para cualquier contenido que necesite ser indexado por motores de búsqueda
Trampa: ¿Qué ocurre si un solo componente profundamente anidado llama a cookies()?
- Toda la ruta se vuelve dinámica (SSR), no solo ese componente
- Para aislar el comportamiento dinámico, usa límites Suspense y PPR para que solo la parte dinámica se transmita
¿Cuál es la compensación de rendimiento entre SSG y SSR?
- SSG: TTFB y FCP más rápidos, servido desde el borde CDN, cero cómputo del servidor en tiempo de solicitud
- SSR: TTFB y FCP moderados, cada solicitud llega al servidor, mayor coste del servidor
- SSG ofrece el mejor rendimiento pero solo funciona para contenido que no cambia por solicitud
¿Cuáles son los errores comunes de PPR a tener en cuenta?
- Límites Suspense demasiado granulares (demasiados agujeros de streaming, sobrecarga)
- Límites Suspense demasiado amplios (secciones dinámicas grandes anulan el beneficio estático)
- No proporcionar fallbacks de skeleton significativos (mala UX durante el streaming)
¿Cómo funciona el alcance de la revalidación ISR -- es global o por página?
- La revalidación ISR es por página, no global
- Cada página tiene su propio temporizador de revalidación independiente
- Configurar
revalidate = 3600en una página no afecta a otras páginas