SvelteKit incluye un modelo de carga de datos que se adapta al trabajo con headless CMS mejor que la mayoria de los frameworks. La separacion entre +page.ts (universal) y +page.server.ts (solo servidor) te indica donde colocar la API key. El objeto params se encarga del enrutamiento por idioma y slug sin middleware adicional. El resultado es una configuracion que toma una hora para un desarrollador que ya conoce el framework y produce un blog que carga rapido, posiciona limpiamente y se mantiene en un solo archivo de configuracion.
Esta guia conecta SvelteKit con una REST API de headless CMS, de principio a fin. El codigo esta escrito contra la REST API de Kamaan como ejemplo concreto, pero el patron se traslada a cualquier CMS con forma REST.
Conclusiones rapidas
- Usa
+page.server.tspara las llamadas al CMS. La funcion server load mantiene la API key fuera del bundle del cliente, y los datos devueltos al cliente nunca incluyen la credencial. - Enruta el idioma con
params. Una carpeta llamada[lang]produce un valorparams.langque la funcion load lee para obtener la locale correcta. - Define
entries()para que SvelteKit pueda prerenderizar las URLs de los articulos. La funcion devuelve las combinaciones de slug e idioma para prerenderizar en build, y el paso de build genera un archivo HTML por articulo por idioma. - La REST API de Kamaan entrega articulos con un parametro de consulta
languagey devuelve el cuerpo, los campos SEO y el slug con prefijo de locale ya rellenos. La llamada es una sola por pagina. - El mismo articulo se publica en /en/blog/, /es/blog/, /de/blog/, /fr/blog/, /it/blog/ con el hreflang correcto automaticamente. SvelteKit lee de Kamaan, Kamaan se encarga de la entrega multilingue.
Las funciones load de SvelteKit son lo unico que necesitas entender. Tres variantes:
+page.ts se ejecuta tanto en el servidor durante el SSR como en el cliente al navegar. Cualquier cosa que importes aqui termina en el bundle del cliente. No coloques API keys aqui.
+page.server.ts se ejecuta solo en el servidor. La funcion se ejecuta durante el SSR, devuelve JSON al cliente para la hidratacion y se ejecuta de nuevo como endpoint remoto cuando el usuario navega. Aqui viven las API keys, las variables de entorno solo de servidor y las llamadas a la base de datos.
+layout.server.ts se ejecuta en el servidor para cada pagina que hereda el layout. Usalo para contexto a nivel de sitio que necesite credenciales de servidor, como un objeto global "site config" obtenido del CMS.
Para un blog conectado a un headless CMS, cada llamada va en +page.server.ts. No hay ningun escenario en el que debas estar llamando a la API del CMS desde +page.ts.
![]()
Estructura del proyecto para un blog multilingue
El layout de carpetas que soporta /[lang]/blog/[slug]:
src/routes/
[lang]/
blog/
+page.server.ts # blog index
+page.svelte
[slug]/
+page.server.ts # single article
+page.svelte
+layout.server.ts # site-wide data
+layout.svelte
La carpeta [lang] produce un valor params.lang en cada load. La carpeta [slug] da params.slug. SvelteKit maneja el enrutamiento sin ningun plugin.
El +page.server.ts minimo para un solo articulo
Esta es la funcion load completa para /[lang]/blog/[slug]:
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';
import { KAMAAN_API, KAMAAN_KEY, KAMAAN_SITE_ID } from '$env/static/private';
export const load: PageServerLoad = async ({ params, fetch }) => {
const { lang, slug } = params;
const url = `${KAMAAN_API}/sites/${KAMAAN_SITE_ID}/articles/${slug}?language=${lang}`;
const res = await fetch(url, {
headers: { Authorization: `Bearer ${KAMAAN_KEY}` }
});
if (!res.ok) throw error(res.status, 'Article not found');
const article = await res.json();
return { article };
};
Algunas cosas que conviene notar. Las importaciones de env usan $env/static/private, que es la verificacion en tiempo de compilacion de SvelteKit de que la variable solo esta disponible en el servidor. El fetch es la version aumentada de SvelteKit que maneja cookies y el comportamiento entre solicitudes correctamente. El helper error() lanza un error tipado de SvelteKit que sera renderizado por la plantilla error.html de SvelteKit.
La forma de los datos devueltos por la REST API de Kamaan incluye title, content, excerpt, meta_title, meta_description, og_image, featured_image_url, slug, language y un arreglo alternates con las URLs de todas las demas versiones de idioma. Ese arreglo alternates es lo que usas para renderizar las etiquetas hreflang en el head.
Renderizando el articulo en +page.svelte
<script lang="ts">
export let data;
const { article } = data;
</script>
<svelte:head>
<title>{article.meta_title}</title>
<meta name="description" content={article.meta_description} />
<link rel="canonical" href={article.canonical_url} />
{#each article.alternates as alt}
<link rel="alternate" hreflang={alt.language} href={alt.url} />
{/each}
<meta property="og:image" content={article.og_image} />
</svelte:head>
<article>
<h1>{article.title}</h1>
{@html article.content_html}
</article>
El bucle hreflang es toda la implementacion del SEO multilingue del lado de SvelteKit. Kamaan emite el arreglo alternates; SvelteKit renderiza las etiquetas link. No hay necesidad de calcular o mantener las referencias cruzadas manualmente.
Prerenderizado para rendimiento
Para un blog de marketing, cada articulo deberia prerenderizarse en build. Dos ajustes:
// +page.server.ts
export const prerender = true;
export const entries = async () => {
const res = await fetch(`${KAMAAN_API}/sites/${KAMAAN_SITE_ID}/articles?per_page=1000`, {
headers: { Authorization: `Bearer ${KAMAAN_KEY}` }
});
const { articles } = await res.json();
return articles.flatMap(a =>
a.languages.map(lang => ({ lang, slug: a.slug }))
);
};
La funcion entries() le dice a SvelteKit que rutas dinamicas prerenderizar en build. SvelteKit entonces escribe un archivo HTML por articulo por idioma en la salida del build. El build se ejecuta una vez cuando el contenido cambia y los archivos estaticos resultantes se sirven desde un CDN.
Para contenido que se actualiza con frecuencia, cambia prerender a false y usa la capa de cache de la plataforma (Vercel ISR, cache de Cloudflare Workers o el stale-while-revalidate de tu CDN). La llamada devuelve el contenido mas reciente en cada cache miss.
La pagina indice del blog
La pagina indice lista todos los articulos para un idioma dado. Mismo patron, distinto endpoint:
// /[lang]/blog/+page.server.ts
import type { PageServerLoad } from './$types';
import { KAMAAN_API, KAMAAN_KEY, KAMAAN_SITE_ID } from '$env/static/private';
export const load: PageServerLoad = async ({ params, fetch }) => {
const res = await fetch(
`${KAMAAN_API}/sites/${KAMAAN_SITE_ID}/articles?language=${params.lang}&per_page=50`,
{ headers: { Authorization: `Bearer ${KAMAAN_KEY}` } }
);
const { articles } = await res.json();
return { articles };
};
La paginacion, el filtrado por categoria y la ordenacion ocurren todos como parametros de consulta en el mismo endpoint. La forma REST se mantiene uniforme entre paginas.
Por que este patron gana
Las funciones server load de SvelteKit eliminan la mayor parte de la superficie en la que los blogs multilingues fallan. La API key nunca llega al bundle del cliente. Las anotaciones hreflang se renderizan a partir de datos del servidor, no se calculan en runtime. El build puede producir HTML estatico para cada articulo en cada idioma, que es lo que el crawler de Google premia. El bundle reducido del framework y el renderizado del lado del servidor mantienen los Core Web Vitals donde deben estar.
Auto-Multilingual Delivery de Kamaan se encarga de la parte que SvelteKit no puede: producir los cuerpos traducidos, los slugs y los campos SEO. El CMS publica las cuatro versiones traducidas; SvelteKit las lee con el mismo fetch de una linea. Una configuracion, cuatro idiomas en vivo, sin flujos de trabajo separados.
Dos flujos de trabajo reales
Una fundadora SaaS bootstrapped levanta un blog en SvelteKit durante un fin de semana. Conecta un +page.server.ts contra la REST API de Kamaan, despliega en Vercel y conecta su dominio. El blog esta en vivo en ingles con la estructura de URL /blog/[slug]. Cuando activa Auto-Multilingual Delivery, el mismo codigo de SvelteKit sirve /es/blog/[slug], /de/blog/[slug], /fr/blog/[slug], /it/blog/[slug] desde el siguiente build. Tiempo total de desarrollo: menos de dos horas.
Una pequena agencia opera tres sitios SaaS de cliente, todos en SvelteKit. El sitio de cada cliente sigue el mismo patron alimentado por Kamaan con un archivo env distinto. Los nuevos articulos se publican desde una sola cuenta de Kamaan y reconstruyen las tres apps de SvelteKit via webhook. El operador de la agencia nunca edita el codigo de Svelte; el CMS hace el trabajo.
Preguntas frecuentes
Deberia usar +page.ts o +page.server.ts para las llamadas al CMS?
Siempre +page.server.ts. Las credenciales y la URL de la API pertenecen al servidor. Los datos devueltos al cliente deberian estar ya filtrados a lo que la pagina renderiza.
Tiene SvelteKit un adaptador especifico para Kamaan?
No, y no lo necesita. La REST API de Kamaan devuelve JSON estandar. Cualquier fetch de SvelteKit funciona. El patron es identico al de obtener datos de cualquier endpoint REST.
Como manejo el contenido de vista previa del CMS?
Anade un parametro de consulta a la llamada a la API (?status=draft) y protegelo detras de una verificacion de variable de entorno. El patron del modo preview de SvelteKit usa cookies para habilitar el acceso a borradores solo en el despliegue de vista previa.
Como prerenderizo miles de articulos de forma eficiente?
La funcion entries() devuelve todas las combinaciones a prerenderizar. SvelteKit maneja la concurrencia en el build. Para blogs muy grandes, cambia a ISR o renderizado del lado del servidor con cache de CDN para amortizar el costo del build.
Funciona esto con adapter-static para una exportacion completamente estatica?
Si. prerender = true junto con entries() permite que adapter-static genere el conjunto completo de archivos HTML. Anade una regla de fallback para idiomas agregados despues del build y dispara rebuilds cuando el contenido cambie.
Que pasa con las rutas autenticadas que necesitan el CMS?
Usa un hook de SvelteKit (hooks.server.ts) para verificar la sesion antes de que se ejecute el load. La llamada al CMS sigue yendo en +page.server.ts, pero el load devuelve 401 si la sesion no es valida.
Relacionado en Kamaan
- Como anadir un blog a tu producto SaaS: una guia para desarrolladores. El pilar padre que cubre la decision completa del lado del desarrollador para anadir un blog a un frontend SaaS.
- Integracion Headless CMS Next.js: configuracion paso a paso desde cero hasta blog en vivo. El equivalente en Next.js de esta guia, util si estas eligiendo entre frameworks.
- Blog API: como obtener y renderizar contenido de un headless CMS en cualquier framework. La version agnostica al framework del contrato de la API, incluyendo la forma JSON que devuelve Kamaan.
- Que es un headless CMS: una guia en lenguaje claro para equipos SaaS. El ancla definicional si llegaste aqui sin el contexto de la categoria.
Empieza con Kamaan
Un endpoint REST, todos los idiomas en vivo
Kamaan te da un solo dashboard para todos los blogs de tus productos, entregados via una REST API limpia a cualquier framework. Traducido automaticamente a mas de 99 idiomas en cada publicacion. Una cuenta cubre sitios ilimitados por 19 dolares al mes, plano. Primer mes gratis.

