Kamaankamaan

Headless CMS für SvelteKit: Setup-Anleitung und API-Integration

SvelteKits Load-Funktionen sind der richtige Ort, um ein Headless CMS aufzurufen. Server-only Load hält API-Keys vom Client fern, params steuern das Sprach-Routing, ISR oder Build-Time-Export decken den Cache ab. Hier is

Junaid Khalid
Junaid Khalid
30. Mai 2026 · 7 min read

SvelteKit liefert ein Daten-Loading-Modell, das besser zu Headless-CMS-Arbeit passt als die meisten Frameworks. Die Trennung zwischen +page.ts (universell) und +page.server.ts (nur Server) sagt dir, wo du den API-Key hinpacken sollst. Das params-Objekt regelt Sprache und Slug-Routing ohne extra Middleware. Das Ergebnis ist ein Setup, das für einen Entwickler mit Framework-Erfahrung etwa eine Stunde dauert und einen Blog liefert, der schnell lädt, sauber rankt und mit einer einzigen Config auskommt.

Diese Anleitung verdrahtet SvelteKit mit einer Headless-CMS-REST-API, von Anfang bis Ende. Der Code ist gegen Kamaans REST-API als konkretes Beispiel geschrieben, aber das Muster lässt sich auf jedes REST-förmige CMS übertragen.

Auf einen Blick

  • Nutze +page.server.ts für CMS-Fetches. Die Server-Load-Funktion hält den API-Key aus dem Client-Bundle raus, und die an den Client zurückgegebenen Daten enthalten den Schlüssel nie.
  • Route die Sprache mit params. Ein Ordner namens [lang] erzeugt einen params.lang-Wert, den die Load-Funktion liest, um die richtige Locale zu fetchen.
  • Definiere entries(), damit SvelteKit die Artikel-URLs prerendern kann. Die Funktion gibt die Slug-und-Sprache-Kombinationen zurück, die beim Build prerendert werden sollen, und der Build-Schritt schreibt eine HTML-Datei pro Artikel pro Sprache.
  • Kamaans REST-API liefert Artikel mit einem language-Query-Parameter und gibt Body, SEO-Felder und locale-präfixierten Slug bereits befüllt zurück. Der Fetch ist ein Aufruf pro Seite.
  • Derselbe Artikel wird auf /en/blog/, /es/blog/, /de/blog/, /fr/blog/, /it/blog/ veröffentlicht, mit automatisch korrektem hreflang. SvelteKit liest aus Kamaan, Kamaan kümmert sich um die mehrsprachige Auslieferung.

SvelteKits Load-Funktionen sind das Einzige, was du verstehen musst. Drei Varianten:

+page.ts läuft sowohl serverseitig beim SSR als auch clientseitig bei Navigation. Alles, was du hier importierst, landet im Client-Bundle. Pack hier keine API-Keys rein.

+page.server.ts läuft ausschließlich serverseitig. Die Funktion wird beim SSR ausgeführt, gibt JSON an den Client für die Hydration zurück und läuft erneut als Remote-Endpoint, wenn der Nutzer navigiert. API-Keys, server-only Umgebungsvariablen und Datenbankaufrufe gehören hierher.

+layout.server.ts läuft serverseitig für jede Seite, die das Layout erbt. Nutze es für seitenweiten Kontext, der Server-Credentials braucht, etwa ein globales "Site-Config"-Objekt aus dem CMS.

Für einen Blog, der an ein Headless CMS verdrahtet ist, gehört jeder Fetch in +page.server.ts. Es gibt kein Szenario, in dem du die CMS-API aus +page.ts aufrufen solltest.

SvelteKit + Headless-CMS-Architektur: Kamaan REST API speist die Page-Server-Load-Funktion, die page.svelte SSR neben Layout-Params für Sprach-Routing rendert

Projektstruktur für einen mehrsprachigen Blog

Das Ordner-Layout, das /[lang]/blog/[slug] unterstützt:

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

Der [lang]-Ordner erzeugt bei jedem Load einen params.lang-Wert. Der [slug]-Ordner liefert params.slug. SvelteKit übernimmt das Routing ohne Plugins.

Die minimale +page.server.ts für einen einzelnen Artikel

Das ist die gesamte Load-Funktion für /[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 };
};

