Skip to content
packages

@docvia/core/source

Declare docs collections in code and query them with loader().

On this page

@docvia/core/source is how an app reads its docs. Declare a collection with defineDocs() from @docvia/core/source/macro, then pass it to loader() for page lookup, the page tree and static params. The API mirrors fumadocs (fumadocs-mdx/macro + fumadocs-core/source).

The docvia bundler plugin (docvia() for Vite, withDocvia() for Next.js) rewrites the macro calls at build time into an index of the folder's pages. Nothing is scanned at runtime, and page bodies compile lazily, in memory, on first load().

Install

pnpm add @docvia/core

Package exports

SubpathContents
.loader() and its types (Source, Page, MetaData, LoaderOutput), plus PageTree, RenderOutput, ComponentRegistry, HydrationManifest, StructuredData.
./macrodefineDocs(), defineRegistry(), BaseFrontmatter, LoadedPage, TocItem.
./runtimeTypes only (PageTree, legacy docviaCollection types).
./macro-runtime, ./internalUsed by generated code. Not public API.

Declare a collection

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",
  // optional: extra frontmatter fields, any Standard Schema lib
  docs: { schema: z.object({ author: z.string().optional() }) },
});

export const source = loader({ baseUrl: "/docs", source: docs.toDocviaSource() });
OptionTypePurpose
dirstringContent directory, relative to the project root. Must be a string literal.
docs.schemaStandard SchemaExtra frontmatter fields (Zod, Valibot, ArkType), merged with the built-ins.

Every page's frontmatter includes title, description, tags, draft, optional order and slug, plus your schema's fields, all typed.

In Vite, any module that imports @docvia/core/source/macro is transformed. In Next.js only files named in macroFiles are (default source.{ts,tsx,js,mjs} and registry.{ts,tsx,js}). Calling defineDocs() without the plugin throws.

Component registry

lib/registry.ts
import { defineRegistry } from "@docvia/core/source/macro";

// Components from `components` in docvia.config.ts.
export const registry = defineRegistry();

Pass registry to <Renderer> (Svelte) or <Renderer> (React).

loader()

function loader<D>(options: { baseUrl: string; source: Source<D> }): LoaderOutput<D>;
MemberReturnsBehavior
getPage(slugs?)Page | undefinedSynchronous lookup by slug segments. [] or undefined is the index page.
getPages()Page[]Every page, frontmatter only.
pageTree / getPageTree()PageTree.RootNavigation tree, built once and cached.
getPageByHref(href){ page, hash? } | undefinedResolves a link like /docs/guide#setup.
generateParams(key?)Record<key, string[]>[]Static params, keyed slug by default.
serializePageTree(tree)Promise<PageTree.Root>Identity (fumadocs parity: the tree already holds plain strings).

A Page has slugs, url, path (relative to the collection directory) and data. For a defineDocs() source, data is the frontmatter plus load():

const page = source.getPage(slugs); // sync, frontmatter only
if (!page) notFound();
const { content, toc, headings, manifest, structuredData } = await page.data.load();
load() fieldMeaning
contentRender tree for <Renderer nodes> / <Renderer nodes>.
toc{ title, url: "#id", depth }[]
headings{ depth, text, id }[], the h2 to h6 outline.
manifestIsland hydration manifest.
structuredDataSearch sections, extracted at compile time. Used by createFromSource().

load() is memoised per page, so repeated calls share one compile. Bodies stay out of the index, which keeps SSR cold start small on Cloudflare Workers.

loader() also accepts any hand-built Source: { files: Array<PageFile | MetaFile> }.

meta.json

A meta.json in a content folder controls that folder's tree node:

content/docs/guide/meta.json
{
  "title": "Guide",
  "pages": ["install", "---Basics---", "...", "!drafts", "[GitHub](https://github.com/kanakkholwal/docvia)"],
  "defaultOpen": true
}
FieldMeaning
titleFolder name in the tree. Defaults to the folder's index.md title.
pagesChild order: names, ... (the rest, sorted by order then name), ---Label--- (separator), !name (exclude), [Text](url) (link).
defaultOpenFolder starts expanded.
rootFolder starts its own sidebar.

Without meta.json, children sort by frontmatter order, then name.

namespace PageTree

namespace PageTree {
  interface Root { name: string; children: Node[] }
  interface Item { type: "page"; name: string; url: string; external?: boolean; $id?: string }
  interface Folder {
    type: "folder";
    name: string;
    children: Node[];
    index?: Item;
    defaultOpen?: boolean;
    root?: boolean;
    $id?: string;
  }
  interface Separator { type: "separator"; name: string }
  type Node = Item | Folder | Separator;
}

Legacy config collections

Collections declared in docvia.config.ts (collections, frontmatter, sourceDir) are still served as virtual:docvia/source (Vite) and docvia/source (Next), typed by docviaCollection in ./runtime, whose getPage() is async. Prefer defineDocs() for new projects.