tutoriales.com

SvelteKit y la Generación de Sitios Estáticos (SSG): Despliegue Rápido y Eficiente 🚀

Este tutorial profundiza en la Generación de Sitios Estáticos (SSG) utilizando SvelteKit, una potente herramienta para construir aplicaciones web. Exploraremos cómo configurar tu proyecto para SSG, las ventajas que ofrece y los pasos para desplegar tu sitio estático de forma eficiente. Prepárate para sitios web ultrarrápidos y seguros.

Intermedio20 min de lectura19 views
Reportar error

Introducción a la Generación de Sitios Estáticos (SSG) con SvelteKit ✨

En el mundo del desarrollo web moderno, la velocidad y la eficiencia son cruciales. La Generación de Sitios Estáticos (SSG, por sus siglas en inglés) ha emergido como una estrategia poderosa para lograr precisamente eso. A diferencia del Server-Side Rendering (SSR), donde cada solicitud renderiza una página en el servidor, o el Client-Side Rendering (CSR), donde el navegador renderiza todo, SSG implica la pre-renderización de tus páginas en tiempo de compilación. Esto significa que cuando un usuario solicita una página, ya está lista y servida como archivos HTML, CSS y JavaScript estáticos, directamente desde una CDN.

SvelteKit, el framework de Svelte para construir aplicaciones web robustas, ofrece un soporte excepcional para SSG, permitiéndote aprovechar sus beneficios de rendimiento, seguridad y facilidad de despliegue. Es una opción fantástica para sitios que no requieren datos en tiempo real para cada visita, como blogs, sitios de documentación, portfolios y páginas de marketing.

💡 Consejo: Piensa en SSG como construir tu casa entera de antemano y luego simplemente entregar las llaves al inquilino. Es mucho más rápido que construirla cada vez que alguien quiere entrar.

¿Por qué elegir SSG? Una Visión Rápida 🚀

Antes de sumergirnos en la implementación, es vital entender las ventajas de SSG:

  • Rendimiento Superior: Al servir archivos estáticos, no hay necesidad de procesamiento en el servidor en tiempo de ejecución. Esto reduce drásticamente el tiempo de primera carga (TTFB) y mejora la métrica Largest Contentful Paint (LCP), lo que se traduce en una experiencia de usuario más rápida y fluida.
  • Seguridad Mejorada: Menos superficie de ataque. Al no tener un servidor de aplicación dinámico en producción, se eliminan muchas vulnerabilidades potenciales asociadas con bases de datos, APIs y lógica de servidor en tiempo real.
  • Costos de Hosting Reducidos: Los archivos estáticos son baratos de almacenar y servir. Puedes alojarlos en servicios de almacenamiento de objetos (como S3) o CDN por una fracción del costo de un servidor de aplicaciones tradicional.
  • Facilidad de Despliegue: Los sitios estáticos son increíblemente sencillos de desplegar. Solo necesitas subir los archivos generados a un servidor web o CDN.
  • Optimización SEO Inherente: Los motores de búsqueda indexan el contenido pre-renderizado de manera más eficiente, ya que el HTML está completamente formado desde el principio.
🔥 Importante: Aunque SSG ofrece muchas ventajas, no es una solución universal. No es adecuado para aplicaciones que requieren actualizaciones de datos muy frecuentes o personalización por usuario a gran escala en tiempo real. Para esos casos, SSR o CSR pueden ser más apropiados.

Configurando SvelteKit para SSG 🛠️

Configurar tu proyecto SvelteKit para generar un sitio estático es un proceso relativamente sencillo, gracias a los adaptadores de SvelteKit. El adaptador clave para SSG es @sveltejs/adapter-static.

1. Inicializar un Proyecto SvelteKit (Si Aún No lo Has Hecho)

Si ya tienes un proyecto SvelteKit, puedes saltarte este paso. Si no, crea uno nuevo:

npm create svelte@latest my-static-app
cd my-static-app
npm install

Durante la creación, se te harán algunas preguntas. Para este tutorial, puedes elegir las opciones predeterminadas o las que prefieras.

2. Instalar el Adaptador adapter-static

