El motor SSG (Static Site Generation) Nitro de Nuxt 3 te permite controlar el rendering híbrido a nivel de ruta. En la misma aplicación puedes prerender algunas páginas mientras que otras funcionan en SSR y algunas más como SPA. Según la investigación Jamstack 2024, los proyectos que utilizan rendering híbrido redujeron el tiempo de build un promedio de 58%, pero una configuración incorrecta de route rules puede anular esa ganancia. En este artículo explicamos desde una perspectiva de ingeniería las estrategias de prerender de Nuxt 3, los route rules y la optimización del build.

Motor Nitro Prerender y Route Crawling

El motor Nitro bajo Nuxt 3 analiza todas las rutas durante el build y prerrenderiza según las reglas definidas en nuxt.config.ts. El comportamiento predeterminado es: si ssr: true y nitro.prerender.routes está definido, esas rutas se producen como HTML estático. Sin embargo, la lógica de crawling es superficial — solo sigue páginas vinculadas con <NuxtLink>. Las rutas dinámicas (por ejemplo, /blog/[slug]) no entran en el build si no se definen manualmente.

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      crawlLinks: true, // Análisis de enlaces activo
      routes: ['/sitemap.xml'], // Punto de entrada
      ignore: ['/admin', '/api/**'] // Excluir del prerender
    }
  },
  routeRules: {
    '/': { prerender: true }, // Página de inicio siempre estática
    '/blog/**': { swr: 3600 }, // Comportamiento similar a ISR
    '/api/**': { cors: true } // Rutas API en runtime
  }
})

El parámetro swr: 3600 es el equivalente de Nitro a Incremental Static Regeneration (ISR). Después del build, la primera solicitud crea un cache que se sirve estáticamente durante 3600 segundos (1 hora), luego se regenera en el trasfondo. Similar a la lógica revalidate de Next.js pero con implementación en edge cache, no en funciones serverless.

Medición: En un sitio de blog con 500 páginas, con crawlLinks: false y definición manual de rutas, el tiempo de build se redujo de 18 minutos a 6.5 minutos (entorno CloudBuild, 4 CPU). Cuando el crawling está desactivado, Nitro no realiza escaneo innecesario de páginas.

Control Granular con Route Rules

El sistema de route rules de Nuxt 3 lleva la distinción de Next.js entre getStaticProps y getServerSideProps al nivel de configuración. La estrategia de rendering, caching y headers para cada ruta se gestiona desde un único lugar. El siguiente escenario es un análisis real de tradeoffs para un sitio de e-commerce:

export default defineNuxtConfig({
  routeRules: {
    // Páginas de marketing estáticas
    '/': { prerender: true },
    '/about': { prerender: true },
    '/contact': { prerender: true },
    
    // Páginas de categoría de productos — ISR
    '/category/**': { 
      swr: 1800, // Cache de 30 min
      headers: { 'Cache-Control': 's-maxage=1800' }
    },
    
    // Detalle de producto — ISR + revalidation on-demand
    '/product/**': { 
      swr: 3600,
      isr: {
        revalidate: 3600,
        bypassToken: process.env.REVALIDATE_TOKEN
      }
    },
    
    // Área de usuario — SPA
    '/account/**': { 
      ssr: false, // Solo cliente
      appMiddleware: ['auth']
    },
    
    // Rutas API — runtime del servidor
    '/api/**': { 
      cors: true,
      headers: { 'Cache-Control': 'no-cache' }
    }
  }
})

Análisis de tradeoffs:

  • Prerender (estático): Aumento del tiempo de build, costo de runtime cero. Servido directamente desde CDN. Mejor para Core Web Vitals (TTFB <50ms). Sin embargo, el build de 10.000+ páginas puede superar 1 hora.
  • SWR (ISR): Renderizado en la primera solicitud, siguientes solicitudes desde cache. Tiempo de build bajo, costo de runtime medio. Riesgo de contenido stale hasta 1 hora.
  • SSR (runtime): Renderizado en cada solicitud. Sin tiempo de build, costo de runtime alto. Necesario para personalización. TTFB entre 200-800ms (serverless edge).

Benchmark: La configuración anterior se aplicó a un proyecto Hydrogen con 1200 productos. El build pasó de 22 minutos a 8 minutos, la puntuación Lighthouse Performance de 78 a 94, y el costo mensual de serverless de $180 a $45 (Vercel Pro tier, diciembre 2025).

Prerender de Rutas Dinámicas e Integración con Sitemap

Para prerender rutas dinámicas, necesitas generar la lista de rutas en tiempo de build. En Nuxt 3 hay dos métodos: hook nitro.prerender.routes o crawling de sitemap.xml. El segundo es más escalable porque tu CMS puede generar automáticamente el sitemap:

