SEO de despliegue para SvelteKit: adaptadores, prerenderizado y renderizado perimetral

El adaptador de SvelteKit y las opciones de prerenderizado por ruta deciden dónde y cuándo se renderizan las páginas, y eso determina el TTFB, el LCP y el presupuesto de rastreo. Un análisis a fondo centrado en el despliegue: cómo elegir entre adapter-static/node/vercel/cloudflare/netlify, prerender = true/false/'auto', las restricciones del entorno de ejecución perimetral y cómo crear sitemap.xml y robots.txt.

Publicado por primera vez: 3 jul 2026 · Última actualización: 14 ago 2026 · Avanzado
Idiomas

El adaptador de SvelteKit y la opción prerender por ruta deciden dónde y cuándo se renderiza una página (HTML estático durante la compilación, SSR en un servidor o SSR en el perímetro), y esa decisión determina el TTFB, que a su vez influye en el LCP y la capacidad de rastreo. adapter-static resulta adecuado para sitios puramente de contenido; un adaptador node/vercel/cloudflare con prerenderizado por ruta, para sitios mixtos de contenido y aplicación; y un adaptador perimetral, cuando importe el TTFB global, con las limitaciones de los arranques en frío y la ausencia de fs de Node. prerender = 'auto' es la herramienta para sitios mixtos. Los entornos de ejecución perimetrales no pueden leer el sistema de archivos. SvelteKit no genera sitemap.xml ni robots.txt: deben crearse como endpoints +server.js, con una estrategia dependiente del adaptador.

TL;DR — El adaptador no cambia qué renderiza SvelteKit: cambia dónde y cuándo. Estático en tiempo de compilación (adapter-static), en tiempo de petición en un servidor propio (adapter-node) o en tiempo de petición en funciones serverless/edge (adapter-vercel/-netlify/-cloudflare). El prerender = true por ruta genera HTML estático y elimina la ruta del manifiesto dinámico; prerender = 'auto' prerenderiza y la mantiene en el manifiesto: la herramienta para sitios mixtos con /blog/[slug]. Los runtimes edge se ejecutan sobre aislados de V8: sin el fs de Node, y los arranques en frío perjudican al TTFB, que alimenta el LCP y (según el documento de presupuesto de rastreo de Google) la capacidad de rastreo. SvelteKit no genera sitemap.xml ni robots.txt: hay que crearlos como endpoints +server.js, y conviene señalar que la estrategia depende del adaptador. Este es un complemento más acotado y centrado en el despliegue del artículo de fundamentos de SEO en SvelteKit de esta sección; se presupone que SvelteKit hace SSR por defecto y no se repite aquí esa explicación.

La idea que hace que todo esto encaje

El adaptador no cambia lo que se renderiza. Cambia dónde y cuándo. Eso es todo. La documentación de SvelteKit lo expresa con precisión: los adaptadores “take the built app as input and generate output for deployment.” (traducción: «toman la aplicación compilada como entrada y generan la salida para el despliegue»). Evidence for this claim SvelteKit adapters take the built application as input and generate deployment-specific output. Scope: Deployment output; adapter choice can still constrain supported runtime features. Confidence: high · Verified: SvelteKit: Adapters Los componentes, las funciones load y los metadatos <svelte:head> son idénticos en todos los adaptadores. Lo que difiere es:

  • Cuándo se produce el HTML: en tiempo de compilación (estático/prerenderizado) o en tiempo de petición (SSR en un servidor, en una función serverless o en una función edge).
  • Dónde se produce: en un único servidor de origen, en una función serverless regional o en una red edge cercana al visitante.

Todo lo que sigue es una consecuencia de esos dos ejes.

Por qué las decisiones de despliegue son decisiones de SEO

La cadena es corta y está bien documentada: TTFB → LCP → capacidad de rastreo.

