Skip to content

Markdown docs, built into your app.

Collections, a page tree, typed frontmatter and search, compiled into Next.js

Works with Next.js SvelteKit TanStack Start
~/my-app

$ pnpm dlx @docvia/cli init

┌ Add docs to your app

│

◇ my-app

│ framework Next.js

│ packages pnpm (lockfile)

│ content content/docs

│ routes app/docs/

│

◇ 13 file(s)

│ + content/docs/guides/meta.json

│ + content/docs/guides/writing.md

│ + content/docs/index.md

│ + lib/source.ts

│ + components/docs-tree.tsx

│ + components/docvia-client.tsx

│ + app/api/search/route.ts

│ + app/docs/layout.tsx

│ + app/docs/[[...slug]]/page.tsx

│ + components/docs-sidebar.tsx

│ + app/docs/docs.css

│ + next.config.ts (updated)

│ + app/globals.css (Tailwind skips content/)

│

◇ Installed with pnpm in 17.08s

│

◇ Next

│ pnpm dev then open http://localhost:3000/docs

│

└ Done in 18.02s

docvia is a docs framework with a framework-agnostic core. Write Markdown and it becomes a typed content source with a page tree, search and highlighting, rendered by React or Svelte inside the app you already ship. No second site to host, no Markdown parser in production.

01

why docvia.

Docs that build, type-check and deploy like the rest of your app.

  1. 01 read Parallel directory walk, xxhash per file
  2. 02 parse remark and rehype, one cached processor
  3. 03 sanitize Allow-list sanitizer, scripts and iframes dropped
  4. 04 transform One pass into a framework-agnostic IR
  5. 05 cache In dev, an unchanged file skips every step above
  6. 06 emit Renderer adapter emits one module per page

Compiled ahead of time

Your bundler turns every Markdown file into a module. Nothing parses Markdown in production.

docvia.config.ts
import { defineConfig } from "@docvia/build/vite";
import { createReactRenderer } from "@docvia/core/react";
import { shiki } from "@docvia/plugin-shiki";

export default defineConfig({
  renderer: createReactRenderer(),
  plugins: [
    shiki({ theme: "github-dark", langs: ["typescript", "bash", "json"] }),
  ],
});
typescript · 10 lines typed

Zero config to start

init detects your renderer and Shiki. Add a typed config file only when you want to change them.

Click a file to edit it.

Instant edits in dev

Edit a page and only that page compiles again: under 70 ms at 300 pages.

src/lib/source.ts
import { loader } from "@docvia/core/source";
import { defineDocs } from "@docvia/core/source/macro";
import { z } from "zod";

const docs = defineDocs({
  dir: "content/docs",
  // Validated at compile time; page.data is typed from it.
  docs: { schema: z.object({ publishedAt: z.coerce.date().optional() }) },
});

export const source = loader({ baseUrl: "/docs", source: docs.toDocviaSource() });
typescript · 11 lines typed

Typed from your schema

Zod, Valibot or ArkType check frontmatter at build, and page.data is typed from the same schema.

02

bring your framework.

One compiled tree, rendered by React or Svelte. The adapter contract is small enough to write your own.

app/docs/[[...slug]]/page.tsx
import { Renderer } from "@docvia/core/react";
import { notFound } from "next/navigation";
import { source } from "@/lib/source";

export default async function DocPage({ params }) {
  const page = source.getPage((await params).slug);
  if (!page) notFound();

  // No parser, no highlighter, just a compiled module.
  const { content } = await page.data.load();
  return <Renderer nodes={content} />;
}
tsx · 12 lines typed

React

@docvia/core/react

src/routes/docs/[...slug]/+page.svelte
<script lang="ts">
  import { Renderer } from "@docvia/core/svelte";
  import type { PageProps } from "./$types";

  let { data }: PageProps = $props();
</script>

<!-- Same compiled IR the React adapter renders. -->
<article>
  <Renderer nodes={data.content} />
</article>
svelte · 11 lines typed

Svelte

@docvia/core/svelte

03

numbers.

docvia against fumadocs, VitePress, Starlight, Nextra and Docusaurus on the same Markdown pages, same machine, best of 2 runs. Lower is better.

  • docvia
  • Other docs tools
  • 300 pages, docvia on SvelteKit, Next.js and TanStack Start
Production build

No data.

Production build
Tool
Starlight
docvia TanStack Start
docvia SvelteKit
docvia Next.js
VitePress
Nextra
Docusaurus

Production build

Seconds to prerender every page.

Cold dev start to first page

No data.

Cold dev start to first page
Tool
VitePress
docvia TanStack Start
docvia SvelteKit
Starlight
docvia Next.js
Docusaurus
Nextra

Cold dev start to first page

Seconds until the page is visible in a browser.

Edit to visible, dev

No data.

Edit to visible, dev
Tool
VitePress
docvia TanStack Start
docvia SvelteKit
docvia Next.js
Starlight
Nextra
Docusaurus

Edit to visible, dev

Milliseconds from saving a page to the open tab showing it.

docvia and fumadocs in depth: Vite SSR and Next.js, up to 1,500 pages

  • fumadocs
  • docvia + React
  • docvia + Svelte
Production build time

No data.

Production build time
Pages
300 pages
1,500 pages

