---
order: 5
section: guides
title: Runtime-API
description: >-
  createScribe und die komplette typisierte Client-Schnittstelle: Lesezugriffe,
  Routing-Hilfsfunktionen, Relationen, Sitemap, Weiterleitungen und
  Framework-Integration.
noindex: false
canonicalPath: /de/docs/runtime-api
---
```ts
import { createScribe } from "scribe-cms/runtime";
import config from "./scribe.config";

const scribe = createScribe(config);
```

`createScribe()` liefert einen typisierten Client zurück. Dieser bietet für jeden Inhaltstyp einen eigenen Accessor (etwa `scribe.blog`, `scribe.author`, …) sowie die Methode `scribe.sitemap()`. Sämtliche Lesezugriffe erfolgen synchron direkt im Arbeitsspeicher. Inhalte werden einmalig geladen, zwischengespeichert und während der Entwicklung automatisch aktualisiert, sobald sich Dateien oder der Übersetzungsspeicher ändern.

Der Client basiert auf reinem Node und kommt komplett ohne Framework-Abhängigkeiten aus. Er funktioniert in Next.js, Astro, Remix, SvelteKit oder in einem einfachen Build-Skript exakt gleich. Die folgenden Seitenbeispiele nutzen die Konventionen von Next.js lediglich zur Veranschaulichung.

Dokumente weisen folgende Struktur auf. Dabei ist `frontmatter` durch dein Zod-Schema typisiert:

```ts
{
  slug: string;        // Slug in der Sprache dieses Dokuments
  enSlug: string;      // Englischer übergeordneter Slug (== Slug für EN-Dokumente)
  locale: string;
  frontmatter: { ... };
  content: string;     // Unverarbeiteter MDX-Inhalt
  publishedAt?: string;
  updatedAt?: string;
  noindex: boolean;
}
```

## Lesezugriffe

### `list(locale?, options?)`

Ruft alle Dokumente für eine bestimmte Sprache ab (Standardwert ist die Standardsprache), sortiert nach dem `orderBy`-Wert des jeweiligen Typs.

```ts
scribe.blog.list("fr");
scribe.blog.list("fr", { limit: 3 });
scribe.blog.list("fr", { orderBy: "slug" });   // Überschreibt die Sortierung für diesen Aufruf
```

Gibt ein leeres Array (`[]`) zurück, falls für die gewählte Sprache keine Dokumente existieren.

### `get(slug, locale?)`

Führt eine exakte Slug-Suche in einer einzigen Sprache durch. Es gibt hierbei keinen Fallback und keine automatischen Weiterleitungen. Das Ergebnis ist das Dokument selbst oder `null`.

### `resolve(slug, locale)`

Übernimmt die komplette Auflösung des Anfragepfads. Nutze diese Methode für deine Seiten:

```ts
const r = scribe.blog.resolve(slug, locale);
// {
//   document: BlogDoc | null,
//   actualLocale: string,        // "en", wenn der EN-Fallback aktiv wurde
//   shouldRedirectTo?: string,   // 301-Ziel (Korrektur bei falscher Slug-Sprache)
//   canonicalPath?: string,      // Sprachspezifischer Pfadname für das Dokument
// }
```

