---
title: Inhaltsquellen
description: >-
  Beziehe Dokumentation aus lokalen Dateien, einem entfernten Repository oder einem beliebigen eigenen Backend – und kombiniere mehrere Quellen zu einer static-first-Website, die zur Build-Zeit gelesen wird.
---

Standardmäßig liest Blume einen Ordner mit `.md`/`.mdx`-Dateien. **Inhaltsquellen** ermöglichen es dir, Seiten von woanders zu beziehen – aus einem entfernten Repository, einem CMS oder einem beliebigen eigenen Backend – und mehrere Quellen zu einer einzigen Website zu kombinieren. Quellen werden zur Build-Zeit gelesen; Blume bleibt static-first.

## Die Voreinstellung [#the-default]

Ohne Konfiguration durchsucht Blume dein Inhaltsverzeichnis (standardmäßig `docs`) als eine implizite Dateisystemquelle. Die Optionen `content.root`/`include`/`exclude` auf oberster Ebene funktionieren weiterhin genau wie zuvor – es muss nichts geändert werden.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: { root: "docs" },
});
```

## Mehrere Quellen [#multiple-sources]

Füge ein `content.sources`-Array hinzu, um Quellen zusammenzustellen. Jeder Eintrag erhält über ein optionales `prefix` einen eigenen Namensraum, sodass seine Routen unter `/<prefix>/…` liegen. Wenn `sources` vorhanden ist, ersetzt es die implizite Voreinstellung – nimm daher einen `filesystem`-Eintrag für deine lokale Dokumentation auf.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      // Local docs at the site root
      { type: "filesystem", root: "docs" },

      // Remote MDX from a GitHub repo, mounted under /sdk
      {
        type: "mdx-remote",
        prefix: "sdk",
        github: { owner: "acme", repo: "sdk", ref: "main", path: "docs" },
      },
    ],
  },
});
```

Wenn zwei Quellen dieselbe Route auflösen, meldet Blume den Build-Fehler `BLUME_DUPLICATE_ROUTE` – gib jeder Quelle ein eigenes `prefix`.

## Obsidian