Este es el corazón de la configuración SSG. Abre tu terminal en la raíz de tu proyecto SvelteKit e instala el adaptador:

npm i -D @sveltejs/adapter-static

3. Configurar svelte.config.js

Ahora, necesitas decirle a SvelteKit que use este adaptador. Abre el archivo svelte.config.js en la raíz de tu proyecto y modifícalo para que use adapter-static:

import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/kit/vite';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	// Consult https://kit.svelte.dev/docs/integrations#preprocessors
	// for more information about preprocessors
	preprocess: vitePreprocess(),

	skit: {
		// adapter-static needs to know which adapter to use.
		// See https://kit.svelte.dev/docs/adapters for more information about adapters.
		adapter: adapter({
			// default options are shown. On some platforms
			// these options are set automatically — see https://kit.svelte.dev/docs/adapters#pre-rendering
			pages: 'build',
			assets: 'build',
			fallback: undefined,
			precompress: false,
			strict: true
		})
	}
};

export default config;

Expliquemos las opciones clave dentro del adapter():

  • pages: Directorio donde se emitirán los archivos HTML pre-renderizados. Por defecto es build.
  • assets: Directorio donde se emitirán los assets estáticos (CSS, JS, imágenes). Por defecto es build.
  • fallback: Esta es una opción crucial para sitios estáticos. Si tu sitio usa enrutamiento client-side (SPA) para rutas no pre-renderizadas, puedes especificar un archivo fallback (ej. 200.html o 404.html). Si lo dejas undefined, SvelteKit generará solo las páginas que puede pre-renderizar completamente, lo que es ideal para sitios puramente estáticos. Para blogs o sitios de documentación, undefined suele ser suficiente.
  • precompress: Si se establece en true, el adaptador generará versiones .br (Brotli) y .gz (Gzip) de tus archivos estáticos, lo que puede reducir el tamaño de transferencia y mejorar la velocidad de carga.
  • strict: Si es true, el adaptador fallará la compilación si encuentra alguna ruta que no puede ser completamente pre-renderizada estáticamente (por ejemplo, si usa load sin prerender: true en una página). Es una buena práctica mantenerlo en true para asegurar un sitio puramente estático.

4. Marcar Páginas para Prerenderizado 📌

Por defecto, SvelteKit no pre-renderiza todas las páginas. Debes indicar explícitamente qué páginas deben ser generadas estáticamente. Hay varias formas de hacer esto:

A. Prerenderizar Todo el Sitio (Globalmente)

Si sabes que todo tu sitio será estático, puedes configurar el prerenderizado globalmente en svelte.config.js:

// svelte.config.js

import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/kit/vite';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	preprocess: vitePreprocess(),
	skit: {
		adapter: adapter({
			pages: 'build',
			assets: 'build',
			fallback: undefined
		}),
		prerender: { default: true } // <-- Añade esto para prerenderizar todo por defecto
	}
};

export default config;

Con prerender: { default: true }, SvelteKit intentará prerenderizar todas las rutas que pueda. Si alguna ruta no puede ser pre-renderizada (por ejemplo, si su load depende de una API de servidor en tiempo real sin un equivalente estático), recibirás un error si strict está en true.

B. Prerenderizar Páginas Individualmente

Para un control más granular, puedes especificar export const prerender = true; en el <script> de una página o layout específico:

<!-- src/routes/+page.svelte -->
<script>
	export const prerender = true;
</script>

<h1>¡Hola desde mi página estática!</h1>
<p>Este contenido ha sido pre-renderizado.</p>

O en el <script context="module"> de un +layout.ts o +page.ts (si estás usando TypeScript):

// src/routes/+page.ts

export const prerender = true;

// Si tienes una función load, asegúrate de que sea compatible con SSG
export async function load() {
	// fetch datos desde una API estática o un archivo local
	const res = await fetch('https://api.example.com/posts');
	const posts = await res.json();

	return { posts };
}
📌 Nota: Cuando `prerender: true` se usa en un `+layout.svelte` o `+layout.ts`, afectará a todas las páginas anidadas bajo ese layout.