El time to first byte es el tiempo que tarda el alojamiento en empezar a enviar la respuesta. Un archivo prerenderizado servido desde una caché de CDN tiene un TTFB casi nulo. Un servidor que tiene que renderizar la página tiene uno más alto. Una función serverless o edge que arranca en frío puede tener uno mucho más alto en el primer acceso. El TTFB es una entrada directa del Largest Contentful Paint (no se puede pintar lo que no se ha recibido) y el LCP es una señal de Core Web Vitals (Métricas web esenciales).

En el rastreo, Google es más explícito. Según la documentación sobre presupuesto de rastreo: “If the site responds quickly for a while, the limit goes up, meaning more connections can be used to crawl. If the site slows down or responds with server errors, the limit goes down and Google crawls less.” Y la línea de buenas prácticas: “Make your pages efficient to load. If Google can load and render your pages faster, we might be able to read more content from your site.” (traducción: «Si el sitio responde rápido, aumenta la capacidad de rastreo; si se ralentiza o devuelve errores, disminuye. Las páginas eficientes pueden permitir que Google lea más contenido»). Una función edge que arranca en frío y tarda en responder está sujeta a la misma dinámica que un servidor de origen lento.

Una nota de honestidad por delante: Google no publica ninguna guía específica para SvelteKit. No hay ningún documento ni episodio de Search Off the Record que mencione los adaptadores de SvelteKit, prerender = 'auto' o los arranques en frío en el perímetro. Aquí se aplica la guía general de Google sobre renderizado y presupuesto de rastreo a los mecanismos específicos de SvelteKit; no se atribuye a ningún representante una declaración específica sobre SvelteKit. El planteamiento de Google de que “server-side or pre-rendering is still a great idea because it makes your website faster for users and crawlers, and not all bots can run JavaScript” (traducción: «el renderizado del servidor o el prerenderizado sigue siendo recomendable porque acelera el sitio para usuarios y rastreadores, y no todos los bots ejecutan JavaScript») es el anclaje oficial más cercano, y es agnóstico respecto al framework.

Cómo elegir un adaptador según los resultados de SEO

adapter-auto: el valor por defecto sin configuración, y su límite

Los proyectos nuevos de SvelteKit vienen con adapter-auto. Detecta la plataforma (Vercel, Netlify, Cloudflare Pages, Azure, AWS) e instala el adaptador correspondiente en tiempo de compilación. Es un buen punto de partida, pero tiene un límite claro que conviene conocer: adapter-auto no acepta ninguna opción. Cuando se necesita { edge: true }, bindings de Cloudflare, ISR de Vercel o cualquier configuración específica de la plataforma, debe instalarse directamente el adaptador subyacente (adapter-vercel, adapter-cloudflare, etc.). auto debe tratarse como un andamiaje, no como una decisión de producción.

adapter-static: SSG completo, para sitios centrados en el contenido

adapter-static prerenderiza todo el sitio como archivos estáticos durante la compilación. No se ejecuta ningún servidor; un alojamiento sirve HTML plano. Evidence for this claim adapter-static prerenders a SvelteKit site as static files. Scope: Routes must be prerenderable; performance outcomes depend on hosting and page design. Confidence: high · Verified: SvelteKit: Static site generation Para un sitio centrado en el contenido este es el perfil de SEO más sólido disponible: el TTFB más bajo, sin arranques en frío ni procesos que puedan venir abajo. El único requisito es la trampa que se explica con detalle en el artículo de fundamentos: el SSR debe permanecer activado durante la compilación; de lo contrario, se obtienen contenedores vacíos en lugar de HTML renderizado. Aquí basta con señalar esa condición.

El inconveniente es la rigidez. Cualquier cosa que realmente necesite lógica de servidor en cada petición (una búsqueda real, contenido por usuario, gestión de formularios sin un endpoint de terceros) no puede vivir en una compilación puramente estática, que es exactamente para lo que sirven los siguientes adaptadores.

adapter-node: un servidor bajo control propio