Die eingebaute Quelle `obsidian` liest einen [Obsidian](https://obsidian.md)-Vault direkt an Ort und Stelle. Es gibt keinen Exportschritt, und es wird nichts in dein Repository generiert: Der Vault bleibt die Quelle der Wahrheit, und Blume überführt Obsidians Dialekt beim Laden nach Markdown.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "obsidian",
        prefix: "notes",
        vault: "vault",
        // Vault folder names to skip at any depth, on top of dot-folders
        exclude: ["Templates", "Daily"],
      },
    ],
  },
});
```

`[[Wikilinks]]` werden zu Route-Links, die – so wie Obsidian Notizen adressiert – über den Notiznamen im gesamten Vault statt über einen Pfad aufgelöst werden. Eigener Linktext (`[[Note|label]]`), Überschriften-Anker (`[[Note#Install]]`), vollständige Pfade (`[[folder/Note]]` und `[[folder/Note.md]]`), die Teilpfade, die Obsidians Standardeinstellung „Kürzester Pfad, wenn möglich“ schreibt (`[[guides/Note]]`), und die Form `[[Note\|label]]`, die Obsidian innerhalb einer Tabellenzelle schreibt, funktionieren alle; und eine Notiz, die `slug` in ihrem Frontmatter setzt, wird unter der Route verlinkt, die dieser Slug veröffentlicht. Wenn zwei Notizen denselben Namen haben, gewinnt eine Notiz, deren vollständiger Vault-Pfad genau diesem Namen entspricht – Obsidian löst einen Link zuerst als Pfad und dann als Namen auf –, danach die erste in Vault-Reihenfolge (Ordner vor Notizen, ohne Beachtung der Groß-/Kleinschreibung, wie in Obsidians Dateiexplorer). Blume warnt nur, wenn ein Wikilink tatsächlich über eine solche Kollision aufgelöst wird; schreibe einen längeren Pfad, um die Mehrdeutigkeit aufzulösen. Eine Blockreferenz (`[[Note#^id]]`) verlinkt auf ihre Notiz ohne Anker: Blöcke werden ohne id gerendert, auf der man landen könnte. Ein Überschriften-Anker wird gegen die echten Überschriften der Zielnotiz aufgelöst, abgeglichen so, wie Obsidians Autovervollständigung sie schreibt (ohne `**bold**`, `` `code` `` und Link-Syntax), und mit demselben `extractHeadings`-Durchlauf zu einem Slug gemacht, der auch das Seiten-Manifest füllt – ein Link auf `#Install` landet also auf der Überschrift statt auf einer id, die keine Seite ausgibt. `[[#Install]]` adressiert eine Überschrift in der Notiz, die du gerade schreibst. Ein Link auf eine Überschrift, die nicht existiert, behält den Seitenlink, verwirft den Anker und warnt.

Das Frontmatter behält, was Blumes [Seitenschema](/docs/reference/frontmatter) akzeptiert, plus jeden Schlüssel, den du in [`frontmatter.extend`](/docs/reference/frontmatter#custom-keys) deklarierst (oder, für Notizen dieses `type`, im `frontmatter` eines Inhaltstyps); jede andere Obsidian-Eigenschaft – Dataview-Felder, Templater-Daten, `publish` sowie Obsidians eigene `tags`, `aliases` und `cssclasses` – wird beim Überführen einer Notiz verworfen, sodass ein mit der Properties-Oberfläche geschriebener Vault ohne Frontmatter-Fehler baut. `aliases` wird verworfen statt aufgelöst – Alias-Linkziele werden noch nicht unterstützt. Ein relatives Markdown-Bild neben einer Notiz (`![chart](./chart.png)`) wird aus dem Vault ausgeliefert, und wenn der Vault innerhalb deines Git-Repositorys liegt, erhalten Vault-Seiten wie jede andere Seite aus Git abgeleitete [„Zuletzt aktualisiert“-Daten](/docs/configuration#last-modified). „Diese Seite bearbeiten“-Links werden über `github.dir` aufgelöst, sodass ein Vault, der in einem Monorepo neben der Dokumentations-App liegt, weiterhin auf seine Datei verlinkt; ein Vault außerhalb des Repositorys bekommt keinen Link.

Locale-Verzeichnisse und Versions-Snapshots innerhalb des Vaults werden genauso gelesen, wie die Dateisystemquelle sie liest: `fr/Note.md` wird bei konfiguriertem [i18n](/docs/content/i18n) unter `/fr/` veröffentlicht, `v1.0/Note.md` bei [Versionen](/docs/content/versioning) unter `/v1.0/`, und Wikilinks auf diese Notizen zeigen auf die Route, die die jeweilige Notiz veröffentlicht.

:::note
Eine Überschrift, die selbst einen Link enthält, erhält ihren Manifest-Anker aus dem Markdown der Überschrift und ihre gerenderte `id` aus ihrem Textinhalt. Für diese Überschrift unterscheiden sich beide, sodass ein Wikilink darauf eventuell auf der Seite statt im Abschnitt landet.
:::

Ein Link auf eine `index`-Notiz landet auf der Route ihres Ordners statt auf einem Phantom-`/index`. **Ein nicht auflösbarer Wikilink wird zu einfachem Text mit einer Build-Warnung abgestuft, statt den Build fehlschlagen zu lassen**, sodass ein Vault mitten im Refactoring weiterhin veröffentlicht wird. Einzeilige `%%comments%%` werden entfernt, ein Wikilink innerhalb eines HTML-Kommentars (`<!-- [[Draft]] -->`) bleibt unangetastet, da Obsidian ihn ebenfalls verbirgt, und eine Notiz ohne `title` im Frontmatter erhält ihren Dateinamen als Titel – dieselbe Regel, die auch Obsidian selbst anwendet. Eine `index`-Notiz ist die einzige Ausnahme: Sie benennt eine Route statt einer Notiz, daher fällt ihr Titel auf Blumes übliche Herleitung zurück (erste Überschrift, dann das lesbar gemachte Segment). Code in Fences, eingerückter Code und Inline-Code werden wortwörtlich durchgereicht, sodass eine Notiz, die die Syntax dokumentiert, erhalten bleibt.

Punkt-Ordner werden übersprungen, einschließlich Obsidians eigenem Konfigurationsverzeichnis `.obsidian` und `.trash` – das der Dev-Watcher ebenfalls ignoriert, sodass das Verschieben eines Panels in der App oder das Löschen einer Notiz in den Papierkorb deine Website nicht neu baut. Das Bearbeiten einer Notiz schon. Die Verzeichnisse, die kein Inhaltsscan liest (`node_modules`, `dist`, `.git`, …), werden ebenfalls übersprungen, sodass ein Vault, der im Projekt selbst wurzelt, keine READMEs von Abhängigkeiten veröffentlicht. Symlinks innerhalb des Vaults werden verfolgt, so wie die Dateisystemquelle sie verfolgt, sodass ein in den Vault verlinkter geteilter Ordner mit ihm veröffentlicht wird. Ein Vault, der innerhalb von `content.root` liegt, muss von der Dateisystemquelle ausgeschlossen werden (`exclude: ["vault/**"]`); `blume version cut` lässt ihn dann aus dem Snapshot heraus, da der Vault seine eigenen Notizen weiterhin als aktuell veröffentlicht.

Noch nicht überführt: Callouts (`> [!note]`) werden als einfache Blockzitate gerendert, Einbettungen (`![[image.png]]`) werden unverändert durchgereicht, mehrzeilige `%%comments%%` bleiben stehen, und es gibt keinen Backlink-Graphen.

## Entferntes MDX [#remote-mdx]

Die eingebaute Quelle `mdx-remote` lädt rohe `.md`/`.mdx`-Dateien über HTTP. Zähle die Dateien entweder über einen Teilbaum eines GitHub-Repositorys (`github`) oder explizit gegenüber einer Raw-Basis-URL (`url` + `files`) auf:

```ts blume.config.ts
{
  type: "mdx-remote",
  prefix: "sdk",
  url: "https://raw.githubusercontent.com/acme/sdk/main/docs",
  files: ["intro.mdx", "guide.mdx"],
}
```

Das Token eines privaten Repositorys wird aus der Umgebungsvariable `GITHUB_TOKEN` gelesen – es wird niemals in deine Konfiguration oder die generierte Ausgabe eingebettet und ausschließlich an GitHubs eigene Hosts (`api.github.com`, `raw.githubusercontent.com`) gesendet, niemals an eine eigene `url`-Basis.

Entfernte Seiten werden mit voller MDX-plus-Komponenten-Treue gerendert: Ihre Inhalte werden in einem versteckten Staging-Verzeichnis materialisiert und zusammen mit deiner lokalen Dokumentation über Astro gerendert, sodass Callouts, Tabs und alle anderen Blume-Komponenten weiterhin funktionieren.

### Caching und Offline-Builds [#caching-and-offline-builds]

Jede entfernte Quelle hält einen Snapshot unter `.blume/cache/<source>/` vor. Wenn ein Abruf fehlschlägt – eine Netzwerkstörung oder ein CMS-Ausfall –, liefert Blume den letzten funktionierenden Snapshot mit einer Warnung aus, anstatt den Build fehlschlagen zu lassen. Der Cache liegt innerhalb von `.blume/` und wird neu erzeugt, niemals eingecheckt.

In der Entwicklung werden entfernte Inhalte einmal abgerufen und für die Sitzung eingefroren; starte den Dev-Server neu, um sie zu aktualisieren. Lokale Dateisystemquellen laden wie gewohnt per Hot-Reload neu. Um eine entfernte Quelle stattdessen auf Änderungen abzufragen, setze bei ihr `pollInterval` (in Sekunden) – der Dev-Server ruft in diesem Intervall erneut ab und lädt nur dann neu, wenn sich der Inhalt tatsächlich geändert hat. Lass die Option ungesetzt, um die API während der Arbeit nicht zu belasten.

## GitHub Releases

Die eingebaute Quelle `github-releases` verwandelt die Releases eines Repositorys in ein Changelog: Jedes Release wird zu einem Eintrag mit `type: changelog`, sodass deine Release Notes _dein_ Changelog sind – nichts muss doppelt geschrieben werden. In Kombination mit der generierten [Changelog-Timeline](/docs/advanced/changelog) liefert die Veröffentlichung eines GitHub-Releases direkt einen Changelog-Eintrag.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "content" },
      {
        type: "github-releases",
        prefix: "changelog",
        owner: "acme",
        repo: "sdk",
        // prereleases: false,  // include prereleases (default off)
        // drafts: false,       // include drafts (needs a write token)
        // limit: 100,          // cap releases, newest-first
      },
    ],
  },
});
```

Jedes Release wird automatisch auf die Changelog-Felder abgebildet: Sein Name (oder Tag) wird zum Titel, sein Veröffentlichungsdatum bestimmt die Reihenfolge in der Timeline, der Tag wird zu `changelog.version`, und Prereleases werden mit `Prerelease` gekennzeichnet (alle anderen mit `Release`). Die Notes werden als Inhalt des Eintrags gerendert. Gib der Quelle ein `prefix`, damit ihre Release-Seiten unter einer Route wie `/changelog/v1-2-0` liegen.

Ein privates Repository authentifiziert sich über die Umgebungsvariable `GITHUB_TOKEN` – dasselbe Token, das auch die anderen GitHub-Funktionen verwenden, und es wird niemals in deine Konfiguration eingebettet. Wie jede entfernte Quelle wird sie unter `.blume/cache/<source>/` zwischengespeichert und offline ausgeliefert, falls die API nicht erreichbar ist. Da ein Changelog ergänzend ist, führt ein fehlgeschlagener Abruf ohne Cache (etwa ein CI-Build ohne Token) zu einem leeren Changelog mit einer Warnung, statt den Build fehlschlagen zu lassen – setze `GITHUB_TOKEN` in deinen CI- und Deploy-Umgebungen, um es zu befüllen.

## Sanity

Die eingebaute Quelle `sanity` führt eine GROQ-Abfrage aus und bildet die Felder jedes Dokuments auf Frontmatter sowie seinen Portable-Text-Inhalt auf Markdown ab. Das Paket `@sanity/client` ist eine optionale Peer-Abhängigkeit – installiere es nur, wenn du diese Quelle verwendest.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "sanity",
        prefix: "guides",
        projectId: "abc123",
        dataset: "production",
        query: `*[_type == "guide"]`,
        // Field paths default to title / slug.current / body / _updatedAt
        fields: { slug: "slug.current", body: "content" },
      },
    ],
  },
});
```

