Auf Blume 2 upgraden
Hebe eine Blume-1-Site mit einem Befehl auf Blume 2 und nutze dann diesen Leitfaden für jede Konfigurationsänderung – oder überlass das gesamte Upgrade Codex oder Claude Code.
Blume 2 ändert die Konfiguration, nicht die Inhalte: Deine Markdown- und MDX-Seiten brauchen keine Anpassungen (unter Frontmatter findest du ein Feld, das jetzt etwas bewirkt). Einstellungen, die früher ein benannter String oder ein Block mit Schlüssel waren – der Suchanbieter, das Deployment-Ziel, Content-Quellen, API-Referenzen, Analytics und das Modell-Backend des Assistenten –, sind jetzt Adapter, die du aus einem blume/*-Subpath importierst und aufrufst. Ask AI heißt jetzt Assistent, daher wird ai.ask zu ai.assistant. Die maschinenlesbaren Einstellungen wandern von ai in einen neuen Schlüssel agents, und Überschreibungen in components.ts werden vor dem Build geprüft. Eine Site ohne Konfiguration oder eine, die nichts davon setzt, braucht nur das Versions-Update.
Mit einem Befehl upgraden
Führe das Upgrade in deinem Projekt aus, also im Ordner mit blume.config.ts:
npx blume@latest upgradepnpm dlx blume@latest upgradeyarn dlx blume@latest upgradebunx blume@latest upgradenubx blume@latest upgradeaube dlx blume@latest upgradeDer Befehl hebt blume in deiner package.json auf 2 an, installiert es mit dem Paketmanager, den dein Projekt nutzt, und prüft dann deine Konfiguration und components.ts gegen Blume 2. Jede noch nötige Änderung wird mit Datei, Zeile und Ersatz aufgelistet – einschließlich package.json-Scripts, die noch die entfernten blume build-Flags übergeben –, und der Befehl endet mit einem Exit-Code ungleich null, bis keine mehr übrig ist. Führst du ihn in einem Ordner ohne Konfiguration und ohne blume-Abhängigkeit aus, bricht er stattdessen mit einem Fehler ab. Starte ihn über npx blume@latest statt über blume: Der Befehl kommt mit Blume 2, ein Projekt auf Version 1 hat ihn also noch nicht. Unter pnpm 12 ergänzt du --allow-build=esbuild nach pnpm dlx, da pnpm 12 das Installationsskript von esbuild nicht ohne Freigabe ausführt.
Wenn du die Änderungen stattdessen einem Coding-Agent überlassen willst, ergänze --codex oder --claude:
npx blume@latest upgrade --codexpnpm dlx blume@latest upgrade --codexyarn dlx blume@latest upgrade --codexbunx blume@latest upgrade --codexnubx blume@latest upgrade --codexaube dlx blume@latest upgrade --codexDer Agent startet interaktiv mit den Befunden und diesem Leitfaden, setzt jede Änderung um und führt blume doctor und blume build aus, bis beide durchlaufen – so prüfst du jede Bearbeitung über seinen eigenen Freigabeablauf. Übergib --no-install, um package.json anzuheben, ohne zu installieren.
Die folgenden Abschnitte behandeln jede Änderung – zum Upgraden von Hand oder um zu prüfen, was der Agent gemacht hat.
Suche
search nimmt einen Adapter aus blume/search statt eines provider-Strings mit Zugangsdaten-Block. Die standardmäßige lokale Suche braucht keine Änderung.
export default defineConfig({
search: {
provider: "algolia",
algolia: { appId: "APP_ID", indexName: "docs", searchApiKey: "SEARCH_KEY" },
},
});import { defineConfig } from "blume";
import { algolia } from "blume/search";
export default defineConfig({
search: algolia({ appId: "APP_ID", indexName: "docs", apiKey: "SEARCH_KEY" }),
});- Orama Cloud, Typesense und Mixedbread werden genauso auf
oramaCloud(),typesense()undmixedbread()abgebildet. Der reine Such-Key heißt in jedem Adapter, der einen erwartet,apiKey;mixedbread()nimmt statt eines Keys einestoreId, da seine Abfragen auf dem Docs-Server laufen. Alle weiteren Optionen, die du ihm übergibst, werden an den Store-Suchaufruf weitergereicht, bei demtop_kstandardmäßig 8 ist. Admin-Keys bleiben in ihren Umgebungsvariablen (ALGOLIA_ADMIN_API_KEY,ORAMA_PRIVATE_API_KEY,TYPESENSE_ADMIN_API_KEY,MIXEDBREAD_API_KEY). provider: "pagefind"wird zupagefind(), undprovider: "none"wird zusearch: false.- Um
popular-Links oderindexing-Optionen zu behalten, umschließt du den Adapter:search: { provider: algolia({ … }), popular: […] }.
Deployment
deployment nimmt einen Host-Adapter aus blume/deploy statt der Felder adapter und output. site und base wandern in die Optionen des Adapters.
export default defineConfig({
deployment: {
adapter: "vercel",
output: "server",
site: "https://docs.example.com",
},
});import { defineConfig } from "blume";
import { vercel } from "blume/deploy";
export default defineConfig({
deployment: vercel({ site: "https://docs.example.com" }),
});netlify(),cloudflare()undnode()funktionieren genauso. Sobald du einen Host-Adapter angibst, wird auf Server-Output umgeschaltet; übergiboutput: "static", um einen statischen Build mit den Plattformdateien dieses Hosts zu behalten.- Eine Konfiguration, die nur
siteoderbasesetzt, bleibt, wie sie ist:deployment: { site, base }ist weiterhin die statische Form. redirectslesen:name-Segmente und ein abschließendes*als Muster, die auf jedem Host gleich ausgewertet werden (siehe Redirects mit Mustern). Blume 1 hat Muster nie unterstützt und sie unverändert an den jeweiligen Host weitergegeben – prüf also jedesfromoderto, das eines enthält.- Die Flags
--adapter,--outputund--basevonblume buildgibt es nicht mehr; übergibst du eines davon, bricht der Build mit einem Fehler ab, der diedeployment-Einstellung nennt, die es ersetzt. Setze den Adapter inblume.config.tsund gib ihn explizit an: Server-Output wird nicht mehr aus der Umgebung der Plattform abgeleitet. Ein CI-Schritt, der pro Deployment ein anderes--baseübergeben hat, kann es stattdessen in der Konfiguration aus einer Umgebungsvariable lesen.
Die Optionen der einzelnen Adapter findest du unter Deployment.
Content-Quellen
Jeder Eintrag in content.sources ist ein Adapter aus blume/sources statt eines { type }-Objekts.
export default defineConfig({
content: {
sources: [
{ type: "filesystem", root: "content" },
{
type: "github-releases",
owner: "acme",
repo: "sdk",
prefix: "changelog",
},
],
},
});import { defineConfig } from "blume";
import { filesystem, githubReleases } from "blume/sources";
export default defineConfig({
content: {
sources: [
filesystem({ root: "content" }),
githubReleases({ owner: "acme", repo: "sdk", prefix: "changelog" }),
],
},
});mdx-remote,sanity,notionundobsidianwerden zumdxRemote(),sanity(),notion()undobsidian(), wobei alle anderen Felder unverändert in den Aufruf wandern.{ type: "custom", source }wird zucustom(source).content.root,content.includeundcontent.excludesind weiterhin die Kurzform für einen einzelnen Ordner, können aber nicht mehr nebensourcesstehen. Verschiebe sie in denfilesystem()-Eintrag.- Release-Seiten aus
githubReleases()werden jetzt in nur einer Sprache veröffentlicht, eine mehrsprachige Site kopiert sie also nicht mehr unter die URL jeder anderen Locale (/de/changelog/…). Wenn andere Sites auf diese Kopien verlinken, füge Redirects auf die Seiten der Standard-Locale hinzu. - Text, den du in Notion eingibst, wird jetzt so dargestellt, wie er geschrieben ist: Eine Komponente, ein
{oder ein Markdown-Zeichen auf einernotion()-Seite erscheint wie eingegeben, statt als MDX gelesen zu werden. Enthalten deine Notion-Seiten Komponenten oder Markdown, die als Text eingegeben sind, setzmdx: truefür die Quelle.
API-Referenzen
Die Top-Level-Blöcke openapi, asyncapi und graphql werden zu einer einzigen reference-Liste mit Adaptern aus blume/reference. Lass enabled weg.
export default defineConfig({
openapi: { enabled: true, spec: "./openapi.yaml" },
graphql: {
enabled: true,
spec: "./schema.graphql",
endpoint: "https://api.example.com/graphql",
},
});import { defineConfig } from "blume";
import { graphql, openapi } from "blume/reference";
export default defineConfig({
reference: [
openapi({ spec: "./openapi.yaml" }),
graphql({
spec: "./schema.graphql",
endpoint: "https://api.example.com/graphql",
}),
],
});asyncapi: { … }wird zuasyncapi({ … })mit denselben Optionen.- Eine AsyncAPI-Spec in Version 1.x oder 2.x wird weiterhin automatisch nach 3.0 konvertiert, der Konverter ist jetzt aber eine optionale Peer-Dependency: Installiere
@asyncapi/converterin deinem Projekt, sonst schlägt der Build fehl und nennt dir den Installationsbefehl. Eine 3.x-Spec braucht nichts. renderer: "scalar"wird zu einem eigenenscalar({ spec, theme, … })-Eintrag in der Liste, derrouteundsourcesdes Blocks übernimmt.- Ein Block mit
enabled: falsewird einfach aus der Liste weggelassen.
Analytics
Das analytics-Objekt wird zu einer Liste von Adaptern aus blume/analytics.
export default defineConfig({
analytics: {
posthog: { key: "phc_…" },
vercel: true,
},
});import { defineConfig } from "blume";
import { posthog, vercel } from "blume/analytics";
export default defineConfig({
analytics: [posthog({ key: "phc_…" }), vercel()],
});cloudflare: { token } wird zu cloudflare({ token }), und jeder scripts[]-Eintrag wird zu script({ … }).
Assistent
Ask AI heißt jetzt Assistent, und die Konfiguration zieht mit dem Namen um: ai.ask wird zu ai.assistant. Der provider des Assistenten nimmt einen Adapter aus blume/ai, der das Modell und die zugehörigen Felder verwaltet.
export default defineConfig({
ai: {
ask: {
enabled: true,
provider: "openrouter",
model: "anthropic/claude-sonnet-4-5",
reasoning: "none",
},
},
});import { defineConfig } from "blume";
import { openrouter } from "blume/ai";
export default defineConfig({
ai: {
assistant: {
enabled: true,
provider: openrouter({
model: "anthropic/claude-sonnet-4-5",
reasoning: "none",
}),
},
},
});Ein provider-Name aus 1.x wird auf gateway(), openrouter(), llmgateway() oder inkeep() abgebildet und openai-compatible auf openai({ baseUrl, name, model, apiKeyEnv }); alle weiteren findest du in der Adapter-Liste. model, apiKeyEnv, baseUrl, headers und reasoning wandern in den Adapter; enabled, instructions, retrieval, suggestions, cors und endpoint wandern unverändert nach ai.assistant. Lässt du provider weg, wird weiterhin das AI Gateway verwendet.
Die Umbenennung betrifft jeden Namen, in dem Ask AI vorkam:
i18n.ui-Überschreibungen: Die Gruppeaskwird zuassistant, undsearch.askAiundsearch.askAiHintwerden zusearch.assistantundsearch.assistantHint.useAskAIausblume/hookswird zuuseAssistant, samt den TypenUseAssistantundUseAssistantOptions.- Die Prop
askEnabledanPageLayout,RootLayout,Headerund einerSearch-Überschreibung wird zuassistantEnabled. - Im Modul
blume:datawirdconfig.askzuconfig.assistantundui.askzuui.assistant, und der TypUIStringsausblumezieht entsprechend nach. - Die Adapter-Typen aus
blume/aitauschen ihr PräfixAskgegenAssistant(AskAdapterwird zuAssistantAdapter,AskGatewayOptionswird zuAssistantGatewayOptions), undaskReasoningLevelsundAskReasoningausblume/schemawerden zuassistantReasoningLevelsundAssistantReasoning. - Das Window-Event
blume:open-ask-aiwird zublume:open-assistant, und das Attributdata-blume-askan<body>wird zudata-blume-assistant.
Die generierte Route /api/ask und die Analytics-Events ask, ask_answer und ask_error behalten ihre Namen, Aufrufer und Dashboards brauchen also keine Änderung. blume upgrade und blume doctor nennen jeden alten Konfigurationsschlüssel, den sie finden – ai.ask ebenso wie die i18n.ui-Schlüssel –, samt seinem Ersatz.
Hast du schon auf Blume 2.0.0 upgegradet? Diese Version hat noch ai.ask gelesen – aktualisiere blume also wie gewohnt und nimm dieselbe Umbenennung vor.
Agents und weitere Verschiebungen in der Konfiguration
Die maschinenlesbaren Einstellungen wandern von ai in einen neuen Schlüssel agents, und drei kleinere Felder ändern ihre Form.
export default defineConfig({
ai: { mcp: { enabled: true }, skills: "./skills" },
lastModified: true,
markdown: {
codeBlocks: { theme: { light: "github-light", dark: "github-dark" } },
},
theme: { layout: "sidebar" },
});export default defineConfig({
agents: { mcp: { enabled: true }, skills: "./skills" },
lastModified: "git",
markdown: {
code: { theme: { light: "github-light", dark: "github-dark" } },
},
});ai.api,ai.catalog,ai.llmsTxt,ai.markdownComponents,ai.mcp,ai.skills,ai.webBotAuthundai.webmcpwerden zuagents.*, ebensoseo.agentReadabilityundseo.contentSignals. Inaibleiben nurassistant(früherask) undopenInChat.lastModifiedist ein einfacher Wert:truewird zu"git", und{ type: "git" }oder{ type: "frontmatter" }wird zum bloßen String.markdown.codeBlocksgeht inmarkdown.codeauf.theme.layoutentfällt. Es wurde nirgends gelesen, also lösch es.
Frontmatter
Seiten behalten ihr Frontmatter. Ein Feld ändert, was es bewirkt: search.boost. Blume 1 hat es akzeptiert, die Suche hat es aber nie ausgewertet – eine Seite wurde ohne das Feld also genauso gerankt. Jetzt multipliziert es die Suchrelevanz der Seite (siehe Ranking). Prüf jede Seite, die es setzt: Ein Boost, den du irgendwann hinzugefügt und dann vergessen hast, schiebt die Seite jetzt nach oben.
Komponenten-Überschreibungen
Blume 2 prüft jeden Eintrag in components.ts vor dem Build, statt zur Laufzeit auf einen Fallback zurückzugreifen. Jeder mdx- und layout-Eintrag muss eine importierte Komponente, ein Pfad-String oder ein { component, client, media }-Objekt sein, und die Gruppe islands entfällt: Ein mdx-Eintrag mit einem client-Modus ist eine Island.
import { defineComponents } from "blume";
import Counter from "./islands/Counter.tsx";
export default defineComponents({
islands: { Counter },
});import { defineComponents } from "blume";
import Counter from "./islands/Counter.tsx";
export default defineComponents({
mdx: { Counter: { component: Counter, client: "visible" } },
});Eine Inline-Funktion, eine direkt in components.ts deklarierte Komponente, ein Spread oder ein berechneter Schlüssel schlägt jetzt mit BLUME_COMPONENTS_INVALID fehl, wobei der betroffene Eintrag genannt wird. Verschiebe die Komponente in eine eigene Datei und importiere sie. Die Ordnerkonvention islands/ funktioniert wie bisher. Die akzeptierten Formen findest du unter Anpassung.
Apps nach einem Eject
Eine App, die du unter Blume 1 ejectet hast, läuft nicht mehr über die Blume-CLI, hängt aber weiterhin vom Paket blume ab. Ihre Seiten importieren Blumes Komponenten, src/generated/ enthält einen Snapshot deiner Site, den der Generator von Blume 1 geschrieben hat, und astro build lädt blume.config.ts erneut, um den Suchindex, llms.txt und die Sitemap zu schreiben. Hebst du blume darunter auf 2 an, trifft dieser Blume-1-Snapshot auf Blume-2-Komponenten, die die neuen Formen erwarten – ejecte also stattdessen erneut:
Copy the project out
Kopiere alles außer astro.config.mjs, src/, .blume/, dist/ und
node_modules/ in einen leeren Ordner: deine Inhalte, blume.config.ts,
components.ts, islands/, public/, alle Spec-Dateien, die deine
Referenzen einlesen, und package.json. Lass die ejectete App, wie sie ist.
Upgrade the copy
Führe in der Kopie npx blume@latest upgrade aus und setz um, was der
Befehl auflistet, damit blume.config.ts und components.ts gültiges Blume
2 sind. Führe dann npx blume build aus, um sicherzustellen, dass die Site
baut, bevor du sie ejectest.
Eject a fresh copy
Führe npx blume eject --yes in der Kopie aus, installiere die Pakete, die
der Befehl hinzufügt, und baue sie mit npm run build.
Carry your edits across
Vergleiche die frische astro.config.mjs und src/ per Diff mit deiner
ejecteten App und übertrage deine eigenen Änderungen auf die neuen Dateien.
Bis die frische Kopie fertig ist, belässt du die ejectete App auf Blume 1 ("blume": "^1") und führst darin nicht blume upgrade aus: Solange du die Version nicht anhebst, ändert sich nichts.
Kommandozeilen-Flags
Jeder blume-Befehl lehnt jetzt Flags ab, die er nicht kennt – Blume 1 hat sie ignoriert. Ein Script oder CI-Schritt, der ein überflüssiges oder falsch geschriebenes Flag übergibt, schlägt fehl und nennt das nicht erkannte Flag sowie die Flags, die der Befehl akzeptiert. blume upgrade meldet nur die drei entfernten blume build-Flags (--adapter, --output, --base), prüf also auch deine anderen blume-Scripts.
Ergebnis prüfen
Sobald blume upgrade nichts mehr zu ändern meldet, führe die Prüfungen der Site selbst aus:
npx blume doctor
npx blume build
Die vollständige Liste der Änderungen samt Begründung findest du im Changelog.