adapter-node produce un servidor Node.js independiente que debe operarse y escalarse; el TTFB queda bajo control propio. Es la opción más flexible y la que menos sorpresas de entorno de ejecución depara: APIs completas de Node, incluido fs. Encaja bien cuando ya existe infraestructura, se necesitan bibliotecas de Node que los entornos perimetrales no pueden ejecutar o se buscan tiempos de respuesta predecibles (sin arranques en frío) desde un servidor caliente. La contrapartida es operativa: debe mantenerse un servidor, y su velocidad y disponibilidad determinan la capacidad de rastreo.

adapter-vercel: serverless, edge e ISR

adapter-vercel despliega en las funciones serverless de Vercel por defecto, con varias palancas relevantes para el SEO que se configuran por ruta mediante export const config:

  • runtime: 'edge' traslada esa ruta al runtime edge de Vercel (más abajo se detalla).
  • regions controla dónde se ejecutan las funciones serverless: la proximidad a los usuarios o a la base de datos reduce la latencia.
  • isr activa Incremental Static Regeneration: isr: { expiration: 60 } sirve un recurso estático cacheado y lo regenera pasada esa ventana, ofreciendo “the performance and cost advantages of prerendered content with the flexibility of dynamically rendered content.” (traducción: «las ventajas de rendimiento y costo del contenido prerenderizado con la flexibilidad del contenido dinámico»). ISR es una cuarta vía genuina entre lo puramente estático y el SSR puro, pero atención a la advertencia de la propia documentación: “Using ISR on a route with export const prerender = true will have no effect, since the route is prerendered at build time.” (traducción: «ISR no tiene efecto en una ruta con prerender = true porque ya se prerenderiza durante la compilación»). ISR y prerender son alternativas, no acumulables.

adapter-cloudflare: Workers/Pages, edge global

adapter-cloudflare apunta a Cloudflare Workers y Pages: SSR en una red edge global, a menudo el TTFB más bajo para una audiencia dispersa geográficamente. La restricción importante es el runtime: los Workers se ejecutan sobre aislados de V8, no sobre Node. De la documentación: “You can’t use fs in Cloudflare Workers.” (traducción: «No se puede usar fs en Cloudflare Workers»). Algunas APIs de Node solo funcionan tras el flag de compatibilidad nodejs_compat, e incluso entonces el soporte no es uno a uno. Si estaba leyendo archivos en tiempo de petición (un mapa de redirecciones, un archivo de datos, entradas para imágenes OG personalizadas), ese código necesita replantearse; se trata en la sección sobre el edge más abajo.

(El antiguo adapter-cloudflare-workers está obsoleto; los proyectos nuevos usan adapter-cloudflare, que cubre tanto Workers como Pages. Para instalaciones antiguas, se recomienda migrar.)

adapter-netlify: funciones o Edge Functions (Deno)

adapter-netlify despliega en las funciones basadas en Node de Netlify por defecto, o en Edge Functions basadas en Deno con edge: true. El mismo esquema que Vercel: serverless por defecto con una opción de edge. Una nota específica de SvelteKit: los Netlify Forms requieren que la página del formulario esté prerenderizada para que Netlify pueda detectar el marcado del formulario en el momento del despliegue, un requisito de prerenderizado de esa ruta que se superpone a la elección del adaptador.

La decisión, en una línea cada una

  • Sitio puramente de contenidoadapter-static, con prerenderizado completo.
  • Sitio de contenido con bolsas dinámicasadapter-node/-vercel/-cloudflare, prerender = true en el contenido, false/'auto' en las rutas dinámicas.
  • Aplicación o panel con personalización → SSR en primer lugar (node o edge), con prerenderizado solo el armazón estático (marketing, inicio de sesión).
  • Audiencia global con TTFB crítico → un adaptador edge para las rutas dinámicas, asumiendo las restricciones de las APIs de Node y la realidad de los arranques en frío.

(La pestaña Decision Tree recorre esto como un flujo ramificado.)

Estrategia de prerenderizado para sitios mixtos

Qué hacen realmente true / false / 'auto'

