Kamaankamaan

Headless CMS para SvelteKit: guia de configuracion e integracion con la API

Las funciones load de SvelteKit son el lugar correcto para llamar a un headless CMS. El load solo de servidor mantiene las API keys fuera del cliente, los params dirigen el enrutamiento por idioma, e ISR o la exportacion

Junaid Khalid
Junaid Khalid
30 de mayo de 2026 · 9 min read

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.ts para 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 valor params.lang que 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 language y 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.

Arquitectura SvelteKit + headless CMS: la REST API de Kamaan alimenta la funcion server load de la pagina, que renderiza page svelte por SSR junto con los params del layout para el enrutamiento por idioma

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

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.

Empieza gratis en kamaan.io

Junaid Khalid
Written by
Junaid Khalid

Junaid Khalid is the founder of Kamaan, a headless blog CMS that auto-publishes in five languages and lets you manage every product blog from one dashboard.