C. Prerenderizado de Rutas Dinámicas (con entries) 💡

Para rutas dinámicas, como /blog/[slug], SvelteKit necesita saber qué slugs (o parámetros) debe pre-renderizar. Esto se hace usando la opción entries en la función load del archivo +page.ts (o +server.ts para endpoints de datos) que maneja la ruta dinámica.

Imaginemos que tienes una ruta src/routes/blog/[slug]/+page.svelte.

Necesitarás un src/routes/blog/[slug]/+page.ts para decirle a SvelteKit qué slugs generar:

// src/routes/blog/[slug]/+page.ts

import type { EntryGenerator } from './$types';

export const prerender = true; // Asegúrate de que el prerender esté activado

// Esta función le dice a SvelteKit qué rutas dinámicas generar.
export const entries: EntryGenerator = async () => {
	// Aquí, deberías obtener tus slugs de alguna fuente de datos,
	// como una API, un sistema de archivos o una base de datos.
	// Por ejemplo, de un array de posts:
	const posts = [
		{ slug: 'mi-primer-post', title: 'Mi Primer Post' },
		{ slug: 'un-segundo-articulo', title: 'Un Segundo Artículo' }
	];

	return posts.map((post) => ({ slug: post.slug }));

	// Si no tienes slugs dinámicos, podrías retornar un array vacío
	// return [];
};

export async function load({ params }) {
	// En tiempo de prerenderizado, params.slug será uno de los slugs generados por 'entries'.
	// Aquí, puedes cargar los datos específicos para ese slug.
	const response = await fetch(`https://api.example.com/posts/${params.slug}`);
	const post = await response.json();

	if (response.ok) {
		return { post };
	} else {
		// SvelteKit renderizará un error 404 si la data no se encuentra
		return { status: response.status, error: new Error('Post no encontrado') };
	}
}
⚠️ Advertencia: La función `entries` **solo se ejecuta en tiempo de compilación**. Asegúrate de que las fuentes de datos que consulta sean accesibles durante la compilación. No puede depender de un servidor de API que solo esté disponible en tiempo de ejecución.
Inicio Leer svelte.config.js ¿prerender: default: true? No ¿prerender: true en página/layout? ¿Ruta dinámica? Ejecutar entries() Generar HTML slugs No Generar HTML No Saltar prerender Final

Construyendo y Desplegando tu Sitio Estático 🚀

Una vez que tu proyecto está configurado para SSG y has marcado las páginas que deben prerenderizarse, el siguiente paso es construir tu aplicación y luego desplegarla.

1. Construir la Aplicación

Para generar los archivos estáticos, simplemente ejecuta el comando de construcción de SvelteKit:

npm run build

SvelteKit ejecutará un proceso de compilación que incluirá el prerenderizado. Los archivos estáticos resultantes se colocarán en el directorio que especificaste en tu svelte.config.js (por defecto, la carpeta build).

Después de una construcción exitosa, verás una estructura de directorios similar a esta dentro de build:

build/
├── index.html
├── blog/
│   ├── mi-primer-post/
│   │   └── index.html
│   └── un-segundo-articulo/
│       └── index.html
├── _app/
│   ├── assets/
│   │   └── ... (archivos CSS, imágenes)
│   └── ... (archivos JavaScript de la aplicación)
└── favicon.png

Cada página prerenderizada tendrá su propio archivo index.html dentro de un directorio con el nombre de la ruta, lo que es ideal para hosting en servidores web o CDNs.

2. Desplegar el Sitio Estático

La belleza de los sitios estáticos es que son increíblemente fáciles de desplegar. Puedes simplemente subir el contenido de tu carpeta build a cualquier servicio de hosting que sirva archivos estáticos. Aquí hay algunas opciones populares:

A. Hosting de Objetos y CDN (AWS S3 + CloudFront, Google Cloud Storage, Azure Blob Storage)

Esta es la forma más escalable y de alto rendimiento. Subes los archivos de tu carpeta build a un bucket de almacenamiento de objetos y luego configuras una CDN (Content Delivery Network) para servir esos archivos. La CDN almacena en caché tu sitio en ubicaciones geográficas diversas, lo que garantiza una entrega ultrarrápida a usuarios de todo el mundo.

