It started as a weekend project I was genuinely excited about.
I had a documentation site running on one of the popular React based doc compilers. You know the ones. Mintlify, Fumadocs, the polished tools everyone reaches for. They look great out of the box and they carry you very far, very fast. I honestly had no complaints. Then I started learning Svelte, fell for it hard, and decided to move my docs into a SvelteKit app.
That is the exact moment the weekend stopped being fun.
The wall
I expected the migration to be tedious. Rewrite some components, swap some imports, fight some config. Normal stuff. What I did not expect was to discover that the documentation compiler itself assumed React all the way down. Not in a "here is a React component you can replace" way. In a "the rendering model and the compiler are the same object" way.
The Markdown parsing, the MDX handling, the syntax highlighting, the navigation tree, all of it was wired straight into a React render tree. There was no clean seam where Markdown stopped being content and started being a component. So moving to Svelte was not a migration. It was a rewrite, and the thing I would have to rewrite was the part I had assumed was neutral: the compiler.
That bothered me for days.
The realization
Here is the thing I finally understood. Framework lock-in in docs tooling is not a config problem you can flag your way out of. It is an architecture decision that got baked in early. The compiler and the renderer were fused, so the framework you rendered with became the framework you were forced to author and build with.
That fusion is invisible while you stay inside one ecosystem. It only shows up the day you try to leave. And by then it is too late to be cheap.
So I asked a simple question. What if parsing Markdown and rendering Markdown were two completely separate jobs, with a typed contract in the middle? Parse once. Render anywhere. React today, Svelte tomorrow, something nobody has invented yet next year.
That question turned into Docvia.
The one decision everything else hangs on
The core idea is an Intermediate Representation, or IR.
Markdown gets parsed, sanitized, and transformed into a typed IR exactly once. The IR is the single source of truth. Renderers are thin adapters whose only job is to turn IR into framework specific output. React is one renderer. Svelte is another. The compiler does not know or care which one you picked.
This sounds almost too obvious written down, but it changes the shape of everything. The moment rendering is a downstream concern, framework agnosticism stops being a marketing word and becomes a structural property. The same docs/ directory can be rendered by the React adapter and the Svelte adapter, and you get the same content out of both. That side by side, same source two frameworks result is the whole thesis of the project in a single screenshot.
The technical decisions I actually had to defend
A clean idea is easy. Living with the consequences is where the real choices are. A few that mattered.
One compile core, three modes, identical output. Build, dev, and server side rendering all sit on the same long lived compile service. Build compiles the whole tree ahead of time. Dev recompiles incrementally inside the framework dev server on every keystroke. SSR renders a single document per request, on Node or on the edge. Because they share one core, the output is byte identical no matter which mode produced it. I did not want a class of bug that only exists because dev and prod ran different code paths. With one core, that class of bug simply cannot exist.
Syntax highlighting happens at build time, not in the browser. Highlighting is a build time plugin. It bakes the highlighted HTML directly into the IR, which means zero highlighter ships to the browser or to the edge bundle. Your readers download highlighted code, not a highlighting engine. This was a deliberate trade. I pay the cost once, at build, so every visitor does not pay it again at runtime.
Frontmatter is typed, not hoped. You extend the built in schema with a Zod object, and Docvia generates a real TypeScript interface for every collection. So your frontmatter is validated at build time and autocompleted in your editor. No more guessing whether a page has a description field and finding out in production that half of them do not.
The bundler plugin runs in process. The recommended setup is not a separate build script you run and forget. It is an in process Vite plugin (and a Next.js wrapper) that drives the compile service inside your bundler, serves the compiled content as a virtual module in dev, and gives you incremental hot module replacement. Nothing gets written to disk while you work. Editing a Markdown file feels exactly like editing a component.
Here is the entire config for a SvelteKit setup:
// docvia.config.ts
import { defineConfig } from "@docvia/cli";
import { createSvelteRenderer } from "@docvia/renderer-svelte/node";
import { shiki } from "@docvia/plugin-shiki";
export default defineConfig({
sourceDir: "docs",
outDir: ".docvia",
renderer: createSvelteRenderer(),
plugins: [shiki({ theme: "github-dark" })],
});
Swap createSvelteRenderer() for createReactRenderer() and the same docs/ folder now renders through React. That swap is the entire point.
Where this honestly stands
Docvia is a v0.2 preview. The architecture is solid and the core ideas have earned their keep, but the APIs are still stabilizing and there will be breaking changes before v1.0. I am putting it out now precisely because I want it shaped by people who hit the same wall I did, not after I have quietly decided everything alone.
If you have ever tried to move documentation between frameworks and felt the floor give way, you already understand why this exists.
If this resonates
I would love two things from you. Try it on a real folder of Markdown and tell me exactly where it breaks, because that is the feedback that actually moves a v0.2 toward a v1.0. And if the IR approach is interesting to you, a star genuinely helps me gauge whether this is worth pushing all the way.
Repo: https://github.com/kanakkholwal/docvia
I will be in the comments. Tell me what you would want before you would trust this with your own docs.
Top comments (0)