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 Claude Code oder Codex.
Blume 2 ändert die Konfiguration, nicht die Inhalte: Deine Markdown- und MDX-Seiten brauchen keine Anpassungen, es sei denn, eine davon setzt das entfernte Frontmatter-Feld search.boost (siehe Frontmatter). 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 Ask-AI-Backend –, sind jetzt Adapter, die du aus einem blume/*-Subpath importierst und aufrufst. 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 --claude oder --codex:
npx blume@latest upgrade --claudepnpm dlx blume@latest upgrade --claudeyarn dlx blume@latest upgrade --claudebunx blume@latest upgrade --claudenubx blume@latest upgrade --claudeaube dlx blume@latest upgrade --claudeDer 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. redirectserwarten exakte Pfade. Einfromodertomit einem:param-Segment oder einer*-Wildcard besteht die Validierung jetzt nicht mehr; Blume 1 hat Muster nie unterstützt, und Hosts haben sie unterschiedlich behandelt. Verschiebe Regeln mit Mustern in die eigene Konfiguration deines Hosts (vercel.json,_redirects).- 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.
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.
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({ … }).
Ask AI
ai.ask.provider 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: {
ask: {
enabled: true,
provider: openrouter({
model: "anthropic/claude-sonnet-4-5",
reasoning: "none",
}),
},
},
});Die Adapter sind gateway(), openrouter(), llmgateway(), inkeep() und openaiCompatible({ baseUrl, name, model, apiKeyEnv }). model, apiKeyEnv, baseUrl, headers und reasoning wandern in den Adapter; enabled, instructions, retrieval, suggestions, cors und endpoint bleiben unter ai.ask. Lässt du provider weg, wird weiterhin das AI Gateway verwendet.
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 nuraskundopenInChat.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
Ein Frontmatter-Feld entfällt: search.boost. Blume 1 hat es akzeptiert, die Suche hat es aber nie ausgewertet – eine Seite wurde ohne das Feld also genauso gerankt. Lösch es überall, wo es vorkommt; eine Seite, die es noch setzt, besteht die Validierung nicht und bekommt einen Hinweis, und blume upgrade listet jedes Vorkommen mit Datei und Zeile auf.
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.