Skip to content
packages

@docvia/build/next

Next.js integration: compiles defineDocs() collections and Markdown pages for webpack and Turbopack.

On this page

@docvia/build/next integrates docvia with Next.js. withDocvia() wraps your next.config and registers two loaders for both webpack and Turbopack: one rewrites defineDocs() / defineRegistry() calls into an index of your pages, the other compiles each Markdown page lazily when page.data.load() first imports it. No .docvia/ folder is written.

Install

pnpm add -D @docvia/build
pnpm add @docvia/core

Next.js is a peer dependency: next >= 14. The generated code imports @docvia/core/source, so install it in your app.

Package exports

SubpathContents
.withDocvia, defineConfig, DocviaNextOptions.
./loaderThe .md?docvia loader.
./macro-loaderThe defineDocs() / defineRegistry() loader.
./package.jsonPackage metadata.

Import defineConfig from here in docvia.config.ts.

API reference

interface DocviaNextOptions

interface DocviaNextOptions {
  configPath?: string;
  macroFiles?: string[];
}
FieldTypeDefaultMeaning
configPathstring"./docvia.config.ts"Path to the docvia config file.
macroFilesstring[]source.{ts,tsx,js,mjs}, registry.{ts,tsx,js}File names that may call defineDocs() / defineRegistry().

Only files with these names are transformed (Turbopack matches rules by file name). A macro call in any other file throws at runtime.

withDocvia

function withDocvia(
  options?: DocviaNextOptions,
): (nextConfig?: NextConfig) => (phase: string, context: unknown) => Promise<NextConfig>;

A curried wrapper for next.config. Calling withDocvia() returns a function that takes your existing NextConfig (or a config function); that returns the async (phase, context) function Next.js expects.

The returned config adds:

  • Macro loader. A pre webpack rule (before SWC) and a turbopack.rules entry per name in macroFiles.
  • Markdown loader. A .md?docvia webpack rule and a *.md Turbopack rule. Each page compiles in memory on first import, keyed by content hash.

An existing webpack() hook or turbopack block on your config is preserved and composed.

Usage

next.config.ts

next.config.ts
import { withDocvia } from "@docvia/build/next";

export default withDocvia()({ reactStrictMode: true });

Declare the collection

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

const docs = defineDocs({ dir: "content/docs" });

export const source = loader({ baseUrl: "/docs", source: docs.toDocviaSource() });
lib/registry.ts
import { defineRegistry } from "@docvia/core/source/macro";

export const registry = defineRegistry();

Render a page

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

export function generateStaticParams() {
  return source.generateParams();
}

export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {
  const { slug } = await params;
  const page = source.getPage(slug);
  if (!page) notFound();
  const { content } = await page.data.load();
  return <Renderer nodes={content} registry={registry} />;
}

Custom file names

export default withDocvia({ macroFiles: ["docs-source.ts", "registry.ts"] })();

Legacy config collections. When docvia.config.ts declares collections (or no macro file exists), withDocvia() instead compiles everything up front into .docvia/ under a cross-process lock, watches it in dev, and aliases docvia/source, docvia/source/browser and docvia/registry to it.