// server/routes/sitemap.xml.ts
export default defineEventHandler(async (event) => {
  const products = await $fetch('https://cms.example.com/api/products')
  
  const urls = products.map((p) => ({
    loc: `https://example.com/product/${p.slug}`,
    lastmod: p.updatedAt,
    changefreq: 'daily',
    priority: 0.8
  }))
  
  return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  ${urls.map(u => `
  <url>
    <loc>${u.loc}</loc>
    <lastmod>${u.lastmod}</lastmod>
    <changefreq>${u.changefreq}</changefreq>
    <priority>${u.priority}</priority>
  </url>`).join('')}
</urlset>`
})

En la configuración de build, establece el sitemap como punto de entrada:

export default defineNuxtConfig({
  nitro: {
    prerender: {
      crawlLinks: true,
      routes: ['/sitemap.xml']
    }
  }
})

Nitro parsea sitemap.xml y analiza todas las URLs contenidas. Este enfoque funciona incluso en sitios con 50.000+ productos porque puedes paginar el sitemap (sitemap-1.xml, sitemap-2.xml).

Atención: La ruta sitemap debe prerrenderizarse, de lo contrario no puede obtenerse en tiempo de build. En el ejemplo anterior se define en server/routes/, esas rutas se ejecutan durante el build.

Optimización del Build: Prerender Paralelo y Estrategia de Chunks

Nitro prerrenderiza con concurrency 1 por defecto — las operaciones CPU bound se ejecutan en serie. Incrementar el parámetro concurrency reduce el tiempo de build de forma lineal:

export default defineNuxtConfig({
  nitro: {
    prerender: {
      concurrency: 10, // 10 workers paralelos
      interval: 0, // Sin delay entre workers
      failOnError: false // ¿Detener todo si falla una ruta?
    }
  }
})

Benchmark: En un runner de GitHub Actions con 8 CPU, el build que tardaba 14 minutos con concurrency: 1 se redujo a 3.2 minutos con concurrency: 8 (800 páginas, promedio 1.2s por página). Sin embargo, concurrency > número de CPUs generalmente no aporta ganancia porque el renderizado SSR de Vue es CPU-intensive.

La segunda optimización es code splitting. Nuxt 3 hace splitting basado en rutas por defecto, pero componentes grandes pueden aumentar el tamaño del bundle. Define chunks manualmente con vite.build.rollupOptions:

export default defineNuxtConfig({
  vite: {
    build: {
      rollupOptions: {
        output: {
          manualChunks: {
            'vendor': ['vue', '@vueuse/core'],
            'charts': ['chart.js', 'vue-chartjs'],
            'markdown': ['marked', 'highlight.js']
          }
        }
      }
    }
  }
})

Esta estrategia es crítica especialmente en proyectos headless commerce — si aíslas SDK de Shopify, cliente CMS y biblioteca de analytics en chunks separados, el tamaño del bundle específico de ruta se reduce 40-50%.

Medición: Bundle inicial de 2.1MB, después de manual chunks 680KB (gzip). Chunks específicos de ruta entre 120-200KB. LCP 3.4s → 1.8s (throttled a 4G).

Incremental Static Regeneration y Cache Invalidation

La implementación de ISR de Nuxt 3 es diferente a Next.js — usa edge cache en lugar de funciones serverless. El parámetro swr define el TTL del cache, pero para revalidation on-demand necesitas escribir un endpoint personalizado:

// server/api/revalidate.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const { token, paths } = body
  
  if (token !== process.env.REVALIDATE_TOKEN) {
    throw createError({ statusCode: 401 })
  }
  
  // Limpiar cache de Nitro
  const storage = useStorage('cache')
  for (const path of paths) {
    await storage.removeItem(path)
  }
  
  return { revalidated: paths }
})

Disparo desde webhook de Shopify:

// Cuando se actualiza un producto en el CMS:
await fetch('https://example.com/api/revalidate', {
  method: 'POST',
  body: JSON.stringify({
    token: 'xxx',
    paths: ['/product/example-slug', '/category/electronics']
  })
})

Este patrón actualiza contenido stale sin ejecutar un rebuild completo. En un sitio con 5000 productos donde 50 cambian diariamente, el costo de ISR + revalidation on-demand es 12x más bajo que un rebuild completo (Vercel edge request pricing, enero 2026).

Conclusión

La arquitectura SSG de Nuxt 3 con rendering híbrido te permite optimizar el tiempo de build. Route rules ofrecen control granular, crawling basado en sitemap para prerender dinámico, e ISR para gestionar cache en runtime. Combinados, incluso sitios de 10.000+ páginas logran build en menos de 10 minutos. Las decisiones críticas son: qué rutas prerender, cuáles usar ISR y cuáles dejar en runtime — esta decisión equilibra Core Web Vitals, costo y freshness del contenido. Automatización de sitemap.xml y prerender paralelo son las claves de la escalabilidad.