💡 Consejo: Para S3, después de subir los archivos, configura el bucket para "Static Website Hosting" y asegúrate de que el "Index document" sea `index.html` y el "Error document" sea `404.html` (si lo tienes).

B. Servicios de Hosting Estático (Netlify, Vercel, Cloudflare Pages, GitHub Pages)

Estos servicios están diseñados específicamente para el despliegue de sitios estáticos y ofrecen una integración fantástica con repositorios Git. Simplemente conectas tu repositorio, y cada vez que hagas un push a una rama específica, el servicio automáticamente construirá (ejecutará npm run build) y desplegará tu sitio.

Ejemplo con Vercel/Netlify:

  1. Crea un nuevo proyecto y conecta tu repositorio de GitHub/GitLab/Bitbucket.
  2. Cuando se te pida la configuración de construcción, la mayoría de las veces detectará SvelteKit automáticamente.
    • Comando de construcción: npm run build
    • Directorio de salida: build (o el que hayas configurado en svelte.config.js)
  3. Haz push de tus cambios y observa cómo se despliega tu sitio.

C. Servidor Web Tradicional (Apache, Nginx)

Si tienes tu propio servidor web, simplemente copia el contenido de la carpeta build al directorio raíz de tu servidor (ej. /var/www/html para Apache o la ubicación de tu root en Nginx). Asegúrate de configurar tu servidor para servir index.html para directorios y manejar correctamente las rutas 404.


Consideraciones Avanzadas y Trucos 🧠

Generación de Sitemap y RSS Feed

Para sitios estáticos como blogs, generar un sitemap.xml y un rss.xml es crucial para SEO y para que tus suscriptores estén al día. Puedes crear endpoints de SvelteKit (src/routes/sitemap.xml/+server.ts y src/routes/rss.xml/+server.ts) que generen estos archivos en tiempo de compilación. Asegúrate de marcar estos endpoints con export const prerender = true;.

Ejemplo de src/routes/sitemap.xml/+server.ts:

import type { RequestHandler } from './$types';
import { posts } from '$lib/posts'; // Asumiendo que tus posts están en $lib/posts.js

export const prerender = true; // Crucial para generar el sitemap estáticamente

export const GET: RequestHandler = async () => {
	const headers = {
		'Content-Type': 'application/xml'
	};

	const sitemap = `<?xml version="1.0" encoding="UTF-8" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
        <loc>https://www.tusitioweb.com/</loc>
        <lastmod>${new Date().toISOString()}</lastmod>
        <changefreq>daily</changefreq>
        <priority>1.0</priority>
    </url>
    ${posts
		.map(
			(post) =>
				`<url>
                <loc>https://www.tusitioweb.com/blog/${post.slug}</loc>
                <lastmod>${new Date(post.date).toISOString()}</lastmod>
                <changefreq>weekly</changefreq>
                <priority>0.8</priority>
            </url>`
		)
		.join('')}
</urlset>`;

	return new Response(sitemap, { headers });
};

Manejo de Formato de URLs para SSG

Al generar HTML estático, SvelteKit por defecto generará directorios con un index.html dentro (ej. /about/index.html). Esto es bueno porque permite URLs limpias como /about/. Asegúrate de que tu servidor de hosting esté configurado para servir index.html cuando se acceda a un directorio.

Actualización de Contenido sin Reconstruir el Sitio Entero

Esto es una limitación de SSG. Si tu contenido cambia (ej. añades un nuevo post de blog), necesitas ejecutar npm run build de nuevo y volver a desplegar el sitio para que los cambios se reflejen. Para muchos sitios, esto no es un problema y se automatiza fácilmente con CI/CD.

Uso de Datos Remotos en Tiempo de Compilación

Como se mencionó con entries, si tu load function o entries function en una página prerenderizada necesita datos remotos, esos datos deben ser accesibles durante la compilación. Esto es perfectamente viable si tu API es pública o si tienes credenciales configuradas en tu entorno de construcción. Si la API es interna o muy dinámica, SSG podría no ser la mejor opción, o podrías considerar un enfoque híbrido.