Ein paar Punkte zum Beachten. Die Env-Imports nutzen $env/static/private, SvelteKits Compile-Time-Check, dass die Variable nur serverseitig verfügbar ist. Das fetch ist die von SvelteKit erweiterte Version, die Cookies und Cross-Request-Verhalten korrekt behandelt. Der error()-Helper wirft einen typisierten SvelteKit-Fehler, den SvelteKits error.html-Template rendert.

Die von Kamaans REST-API zurückgegebene Datenstruktur enthält title, content, excerpt, meta_title, meta_description, og_image, featured_image_url, slug, language und ein alternates-Array mit den URLs aller anderen Sprachversionen. Dieses Alternates-Array verwendest du, um die hreflang-Link-Tags im Head zu rendern.

Den Artikel in +page.svelte rendern

<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>

Die hreflang-Schleife ist die komplette mehrsprachige SEO-Implementierung auf der SvelteKit-Seite. Kamaan liefert das Alternates-Array, SvelteKit rendert die Link-Tags. Du musst die Cross-References nicht selbst berechnen oder pflegen.

Prerendering für Performance

Für einen Marketing-Blog sollte jeder Artikel beim Build prerendert werden. Zwei Anpassungen:

// +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 }))
  );
};

Die entries()-Funktion sagt SvelteKit, welche dynamischen Routen beim Build prerendert werden sollen. SvelteKit schreibt dann eine HTML-Datei pro Artikel pro Sprache in den Build-Output. Der Build läuft einmal bei Content-Änderungen, und die resultierenden statischen Dateien werden aus einem CDN ausgeliefert.

Für Inhalte, die sich häufig ändern, setze prerender auf false und nutze die Caching-Schicht der Plattform (Vercel ISR, Cloudflare Workers Cache oder das stale-while-revalidate deines CDN). Der Fetch liefert bei jedem Cache-Miss den aktuellen Inhalt.

Die Blog-Indexseite

Die Indexseite listet alle Artikel für eine gegebene Sprache. Gleiches Muster, anderer 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 };
};

Pagination, Kategorie-Filter und Sortierung laufen alle als Query-Parameter am selben Endpoint. Die REST-Form bleibt seitenübergreifend einheitlich.

Warum dieses Muster gewinnt

SvelteKits Server-Load-Funktionen entfernen den Großteil der Angriffsfläche, an der mehrsprachige Blogs scheitern. Der API-Key erreicht nie das Client-Bundle. Die hreflang-Annotationen werden aus Serverdaten gerendert, nicht zur Laufzeit berechnet. Der Build kann statisches HTML für jeden Artikel in jeder Sprache erzeugen, genau das, was Googles Crawler belohnt. Das kleine Bundle des Frameworks und das serverseitige Rendering halten die Core Web Vitals dort, wo sie sein müssen.

Kamaans Auto-Multilingual Delivery übernimmt den Teil, den SvelteKit nicht kann: die übersetzten Bodies, Slugs und SEO-Felder produzieren. Das CMS veröffentlicht die vier übersetzten Versionen, SvelteKit liest sie mit demselben einzeiligen Fetch. Eine Config, vier Sprachen live, keine separaten Workflows.

Zwei reale Workflows

Ein bootstrapped SaaS-Gründer stampft an einem Wochenende einen SvelteKit-Blog aus dem Boden. Er verdrahtet eine +page.server.ts gegen Kamaans REST-API, deployed auf Vercel und verbindet seine Domain. Der Blog ist auf Englisch mit der URL-Struktur /blog/[slug] live. Wenn er Auto-Multilingual Delivery aktiviert, liefert derselbe SvelteKit-Code ab dem nächsten Build /es/blog/[slug], /de/blog/[slug], /fr/blog/[slug], /it/blog/[slug]. Gesamte Entwicklerzeit: unter zwei Stunden.

Eine kleine Agentur betreibt drei Kunden-SaaS-Sites, alle auf SvelteKit. Jede Kunden-Site ist dasselbe Kamaan-gespeiste Muster mit einer anderen Env-Datei. Neue Artikel werden aus einem Kamaan-Account veröffentlicht und triggern via Webhook einen Rebuild der drei SvelteKit-Apps. Der Agentur-Betreiber editiert nie den Svelte-Code, das CMS erledigt die Arbeit.

FAQ

