I'm moving Logixlysia's documentation from Next.js and Fumadocs to Blume, a zero-config, Markdown-first docs framework built on Astro. This is the decision log, written mid-migration: the maintenance math behind leaving a docs-as-app stack, the AI-readability endpoints that motivated the switch, what the actual migration PR contains, and the sharp edges I'm watching.
I maintain a logging plugin for Elysia. The plugin is a TypeScript library. Its documentation site is a Next.js application: an app directory, a source.config.ts, hand-written route handlers for search, RSS, OG images and llms-full.txt, and a dependency list that includes a chart library, a carousel and a toast component — for a site about logging. At some point I compared the code that documents Logixlysia against the code that exists to serve that documentation, and the ratio was upside down.
This is a mid-migration post. On main, Logixlysia's docs still run on Next.js 16 with Fumadocs; the move to Blume sits in an open pull request (PR #347) with a preview deployment, not yet merged. What follows is the reasoning and what the diff actually contains — a decision log, not a victory lap.
The docs-as-app problem
None of this is a complaint about Fumadocs. It's genuinely good software, and the fastest way I know to get polished docs inside a Next.js app, with search, MDX tooling and a solid component set. But that sentence carries the cost: inside a Next.js app. Fumadocs hands you components and a content loader; you own the application around them.
Owning the application is the right trade for a product team, where control over every route and pixel is the point. For a one-maintainer OSS library it inverts. Logixlysia's docs app tracks next 16, react 19, fumadocs-ui and fumadocs-mdx as four separately versioned moving parts, and its builds break for reasons that have nothing to do with logging. Every hour spent keeping the docs app upgraded is an hour not spent on the library people actually install.
What Blume actually is
Blume is Hayden Bleasel's documentation framework: MIT-licensed, Markdown-first, built on Astro and Vite. It went public in July 2026, days before this migration started, which tells you something about my risk appetite. The pitch that got me is "the framework is the template": you point the CLI at a folder of Markdown and it generates and drives the entire site.
Full disclosure on a bias: part of the pull is the author. I've followed Hayden Bleasel's open-source work for years, and some of it is already load-bearing for me — his Ultracite preset has linted Logixlysia itself from the start, and it lints the site you're reading this on too. A zero-config framework is an easier bet when it comes from someone whose zero-config tooling you already run every day. Blume's own tagline is "Fast, AI-ready, and zero-config. Drop Markdown into a folder and ship a production-grade docs site. Free and open source, forever." Normally I'd discount a sentence like that to nothing; this post is, in effect, me auditing it clause by clause.
Mechanically, blume dev reads a blume.config.ts, scans the content folder into a content graph, then writes a hidden Astro project under .blume/ and drives it for dev and build. You never touch the generated app. The output is static HTML with no client-framework JavaScript in the core theme, so there's no hydration bill to pay before a reader sees the install command.
Here's the entire configuration the new Logixlysia docs run on:
import { defineConfig } from 'blume'
export default defineConfig({
title: 'Logixlysia',
description:
'The logger for Elysia.js — simple and easy to use, beautiful and powerful',
logo: {
image: '/icon.png',
text: 'Logixlysia',
href: '/'
},
content: {
sources: [
{ prefix: 'docs', root: 'content', type: 'filesystem' },
// Logixlysia's GitHub releases become the changelog timeline at /changelog
// (each release is a type:changelog entry). Set GITHUB_TOKEN in CI to
// avoid rate limits; a failed fetch degrades to an empty changelog.
{
type: 'github-releases',
prefix: 'changelog',
owner: 'PunGrumpy',
repo: 'logixlysia'
}
]
},
github: {
owner: 'PunGrumpy',
repo: 'logixlysia'
},
navigation: {
tabs: [
{ label: 'Docs', path: '/docs' },
{ label: 'Changelog', path: '/changelog' }
],
repo: true
},
theme: {
background: {
light: 'oklch(1 0 0)',
dark: 'oklch(0.14 0 0)'
},
accent: '#b24b0a',
fonts: {
body: 'geist',
mono: 'geist-mono'
}
},
markdown: {
codeBlocks: {
theme: {
light: 'github-light',
dark: 'vesper'
}
}
},
deployment: {
adapter: 'vercel'
}
})That file and the content folder are most of the application surface; the one exception, a custom landing page, comes later. The docs workspace's package.json lists one runtime dependency after the migration: blume. Before, it listed twenty-nine, from next and react down through recharts, embla-carousel-react and sonner.
| Fumadocs | Blume | |
|---|---|---|
| What you maintain | A Next.js app plus your content | A content folder and one config file |
| Rendering | React app with server and client components | Static HTML from a generated Astro project |
| AI endpoints | Hand-rolled route handlers | llms.txt, raw .md URLs and an MCP server built in |
| Customization | Unlimited — it is your app | Config, then custom Astro pages, then eject |
If you've ejected from create-react-app, you know this trade
The .blume/ directory is the same move create-react-app made with its hidden webpack config: the framework owns the machinery, and you get a hatch. blume eject produces a standalone Astro project that you own from then on. Blume softens the cliff a little, because the ejected project still consumes the blume package rather than dumping raw generated code on you.
The failure mode is familiar too. The moment your needs outgrow the config surface, you're one command away from owning a whole application again — the exact situation I'm migrating away from. Eject is a one-way door, and I plan to treat it like one.
The AI part is the actual reason
Here's the shift that made this worth a migration rather than a shrug: the median reader of a library's documentation is increasingly not a person. When someone wires Logixlysia into an Elysia app today, there's a decent chance the first thing to read the docs is Claude Code or Cursor, sent there by a human who asked for request logging with nice colors. A docs site that only speaks rendered HTML makes that agent scrape a React page to find one config table.
Blume treats machine readers as a first-class audience. Every page serves raw Markdown when you append .md to its URL. The build emits llms.txt and llms-full.txt. Each page carries copy-as-Markdown and open-in-chat actions. And you can host an MCP server exposing four read-only tools (search_docs, get_page, list_pages, get_navigation) so agents query the docs instead of scraping them.
None of this was impossible on the old stack — I know because I built it. The current app has hand-written route handlers for llms-full.txt, RSS and OG images, plus an /api/search endpoint. The difference is who maintains them. On Blume, every one of those endpoints is an output of the build rather than code in my repo.
What the diff actually contains
At the time of writing, PR #347 stands at 3,631 additions and 11,361 deletions across 163 files — and it's still moving; a couple of details below changed while I was drafting this post. The shape of the work:
- The URL strategy, twice. Fumadocs kept root pages in a
content/(docs)/route group. The first pass flattened it so every existing URL survived unchanged; a later commit moved the docs under a/docstab with/changelogbeside it, which reads better as navigation but turns URL preservation into a redirect problem. The map from the old root paths (/introduction,/features/*,/integrations/*) is still open work as I write this, and years of GitHub issue links point at those paths. - Ordering metadata. Fumadocs'
meta.jsonfiles become Blume'smeta.tswithdefineMeta: same page lists, now type-checked. - The content barely moves.
introduction.mdxis byte-identical before and after — same frontmatter keys, same Markdown. Logixlysia's docs never leaned on Fumadocs' MDX components, so there was no Tabs-and-Callouts conversion pass; if your docs use them heavily, budget for one. - The landing page is the one custom thing. The marketing page (hero, live release banner, code demo) was a React page; it's rebuilt as a Blume custom page —
pages/index.astroplus a folder of Astro components for the hero, the release banner and the copy-to-install button. No eject required. - The changelog becomes a content source. Blume can treat GitHub Releases as content, so
/changelogand its RSS feed are now generated from releases instead of maintained by hand. - Everything else is deletion: the app directory, the shadcn/radix component stack, the env validation, the analytics provider and every hand-rolled route handler.
What's absent from that list is the part I expected to budget for: no layout re-implementation, no component rewrite, no framework upgrade. Most of the diff is red.
Sharp edges I'm watching
An open PR with a green preview is exactly when the brochure looks most convincing, so here is the honest watch-list.
- The project is weeks old. Blume went public this month and is moving fast. The PR currently declares
"blume": "latest"inpackage.json, which is less a version constraint than a statement of trust; a real pin lands before merge, and release notes get read before every bump. - Static means stale by default. The changelog is sourced from GitHub Releases at build time, so a new release doesn't appear until something rebuilds the site. The fix is a release-published workflow hitting a Vercel deploy hook — fine, but that's a new moving part that exists only because the site is static.
- Not everything ported. The interactive homepage playground and the background video didn't survive the first pass; the plan is to bring the playground back as a React island. Analytics went out with the Next.js provider too. Migration diffs this red usually hide a few things you meant to keep.
- The eject cliff, still. Custom Astro pages covered the landing page, but the customization ladder ends at
blume eject, and past that point I own a generated Astro app I didn't design. Nothing on the roadmap needs it; that's what everyone says before they eject. - Debugging generated code. When a build fails, the stack trace points into
.blume/— code I didn't write. That's the flip side of every framework with a hidden compile step.
Why this matters beyond one logger
Logixlysia is a small library, and that's the point. Small OSS lives or dies on maintenance budget, and documentation is the first thing to rot when the budget runs out. A docs stack that demands a framework upgrade every few months is a docs stack that eventually stops getting upgraded; the graveyard of stale OSS docs sites across GitHub Pages is full of exactly that failure.

I'll write the follow-up once the PR merges and survives a real release cycle, sharp edges included. But the direction is easy to state: Logixlysia's documentation is turning back into what it always was underneath — a folder of Markdown that anyone, human or agent, can just read.