Ein Lesetoken für ein privates Dataset stammt aus der Umgebungsvariable `SANITY_TOKEN`. Eigene Portable-Text-Blocktypen werden über die Option `serializers` des Adapters auf Blume-Komponenten abgebildet; sie steht zur Verfügung, wenn du `sanitySource` direkt über eine [eigene Quelle](#custom-sources) konstruierst.

## Notion

Die eingebaute Quelle `notion` verwandelt eine Notion-Datenbank in eine Sammlung: Jede Zeile wird zu einer Seite, ihre Eigenschaften werden zu Frontmatter und ihr Blockbaum wird zu MDX. Callouts, Toggles, Spalten und Codeblöcke werden auf die passenden Blume-Komponenten abgebildet. Videoblöcke werden zu einer `<YouTube>`-Einbettung, wenn sie einen YouTube-Link enthalten, und ansonsten zu einem `<video>`-Player – in beiden Fällen mit der Beschriftung des Blocks als `<Frame>`-Beschriftung; ein Link auf eine Videoseite statt auf eine Mediendatei (etwa eine Vimeo- oder Loom-URL) wird als Warnung gemeldet, statt eingebettet zu werden. `@notionhq/client` (v5 oder neuer) ist eine optionale Peer-Abhängigkeit; Blume liest die Datenbank über ihre erste Datenquelle.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "notion",
        prefix: "handbook",
        database: process.env.NOTION_DB_ID,
        // Property names default to the title-typed prop / Description / Slug / Order
        // Set publishedValue to treat Status as a publish gate (opt-in)
        publishedValue: "Published",
      },
    ],
  },
});
```

Das Integrationstoken stammt aus der Umgebungsvariable `NOTION_TOKEN` (teile die Datenbank mit deiner Integration). Standardmäßig wird jede Seite importiert; setze `publishedValue`, um die Eigenschaft `Status` zu einer Veröffentlichungssperre zu machen – jeder andere Wert wird dann auf `draft: true` abgebildet, was Produktions-Builds verwerfen. **Notion-Bild- und Video-URLs sind signiert und laufen ab**, daher lädt der Adapter sie zur Build-Zeit in die Assets der Website herunter und schreibt die Verweise um – so verdirbt ein CMS-Asset niemals einen statischen Build. API-Aufrufe werden über einen kleinen Request-Pool getaktet (3 gleichzeitig, passend zum Rate-Limit pro Integration bei Notion), sodass sich Datenbanken mit Hunderten von Seiten importieren lassen, ohne `429`-Antworten auszulösen; setze `concurrency` an der Quelle, um das anzupassen.

## Preview und Sync [#preview-and-sync]

Zwei Flags steuern, wie entfernte Inhalte abgerufen werden und was enthalten ist:

- **`--preview`** bei `blume dev` oder `blume build` rendert Entwürfe und lädt unveröffentlichte CMS-Inhalte – Sanity wechselt in seine `previewDrafts`-Perspektive, und Notion filtert nicht mehr nach `Status`. Produktions-Builds ohne das Flag schließen Entwürfe wie gewohnt aus, sodass ein Preview-Build eine sichere Möglichkeit ist, unveröffentlichte Arbeit vor der Auslieferung zu prüfen.
- **`blume sync`** ruft jede entfernte Quelle erneut ab und generiert die Runtime neu. Die Entwicklung ist cache-first – eine entfernte Quelle wird einmal abgerufen und beim Neustart aus `.blume/cache` ausgeliefert (schnell und offline-tolerant), daher ist `blume sync` der Weg, die neuesten CMS-Inhalte zu holen, ohne den Dev-Server neu zu starten (ein laufender Server lädt per Hot-Reload neu). Ergänze `--force`, um zuerst den Cache zu verwerfen, oder setze `pollInterval` an einer Quelle, um automatisch zu aktualisieren.

```sh
blume dev --preview      # author workflow: see drafts live
blume build --preview    # render a full preview build
blume sync               # refresh remote content now
blume sync --force       # ...ignoring any cached snapshot
```

## Eigene Quellen [#custom-sources]

Jedes Objekt, das die `ContentSource`-Schnittstelle implementiert, kann direkt übergeben werden. So lässt sich ein Adapter mit eigenen Serializern – oder ein beliebiges nicht eingebautes Backend – einbinden, ohne dass dessen SDK die Kerninstallation berührt:

```ts blume.config.ts
import { defineConfig } from "blume";
import { sanitySource } from "blume/sources/sanity.ts";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "custom",
        source: sanitySource({
          name: "guides",
          prefix: "guides",
          projectId: "abc123",
          dataset: "production",
          query: `*[_type == "guide"]`,
          // Map custom Portable Text blocks to Blume components
          serializers: {
            callout: (block) => `<Callout>${block.text}</Callout>`,
          },
        }),
      },
    ],
  },
});
```

Eine Quelle normalisiert ihre native Form (Portable Text, Notion-Blöcke, entferntes HTML) zu Markdown-/MDX-Text, sodass dieselben Komponenten und Markdown-Funktionen gelten, ganz gleich, woher eine Seite stammt.

Eine eigene Quelle, die lokale Dateien liest, sollte an jedem Eintrag `sourcePath` und an der Quelle selbst `contentRoot` setzen. `sourcePath` benennt die Datei in Diagnosemeldungen und löst relative Bilder daneben auf; `contentRoot` grenzt das `log` von Git ein, das Seiten datiert – ohne die Option erhalten die Seiten der Quelle also kein aus Git abgeleitetes [„Zuletzt aktualisiert“-Datum](/docs/configuration#last-modified).