Soll ich +page.ts oder +page.server.ts für CMS-Fetches verwenden?

Immer +page.server.ts. Die Credentials und die API-URL gehören auf den Server. Die an den Client zurückgegebenen Daten sollten bereits auf das gefiltert sein, was die Seite rendert.

Hat SvelteKit einen Kamaan-spezifischen Adapter?

Nein, und es braucht keinen. Kamaans REST-API liefert Standard-JSON. Jeder SvelteKit-Fetch funktioniert. Das Muster ist identisch zum Fetchen von jedem anderen REST-Endpoint.

Wie behandle ich Preview-Inhalte aus dem CMS?

Füge dem API-Call einen Query-Parameter hinzu (?status=draft) und sichere ihn über einen Env-Variable-Check ab. SvelteKits Preview-Modus-Muster nutzt Cookies, um Draft-Zugriff nur auf dem Preview-Deployment freizuschalten.

Wie prerendere ich tausende Artikel effizient?

Die entries()-Funktion liefert alle zu prerendernden Kombinationen. SvelteKit regelt die Concurrency beim Build. Für sehr große Blogs wechsle zu ISR oder serverseitigem Rendering mit CDN-Caching, um die Build-Kosten zu amortisieren.

Funktioniert das mit adapter-static für einen vollständig statischen Export?

Ja. prerender = true plus entries() lässt adapter-static den kompletten Satz an HTML-Dateien erzeugen. Füge eine Fallback-Regel für Sprachen hinzu, die nach dem Build dazukommen, und stoße Rebuilds bei Content-Änderungen an.

Was ist mit authentifizierten Routen, die das CMS brauchen?

Nutze einen SvelteKit-Hook (hooks.server.ts), um die Session vor dem Load zu verifizieren. Der CMS-Fetch bleibt in +page.server.ts, aber der Load liefert 401, wenn die Session ungültig ist.

Verwandt auf Kamaan

Starte mit Kamaan

Ein REST-Endpoint, jede Sprache live

Kamaan gibt dir ein Dashboard für alle deine Produkt-Blogs, ausgeliefert per sauberer REST-API an jedes Framework. Auto-übersetzt in 99+ Sprachen bei jeder Veröffentlichung. Ein Account deckt unbegrenzte Sites für 19 $ pro Monat, flat. Erster Monat gratis.

Kostenlos auf kamaan.io starten

Frequently asked

FAQ · 6 ITEMS
Soll ich +page.ts oder +page.server.ts für CMS-Fetches verwenden?

Immer `+page.server.ts`. Die Credentials und die API-URL gehören auf den Server. Die an den Client zurückgegebenen Daten sollten bereits auf das gefiltert sein, was die Seite rendert.

Hat SvelteKit einen Kamaan-spezifischen Adapter?

Nein, und es braucht keinen. Kamaans REST-API liefert Standard-JSON. Jeder SvelteKit-Fetch funktioniert. Das Muster ist identisch zum Fetchen von jedem anderen REST-Endpoint.

Wie behandle ich Preview-Inhalte aus dem CMS?

Füge dem API-Call einen Query-Parameter hinzu (`?status=draft`) und sichere ihn über einen Env-Variable-Check ab. SvelteKits Preview-Modus-Muster nutzt Cookies, um Draft-Zugriff nur auf dem Preview-Deployment freizuschalten.

Wie prerendere ich tausende Artikel effizient?

Die `entries()`-Funktion liefert alle zu prerendernden Kombinationen. SvelteKit regelt die Concurrency beim Build. Für sehr große Blogs wechsle zu ISR oder serverseitigem Rendering mit CDN-Caching, um die Build-Kosten zu amortisieren.

Funktioniert das mit adapter-static für einen vollständig statischen Export?

Ja. `prerender = true` plus `entries()` lässt adapter-static den kompletten Satz an HTML-Dateien erzeugen. Füge eine Fallback-Regel für Sprachen hinzu, die nach dem Build dazukommen, und stoße Rebuilds bei Content-Änderungen an.

Was ist mit authentifizierten Routen, die das CMS brauchen?

Nutze einen SvelteKit-Hook (`hooks.server.ts`), um die Session vor dem Load zu verifizieren. Der CMS-Fetch bleibt in `+page.server.ts`, aber der Load liefert 401, wenn die Session ungültig ist.

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.