Arbeitet folgende Schritte nacheinander ab: Direkttreffer, Weiterleitung bei einem Slug in der falschen Sprache, [Fallback-Kette der Sprachen](/docs/configuration#locale-fallback-chains) (standardmäßig aktiv) und zuletzt der englische Fallback (falls `indexFallback: "en"` gesetzt ist). Wenn eine Fallback-Sprache die Seite ausliefert, spiegelt `actualLocale` diese Sprache wider. Die Slug-Korrektur berücksichtigt dabei die gesamte Kette. Slug-Migrationen und entfernte Dokumente werden über `_redirects.json`-Regeln in deinem Proxy oder deiner statischen Weiterleitungs-Map abgewickelt und nicht über `resolve()`. Ein typischer Seiten-Handler sieht in Next.js beispielsweise so aus:

```ts
const resolved = scribe.blog.resolve(slug, locale);
if (resolved.shouldRedirectTo) permanentRedirect(resolved.shouldRedirectTo);
if (!resolved.document) notFound();
```

## Routing-Hilfsfunktionen

### `staticParams(options?)`

Liefert jedes `{ locale, slug }`-Paar für das Prerendering. Du kannst dies direkt in den Static-Paths-Hook deines Frameworks einfügen (wie etwa `generateStaticParams` in Next.js, `getStaticPaths` in Astro oder `entries` in SvelteKit):

```ts
export function generateStaticParams() {
  return scribe.blog.staticParams();
}

// Einschränkung auf bestimmte Sprachen (z. B. für eine OG-Image-Route, die nur lateinische Schriften unterstützt):
scribe.blog.staticParams({ locales: ["en", "fr", "de"] });
```

Fehlt für eine Sprache die Übersetzung, stammt der vorgerenderte Slug aus der ersten Sprache der [Fallback-Kette](/docs/configuration#locale-fallback-chains), die einen passenden Eintrag aufweist (danach folgt der englische Slug). Dadurch stimmen die vorgerenderten URLs exakt mit den Ausgaben von `resolve()` überein.

### `alternates(doc)`

Erstellt die Hreflang-Zuordnung für ein Dokument (Sprache zu relativem Pfad) für jede Sprache mit einer vorhandenen Übersetzung. Die Standardsprache ist dabei immer enthalten:

```ts
scribe.blog.alternates(doc);
// { en: "/blog/hello-world", fr: "/fr/blog/bonjour-le-monde" }
```

Übergib diese Daten an deine Hreflang-Tags, beispielsweise an `metadata.alternates.languages` in Next.js oder an manuell generierte Link-Elemente.

### `translation(doc, targetLocale)`

Liefert dasselbe Dokument in einer anderen Sprache oder `null` im Falle einer fehlenden Übersetzung. Meistens greift man bei einem leeren Ergebnis einfach auf das bereits vorliegende Dokument zurück:

```ts
const frDoc = scribe.blog.translation(doc, "fr") ?? doc;
```

### `url(slug, locale)`

Generiert einen Pfadnamen anhand der `path`-Vorlage des Typs:

```ts
scribe.blog.url("hello-world", "fr");  // "/fr/blog/hello-world"
```

Bei reinen Referenztypen ohne eigenen `path` werfen `staticParams`, `alternates` und `url` einen Fehler.

## Relationen

### `related(doc, field, locale?)`

Löst ein Frontmatter-Feld vom Typ `field.relation()` in das entsprechende Dokument oder die Dokumente des Zieltyps auf. Der Rückgabetyp wird direkt aus dem Schema abgeleitet:

```ts
scribe.blog.related(doc, "author");        // AuthorDoc           (erforderlich, einzeln)
scribe.vertical.related(doc, "blogSlug");  // BlogDoc | null      (optional, einzeln)
scribe.glossary.related(doc, "terms");     // GlossaryDoc[]       (mehrere)
```

- Die Auflösung erfolgt in `locale ?? doc.locale`. Ist das Ziel nicht übersetzt, wird auf das englische Dokument zurückgegriffen.
- Eine **erforderliche** Relation gibt niemals `null` zurück. Der Befehl `scribe validate` bricht den Build-Vorgang ab, falls erforderliche Relationen ins Leere laufen. Sollte dennoch eine durchrutschen, wirft `related()` einen Fehler.
- Optionale Relationen liefern `null` zurück. Bei Mehrfachrelationen werden fehlende Ziele stillschweigend ignoriert.
- Es sind ausschließlich Schema-Felder auf oberster Ebene zugänglich. Verschachtelte Relationen werden zwar validiert, du musst sie jedoch manuell auflösen.

## Sitemap

```ts
const entries = await scribe.sitemap({
  baseUrl: "https://example.com",
  typeDefaults: {
    blog: { priority: 0.7, changeFrequency: "monthly" },
  },
});
```

Gibt einfache JSON-Einträge zurück (`url`, `lastModified`, `changeFrequency`, `priority` sowie Hreflang-Daten unter `alternates.languages` inklusive `x-default`). Diese Struktur entspricht `MetadataRoute.Sitemap` in Next.js und lässt sich für jeden anderen Technologie-Stack direkt als Sitemap-XML serialisieren. Pro englischem Dokument über alle routingfähigen Typen hinweg wird ein Eintrag erstellt. Dokumente mit `noindex` und Quell-Slugs aus `_redirects.json` werden ignoriert.

Verfügbare Optionen: `baseUrl` (erforderlich), `contentTypes`, `typeDefaults`, `resolveUrl`, `resolvePathname`, `excludeNoindex` (standardmäßig true) und `includeXDefault` (standardmäßig true).

## Introspektion

Neben den typspezifischen Accessoren stellt der Client auch die vollständig aufgelöste Konfiguration bereit:

```ts
scribe.config;              // Die aufgelöste ScribeConfig
scribe.getType("blog");     // Ein einzelner aufgelöster Inhaltstyp
scribe.listTypes();         // Alle Inhaltstypen
scribe.listRoutableTypes(); // Nur Typen mit einer path-Vorlage
```

Dies ist besonders praktisch für generische Werkzeuge. Du kannst damit beispielsweise über jeden routingfähigen Typ iterieren, um Navigationselemente oder Feeds aufzubauen.

## Weiterleitungen

Verwende für statisch exportierte Weiterleitungsregeln (wie `_redirects.json` oder sprachübergreifende Slugs) den folgenden Build-Skript-Einstiegspunkt:

```ts
// scripts/generate-redirects.ts (Ausführung vor dem App-Build)
import {
  buildAllContentRedirects,
  createProject,
  createUrlBuilder,
  loadConfigSync,
} from "scribe-cms";

const config = loadConfigSync();
const project = createProject(config);
const urlBuilder = createUrlBuilder(project.config);
const rules = buildAllContentRedirects(project, {
  prefixedLocales: urlBuilder.prefixedLocales,
});
// [{ source, destination, permanent: true }, ...]
// Entspricht exakt der Weiterleitungs-Konfiguration von Next.js. Mappe dies je nach Bedarf auf nginx-Regeln, _redirects,
// vercel.json oder das entsprechende Äquivalent deines Frameworks.
```

## Statische Rohdaten-Exporte

So erstellst du LLM- oder Crawler-freundliche reine MDX-Dateien, die als statische Assets (etwa `/blog/my-post.mdx`) bereitgestellt werden:

```bash
scribe export-static --out public
```

Alternativ funktioniert das auch programmatisch:

```ts
import { writeStaticRawExports, createProject, loadConfigSync } from "scribe-cms";

writeStaticRawExports(createProject(loadConfigSync()), { outDir: "public" });
```

Die Methode `getStaticExportRoots(project)` liefert die verwalteten Stammverzeichnisse (wie `blog/` oder `fr/blog/`). Diese kannst du vor dem Schreiben bereinigen lassen. Dokumente, die in der Datei `_redirects.json` als Weiterleitungsquellen aufgeführt sind, werden übersprungen, sofern `excludeRedirected` den Wert true hat (dies ist der Standard). Dokumente mit `noindex` sind eingeschlossen, es sei denn, du setzt `excludeNoindex`. Weitere Informationen zu den Befehls-Flags findest du in der [CLI-Referenz](/docs/cli).

## Notausstieg

Der Aufruf von `scribe.<type>.load()` gibt den rohen Index als `Map<locale, { bySlug, byEnSlug }>` zurück. Greife im Normalfall besser auf `list`, `get` oder `resolve` zurück. Die Methode `load()` ist primär für spezielle Tooling-Zwecke und ungewöhnliche Zugriffsmuster gedacht.

## Framework-Integration

Scribe ist komplett unabhängig vom gewählten Framework. Die Runtime ist reines Node und funktioniert reibungslos in jedem serverseitig gerenderten oder statisch gebauten Stack. Dabei gelten überall drei Grundregeln:

1. Nutze in deinem App-Code den Import von **`scribe-cms/runtime`**. Dadurch werden die CLI- und Übersetzer-Codepfade beim Tracing durch den Bundler ausgeschlossen. In Build-Skripten verwendest du hingegen `scribe-cms`.
2. Halte das native Modul **`better-sqlite3`** sowie Scribe aus deinem Bundler heraus. Nutze dafür beispielsweise `serverExternalPackages` in Next.js, `ssr.external` in Vite oder Astro sowie `external` in esbuild.
3. Speichere den Client in einem Modul-Singleton zwischen:

```ts
// src/lib/scribe.ts
import { createScribe } from "scribe-cms/runtime";
import type { ScribeClient } from "scribe-cms/runtime";
import config from "../../scribe.config";

let cached: ScribeClient<typeof config> | null = null;
export function getScribe() {
  return (cached ??= createScribe(config));
}
```

Mache deine Builds von validen Inhalten abhängig, indem du sie mit `"build": "scribe validate && <framework build>"` koppelst.

### Beispiel für Next.js

```ts
// next.config.ts
const nextConfig = {
  serverExternalPackages: ["better-sqlite3", "scribe-cms", "scribe-cms/runtime"],
  // Erforderlich auf Vercel: Scribe liest content/ und .scribe/ zur Laufzeit.
  outputFileTracingIncludes: {
    "/**": ["./content/**/*", "./.scribe/**/*"],
  },
  // Ebenfalls erforderlich auf Vercel: Durch die Dateizugriffe der Scribe-Runtime
  // kopiert der Tracer den Webpack-Build-Cache von Next in jede Serverless-Funktion.
  // Dadurch wird das 250-MB-Limit gesprengt. Der Cache wird zur Laufzeit nie benötigt.
  outputFileTracingExcludes: {
    "/**": ["./.next/cache/**"],
  },
};
```

Die Funktion `staticParams()` klinkt sich nahtlos in `generateStaticParams()` ein. Du verbindest `alternates()` mit `metadata.alternates.languages`, `scribe.sitemap()` mit `app/sitemap.ts` und `buildAllContentRedirects()` mit `redirects()`.
