Inhaltsquellen
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
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.
import { defineConfig } from "blume";
export default defineConfig({
content: { root: "docs" },
});
Mehrere Quellen
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.
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-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.
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 akzeptiert, plus jeden Schlüssel, den du in frontmatter.extend 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 () 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. „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 unter /fr/ veröffentlicht, v1.0/Note.md bei Versionen unter /v1.0/, und Wikilinks auf diese Notizen zeigen auf die Route, die die jeweilige Notiz veröffentlicht.
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
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:
{
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
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 liefert die Veröffentlichung eines GitHub-Releases direkt einen Changelog-Eintrag.
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.
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 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.
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
Zwei Flags steuern, wie entfernte Inhalte abgerufen werden und was enthalten ist:
--previewbeiblume devoderblume buildrendert Entwürfe und lädt unveröffentlichte CMS-Inhalte – Sanity wechselt in seinepreviewDrafts-Perspektive, und Notion filtert nicht mehr nachStatus. 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 syncruft 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/cacheausgeliefert (schnell und offline-tolerant), daher istblume syncder 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 setzepollIntervalan einer Quelle, um automatisch zu aktualisieren.
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
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:
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.