export const prerender es una opción de página por ruta (o por layout), y los tres valores no son un simple encendido/apagado:

  • true: compila la ruta como HTML estático durante la compilación. Y algo crucial: queda “excluded from manifests used for dynamic SSR, making your server (or serverless/edge functions) smaller.” (traducción: «excluida de los manifiestos de SSR dinámico, lo que reduce el servidor o las funciones serverless/perimetrales»). Una vez prerenderizada, la ruta no puede recurrir al renderizado dinámico: es estática, y punto.
  • false: renderiza siempre en cada petición. Sin archivo estático.
  • 'auto': la herramienta para sitios mixtos. Prerenderiza la ruta y la mantiene en el manifiesto dinámico del servidor, de modo que la misma ruta puede servirse de forma estática para las rutas conocidas y renderizarse en el servidor para el resto. Está pensada exactamente para el caso que describe la documentación: una ruta como /blog/[slug] “where you want to prerender your most recent/popular content but server-render the long tail.” (traducción: «prerenderizar el contenido reciente o popular y renderizar en servidor la cola larga»).

Como las rutas prerenderizadas reducen el bundle del servidor, un sitio mayoritariamente prerenderizado con unas pocas rutas 'auto'/false despliega una función más pequeña, más barata y más rápida: una ganancia de eficiencia independiente del SEO.

Las rutas dinámicas necesitan una función entries

El rastreador de prerenderizado descubre páginas siguiendo enlaces <a> desde sus puntos de entrada. Eso funciona con rutas estáticas, pero una ruta dinámica como /blog/[slug] no tiene ninguna URL fija que el rastreador pueda encontrar. Si nada enlaza a un slug determinado, SvelteKit no sabrá que existe, y se topará con el clásico error de compilación de que las rutas “were marked as prerenderable, but were not prerendered.”

La solución es una función entries explícita (o config.kit.prerender.entries) que enumere los valores del parámetro:

// src/routes/blog/[slug]/+page.server.js
export const prerender = true;

export function entries() {
  return [
    { slug: 'hello-world' },
    { slug: 'sveltekit-deployment-seo' },
  ];
}

En la práctica, esa lista se genera a partir del CMS o del directorio de contenido. Sin ella, el prerenderizado solo cubre los slugs que el rastreador de enlaces encuentre por casualidad.

El patrón /blog/[slug] en la práctica

La configuración canónica de un sitio mixto combina prerender = 'auto' con una función entries que devuelve las entradas recientes y populares. Esas obtienen HTML estático en tiempo de compilación; todo lo que no esté en la lista pasa al SSR bajo demanda. Las entradas nuevas se renderizan dinámicamente hasta que la siguiente compilación las prerenderiza. Es el término medio pragmático entre “prerenderizar las 40 000 entradas en cada compilación” y “renderizar cada entrada en cada petición”.

Restricciones del runtime edge que afectan al SEO

config.runtime = 'edge' se aplica por ruta (en Vercel)

El edge no es un interruptor de todo o nada. En Vercel es una opción de página por ruta:

// +page.server.js or +server.js
export const config = { runtime: 'edge' };

Esto permite llevar al perímetro las rutas almacenables en caché y de mucho tráfico para lograr un TTFB bajo, mientras las rutas dependientes de Node permanecen en el entorno serverless (Node) estándar dentro del mismo despliegue. La combinación debe ser deliberada.

Sin fs, sin APIs arbitrarias de Node

Los runtimes edge (Cloudflare Workers, Vercel Edge Functions, las Edge Functions basadas en Deno de Netlify) no proporcionan el fs de Node. La documentación de Cloudflare: “You can’t use fs in Cloudflare Workers.” La de Vercel: “You can’t use fs in edge functions.” (traducción: «No se puede usar fs en Cloudflare Workers ni en funciones perimetrales»). Ambas apuntan a las mismas dos vías de escape: usar el helper read de $app/server para acceder a los recursos incluidos en el bundle, o “prerender the routes in question” (traducción: «prerenderizar las rutas en cuestión») para que el acceso al archivo se produzca en tiempo de compilación en lugar de en tiempo de petición.

