# Docs are content again: moving Logixlysia from Fumadocs to Blume

**Published:** July 21, 2026 | **Author:** Noppakorn Kaewsalabnil

---

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](https://logixlysia.vercel.app)'s docs still run on Next.js 16 with [Fumadocs](https://fumadocs.dev); the move to [Blume](https://useblume.dev) sits in an open pull request ([PR #347](https://github.com/PunGrumpy/logixlysia/pull/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](https://github.com/haydenbleasel/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](https://haydenbleasel.com)'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.

**How Blume builds the Logixlysia docs**

```trazo
flow
md[content/*.mdx] --> cli[blume CLI]
cfg[blume.config.ts] --> cli
rel[GitHub Releases] --> cli
cli --> graph[content graph]
graph --> astro[hidden Astro project in .blume/]
astro --> site[static HTML pages]
astro --> ai[llms.txt + raw .md + OG images]
```

Here's the entire configuration the new Logixlysia docs run on:

```typescript apps/docs/blume.config.ts
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`.

> **Search:** Blume runs Orama for local search in dev and production, with Pagefind one flag away for larger sites. Logixlysia's docs are small enough that I don't expect to touch the flag.

|  | 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 `/docs` tab with `/changelog` beside 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.json` files become Blume's `meta.ts` with `defineMeta`: same page lists, now type-checked.
- The content barely moves. `introduction.mdx` is 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.astro` plus 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 `/changelog` and 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"` in `package.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.

![Logixlysia with Blume](https://cdn.sanity.io/images/81161r7g/production/39364cc82792f1cc3b9d902ee1288bbc3e33c57e-1402x992.png?w=1600&fm=webp&q=80&auto=format)

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.

---

**More posts:** [View all blog posts](https://www.pungrumpy.com/blog) | [Site map](https://www.pungrumpy.com/sitemap.md)