Enfoques Híbridos (SSG + CSR)

SvelteKit te permite combinar SSG con CSR (Client-Side Rendering) en la misma aplicación. Puedes prerenderizar la mayor parte de tu sitio para el rendimiento inicial y luego, para ciertas secciones interactivas o personalizadas, permitir que SvelteKit se hidrate y maneje las actualizaciones del lado del cliente. Esto se logra omitiendo prerender: true en las rutas que necesitan CSR, pero manteniendo el adaptador static configurado. En este escenario, es probable que necesites un fallback como 200.html.

📌 Nota: Cuando usas un `fallback`, el `adapter-static` generará un solo archivo HTML (`200.html` por defecto, o el que especifiques) que cargará tu aplicación SvelteKit y manejará el enrutamiento del lado del cliente para cualquier ruta que no haya sido pre-renderizada directamente.
CaracterísticaSSG Pura (con fallback: undefined)SSG Híbrida (con fallback: '200.html')
---------
RutasSolo rutas pre-renderizadas estáticamenteRutas pre-renderizadas + Rutas dinámicas manejadas por JS
Archivos generadosHTML para cada ruta + assets JS/CSSHTML para cada ruta + 200.html (o fallback) + assets JS/CSS
---------
Manejo 404Se basa en el servidor web (ej. 404.html)Puede ser manejado por el servidor o por SvelteKit JS
SEOExcelenteBueno (la página inicial es estática, el resto se hidrata)
---------
RendimientoMáximo (sirviendo archivos estáticos directos)Muy bueno (initial load estático, luego SPA)
ComplejidadBajaMedia (requiere entender el comportamiento del fallback)

Ejercicio Práctico: Un Blog Estático Sencillo con SvelteKit SSG 🧑‍💻

Vamos a crear un pequeño blog donde los posts se cargarán desde archivos locales en tiempo de compilación.

Estructura del Proyecto

my-static-blog/
├── src/
│   ├── lib/
│   │   └── posts.js   <-- Aquí estarán tus datos de posts
│   ├── routes/
│   │   ├── +page.svelte     <-- Página principal (índice de posts)
│   │   └── blog/
│   │       ├── [slug]/
│   │       │   └── +page.svelte  <-- Página de post individual
│   │       │   └── +page.ts      <-- Lógica para prerenderizar posts
│   │       └── +page.svelte    <-- Ruta base del blog (opcional)
│   └── app.html
├── svelte.config.js
└── package.json

1. svelte.config.js

Asegúrate de que tu svelte.config.js esté configurado para adapter-static y que el prerenderizado global sea true para simplificar.

// svelte.config.js

import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/kit/vite';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	preprocess: vitePreprocess(),
	skit: {
		adapter: adapter({
			pages: 'build',
			assets: 'build',
			fallback: undefined
		}),
		prerender: { default: true } // Prerenderizar todo por defecto
	}
};

export default config;

2. src/lib/posts.js

Aquí simularemos una base de datos de posts. En un caso real, podrías leer archivos Markdown.

// src/lib/posts.js

export const allPosts = [
	{
		slug: 'introduccion-a-sveltekit',
		title: 'Introducción a SvelteKit',
		date: '2023-01-15',
		content: 'Este es el contenido del primer post sobre SvelteKit. ¡Es genial!'
	},
	{
		slug: 'generacion-estatica-con-sveltekit',
		title: 'Generación Estática con SvelteKit',
		date: '2023-02-01',
		content: 'Aprende cómo usar SSG en SvelteKit para sitios rápidos y eficientes.'
	},
	{
		slug: 'componentes-reactivos-en-svelte',
		title: 'Componentes Reactivos en Svelte',
		date: '2023-02-10',
		content: 'Explorando la reactividad en los componentes de Svelte.'
	}
];

export function getPostBySlug(slug) {
	return allPosts.find(post => post.slug === slug);
}

3. src/routes/+page.svelte (Página de Inicio)

<!-- src/routes/+page.svelte -->
<script>
	import { allPosts } from '$lib/posts';