Los casos relacionados con el SEO en los que esto duele: la generación dinámica de imágenes OG que lee una fuente o un archivo de plantilla, los mapas de redirecciones basados en archivos o un endpoint de sitemap que lee contenido del disco. Cualquiera de ellos pasa al read() de $app/server o pasa al prerenderizado/tiempo de compilación. No es un bloqueante: es una restricción del tipo “conviene saberlo antes de elegir el edge”.

Arranques en frío y TTFB: cuándo ayuda el edge y cuándo no

Las funciones edge también arrancan en frío. Una función edge en frío, en su primera petición, puede ser más lenta que un servidor Node caliente, y muchísimo más lenta que un archivo prerenderizado servido desde caché. El edge gana cuando la función se mantiene caliente o cuando se combina con un almacenamiento en caché agresivo, de modo que la mayoría de las peticiones nunca llegan a la función. No es automáticamente la opción más rápida: “desplegar en el edge” no es sinónimo de “más rápido”. Para un sitio de contenido, la salida estática prerenderizada supera al SSR perimetral en TTFB siempre, porque no hay ninguna función que arrancar.

Generar sitemap.xml y robots.txt (SvelteKit no lo hará)

Esta es la laguna que la mayoría de los tutoriales de SvelteKit se saltan y que la mayoría de las auditorías detecta. SvelteKit no genera automáticamente ningún sitemap.xml ni ningún robots.txt, sea cual sea el adaptador y por muchas páginas que se prerendericen. Un sitio totalmente estático con miles de páginas prerenderizadas se publica igualmente sin sitemap salvo que se cree uno.

El patrón de endpoint +server.js

El sitemap idiomático es un endpoint de ruta que devuelve XML con el Content-Type correcto:

// src/routes/sitemap.xml/+server.js
export const prerender = true; // needed on adapter-static

export async function GET() {
  const urls = await getAllUrls(); // from your CMS/content
  const body = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map((u) => `  <url><loc>${u}</loc></url>`).join('\n')}
</urlset>`;

  return new Response(body, {
    headers: { 'Content-Type': 'application/xml' },
  });
}

La estrategia depende del adaptador

Aquí está la parte que une todo el artículo: la estrategia de sitemap es consecuencia de la elección de adaptador.

  • En adapter-static, el endpoint del sitemap necesita export const prerender = true para incluirse en la salida estática: no hay ningún servidor en tiempo de ejecución que lo genere bajo petición. Se cocina en tiempo de compilación, lo que significa que solo está tan actualizado como la última compilación.
  • En un adaptador Node/serverless/edge, ese mismo endpoint puede generar el sitemap dinámicamente en cada petición a partir del CMS o la base de datos: siempre actualizado, sin necesidad de recompilar. (En un adaptador edge, debe recordarse la restricción de fs: las URL se obtienen de una API o de un binding, no de una lectura de disco.)

Así que la pregunta de “¿mi sitemap debería ser estático o dinámico?” no es una decisión aparte: se desprende del adaptador elegido.

robots.txt: archivo estático frente a endpoint

Hay dos opciones. Un robots.txt plano puede colocarse en la carpeta static/ (se sirve en /robots.txt automáticamente), que es la opción más sencilla y suficiente para la mayoría de los sitios. También puede generarse desde un endpoint src/routes/robots.txt/+server.js cuando sea necesario que cambie según el entorno (bloquear a los rastreadores en staging y permitirlos en producción, por ejemplo). En cualquier caso, no deben bloquearse el bundle /_app/ ni el CSS: eso rompe el renderizado para los motores que sí renderizan.

Si llega a esto desde el ángulo más amplio de los frameworks o del SEO en JavaScript, la lógica de “dónde y cuándo ocurre el renderizado” que se ve aquí es la misma que rige el SEO en JavaScript en general, y la pieza de fundamentos de SvelteKit de esta sección cubre los modos de renderizado y los patrones de metadatos sobre los que se construye este artículo.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.