Production build, Vite SSR

Seconds to build every page.

Edit to visible

No data.

Edit to visible
Pages
300 pages
1,500 pages

Edit to visible, dev

Milliseconds from saving a page to seeing it.

Next.js build time

No data.

Next.js build time
Pages
300 pages
1,500 pages

Production build, Next.js

next build with Turbopack, seconds.

Metricfumadocsdocvia + Reactdocvia + Svelte
Dev server ready404 ms94 ms710 ms
Cold start to first page7.3 s4.0 s3.5 s
Edit to visible702 ms261 ms292 ms
Production SSR build21.8 s17.5 s16.8 s
Server bundle cold import97 ms89 ms32 ms
Heap after import13 MB10 MB9 MB
Render every page, warm365 ms72 ms78 ms
HTML for every page8,300 KB4,559 KB5,633 KB

All measurements at 1,500 pages

Vite 8.3.4 SSR. The best value in each row is highlighted.

fumadocs-mdx 15.4.7 + fumadocs-core 16.17.1, Next 16.4.0. VitePress 1.6.4, Starlight 0.42.6, Nextra 4.6.1, Docusaurus 3.10.2, SvelteKit 3.0.1, TanStack Start 1.168.61; search off and highlighting on for all. AMD Ryzen 7 7435HS, 16 GB, Node 22.20.0, 2026-10-10. Synthetic pages with headings, lists, tables and code.

04

start in one command.

Run init in an app you already have. It finds your framework, writes the docs routes and installs what they need.

$ npx @docvia/cli init

framework Next.js

packages pnpm (lockfile)

content content/docs

routes app/docs/

13 file(s)

+ app/docs/[[...slug]]/page.tsx

+ content/docs/index.md

...

pnpm dev then open http://localhost:3000/docs

init writes the routes and patches your config

Output from a fresh create-next-app.

  • Next.js build/next, webpack and Turbopack
  • SvelteKit build/vite
  • TanStack Start build/vite
  • Standalone docvia build, no framework

Where it runs

Rendered with React or Svelte.

05

built for workers.

A Worker isolate gets 128 MB and pays for every cold start. docvia keeps page bodies out of the startup path, so docs fit with room to spare.

startup

frontmatter index, 1,500 entries

on request, one chunk per page

96 of 1,500 chunks shown

Lazy page bodies

The server bundle eagerly loads a small frontmatter index. Each page body is its own chunk, loaded when that page is requested.

fumadocs 13 MB · 97 ms
docvia + React 10 MB · 89 ms
docvia + Svelte 9 MB · 32 ms

1,500 pages, 128 MB limit

Heap after importing the server bundle, and the cold import time. Bars are to scale.

src/routes/api/search/+server.ts
import { createFromSource, createSearchHandler } from "@docvia/search";
import { source } from "$lib/source";

// Built once per Worker isolate from compile-time data. No filesystem, no index file.
let handler: Promise<(request: Request) => Promise<Response>> | undefined;

export const GET = async ({ request }) =>
  (await (handler ??= createFromSource(source).then(createSearchHandler)))(request);
typescript · 8 lines typed

Search on the edge

A standard Request handler. The index is built in memory from data compiled into the bundle.

  • No createRequire or yaml in the server bundle, checked in CI
  • Highlighting is baked in at build, so no Shiki at runtime
  • Search builds once per isolate from compiled data
  • Docs pages can prerender to static assets

What keeps it small

Constraints in the design, not deploy-time workarounds.

06

what's in the box.

8 packages on npm, each MIT licensed. Most apps install four, and init chooses them.

07

questions.

If yours is missing, open a discussion on GitHub.

Not for your docs. Your bundler compiles Markdown ahead of time, and Shiki highlighting is baked into the output, so neither a parser nor a highlighter reaches the browser or the server bundle.
Use @docvia/markdown. It is a separate, dependency-free package (about 20 KB minified) that renders Markdown in the browser or on a server, streams AI answers as they arrive, and works in any app without the rest of docvia.
Next.js, SvelteKit and TanStack Start, rendered with React or Svelte through the same Renderer component. The compiled tree is framework-agnostic, so another renderer only has to implement the adapter in @docvia/core/render.
Yes. docvia init detects your package manager from the lockfile and installs with it. It was tested by creating and building Next.js, SvelteKit and TanStack Start apps with each one.
Yes, through directives such as :::callout, mapped to components in your config, with optional hydration. docvia does not compile MDX.
Yes. @docvia/plugin-openapi turns an OpenAPI or Swagger spec into reference pages with code samples, and its proxy lets readers send real requests without CORS errors.
Any Standard Schema library: Zod, Valibot, ArkType and others. A missing or wrong field fails the build, and page.data is typed from the schema.
Yes. Page bodies load lazily, so at 1,500 pages the server bundle imports in under 100 ms with about 10 MB of heap. This site builds for Workers with adapter-cloudflare.
Sometimes. The numbers section compares them on the same pages: Starlight builds a little faster and VitePress shows the first page and edits sooner, while docvia builds faster and shows pages and edits sooner than Nextra and Docusaurus. docvia's difference is that docs live inside the app you already ship.
Yes. Every package is MIT licensed, there is no hosted service and nothing phones home.