</script>

<style>
	.post-list {
		display: grid;
		grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
		gap: 20px;
		padding: 20px;
	}

	.post-card {
		background-color: #f9f9f9;
		border: 1px solid #eee;
		border-radius: 8px;
		padding: 15px;
		box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
		transition: transform 0.2s ease;
	}

	.post-card:hover {
		transform: translateY(-5px);
	}

	.post-card h2 {
		margin-top: 0;
		font-size: 1.5em;
	}

	.post-card a {
		text-decoration: none;
		color: #333;
	}

	.post-card p {
		font-size: 0.9em;
		color: #666;
	}
</style>

<h1>Mi Blog Estático con SvelteKit</h1>
<p>Últimos artículos:</p>

<div class="post-list">
	{#each allPosts as post}
		<div class="post-card">
			<a href="/blog/{post.slug}">
				<h2>{post.title}</h2>
				<p>{new Date(post.date).toLocaleDateString()}</p>
			</a>
		</div>
	{/each}
</div>

4. src/routes/blog/[slug]/+page.ts (Generación de Entradas Dinámicas)

// src/routes/blog/[slug]/+page.ts

import type { EntryGenerator } from '@sveltejs/kit';
import { allPosts, getPostBySlug } from '$lib/posts';

// Prerenderizar esta página y todas las entradas generadas
export const prerender = true;

// Le decimos a SvelteKit qué slugs debe generar
export const entries: EntryGenerator = async () => {
	return allPosts.map(post => ({ slug: post.slug }));
};

export async function load({ params }) {
	const post = getPostBySlug(params.slug);

	if (post) {
		return { post };
	} else {
		// Si el post no se encuentra en tiempo de compilación, podemos lanzar un error
		// SvelteKit lo capturará y generará un 404 (si tienes un +error.svelte)
		return { status: 404, error: new Error('Post not found') };
	}
}

5. src/routes/blog/[slug]/+page.svelte (Página de Post Individual)

<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
	export let data;
	const { post } = data;
</script>

<style>
	article {
		max-width: 800px;
		margin: 40px auto;
		padding: 20px;
		background-color: #fff;
		border-radius: 8px;
		box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
	}

	h1 {
		color: #333;
		margin-bottom: 10px;
	}

	.post-meta {
		font-size: 0.9em;
		color: #777;
		margin-bottom: 20px;
		border-bottom: 1px solid #eee;
		padding-bottom: 10px;
	}

	.post-content {
		line-height: 1.6;
		color: #444;
	}
</style>

{#if post}
	<article>
		<h1>{post.title}</h1>
		<p class="post-meta">Publicado el: {new Date(post.date).toLocaleDateString()}</p>
		<div class="post-content">
			<p>{post.content}</p>
		</div>
		<a href="/">← Volver al inicio</a>
	</article>
{:else}
	<p>Post no encontrado.</p>
{/if}

6. Construir y Servir

Ahora, ejecuta la construcción:

npm run build

Después de la construcción, tendrás tu sitio estático en la carpeta build/. Puedes usar una herramienta simple como serve para probarlo localmente:

npm install -g serve
serve build

Navega a http://localhost:3000 (o el puerto que te indique serve) y verás tu blog estático. Todas las páginas, incluida la lista de posts y cada post individual, son archivos HTML estáticos generados previamente. ¡Felicidades, has creado un sitio SSG con SvelteKit!


Conclusión ✅

La Generación de Sitios Estáticos con SvelteKit es una estrategia fantástica para construir aplicaciones web que requieren alto rendimiento, seguridad y eficiencia en el despliegue. Hemos cubierto cómo configurar tu proyecto, marcar páginas para prerenderizado (incluyendo rutas dinámicas), y los pasos para construir y desplegar tu sitio. Al adoptar SSG, estás eligiendo un camino hacia una web más rápida y robusta, ideal para una amplia gama de proyectos.

Experimenta con las opciones del adaptador, explora cómo tus datos son cargados en tiempo de compilación y disfruta de la velocidad que SvelteKit SSG puede ofrecer a tus usuarios.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!