The html generator transforms JSX AST entries into complete web bundles. Its
bundler adapter builds server-rendered HTML and client-side JavaScript, CSS, and
imported assets, then writes the complete static site to output. Vite is the
default adapter, but projects can supply an adapter for webpack or another
bundler. The generator is output-only and does not return an in-memory copy of
its HTML or CSS.
stringstring'template.html'
.stringglobal.project
.string{project}
,
{version}
).
Default:
'{project} v{version} Documentation'
.booleantrue
, all internal links use absolute URLs
based on
baseURL
.
Default:
false
.stringstring'{baseURL}{path}.html'
.stringArraystylesheets
.
Default:
[]
.Object#theme/
aliases to component paths for
customization. See
Default imports
.Object{}
.Objectcomponents
.
Default:
{}
.Objectnavigation
.
Default:
{}
.WebBundlercreateViteBundler()
.The head object controls the project-specific markup injected into the
document <head> (rendered into the template's ${head} placeholder).
Each attribute bag is rendered as a tag: a boolean true becomes a valueless
attribute (e.g. crossorigin), and false/null/undefined attributes are
omitted. Using arrays of attribute bags (rather than name → value maps) means
you can emit repeated tags (e.g. two preconnect links) and pick the right
attribute (name vs property) per tag.
The default head is empty — brand the output by supplying your own tags:
// doc-kit.config.mjs export default { html: { head: { meta: [ { name: 'description', content: 'My project documentation' }, { property: 'og:image', content: 'https://example.com/og.png' }, ], links: [ { rel: 'icon', href: 'https://example.com/favicon.ico' }, { rel: 'stylesheet', href: 'https://example.com/fonts.css' }, ], html: ['<meta name="theme-color" content="#000" />'], }, }, };
Structural and theme-bound tags are emitted by the template itself rather than via
head, includingog:title(which mirrors the per-page title) andog:type. The UI stylesheet bundles its fonts locally.
Each entry is a path to a CSS file, bundled into the site's single stylesheet
after the built-in one — so its rules and custom properties win. Relative paths
resolve against the working directory; prefer absolute paths (e.g.
join(import.meta.dirname, 'theme.css')) when the config file can be loaded
from elsewhere.
The built-in accent palette is a project-neutral grey. Rebrand the output by
redefining the nine --color-brand-* custom properties, which the UI components
use for links, focus rings, and active states:
/* theme.css */ :root { --color-brand-100: #edf2eb; --color-brand-200: #c5e5b4; --color-brand-300: #99cc7d; --color-brand-400: #84ba64; --color-brand-500: #5fa04e; --color-brand-600: #417e38; --color-brand-700: #2c682c; --color-brand-800: #2c682c; --color-brand-900: #1a3f1d; }
// doc-kit.config.mjs import { join } from 'node:path'; export default { html: { stylesheets: [join(import.meta.dirname, 'theme.css')], }, };
The navigation object supplies the site's two navigation surfaces. Both keys
are optional; omit either one to keep that component's default.
Sidebar items are { label, link } and may nest through an items array of
their own. A label is plain text, except that backticked spans render as
<code> ('`fs`'), matching how page headings are rendered. A link is a
page path without its extension (/fs, /generators/html): it is resolved
against the page being rendered, so it obeys useAbsoluteURLs and highlights
while it is the current page. Links starting with http:// or https:// are
used as authored.
Navigation bar links are always used as authored, since they typically point
outside the generated site. Give them a target of '_blank' to open in a new
tab and mark them with an external-link icon.
// doc-kit.config.mjs export default { html: { navigation: { sidebar: [ { groupName: 'Guides', items: [{ label: 'Getting started', link: '/getting-started' }], }, { groupName: 'Reference', items: [{ label: '`fs`', link: '/fs' }], }, ], navbar: [ { text: 'Learn', link: 'https://nodejs.org/en/learn' }, { text: 'Download', link: 'https://nodejs.org/en/download' }, ], }, }, };
The sidebar also renders a version <Select> built from changelog. A site
configured without one has no versions to switch between, so the control is
omitted rather than rendered empty.
The bundler option accepts a small Doc Kit adapter rather than configuration
for a particular build system.
Both render and build receive { entries, virtualImports, config }; build
also receives pages. Entry maps use ${api}.jsx keys, rendered server results
use api keys, and page maps use output-relative HTML file names. config is
the resolved html configuration.
The adapter must compile the generated Preact JSX and CSS imports and resolve
the supplied theme aliases and virtual modules. The generated #theme/config
module exports server as true for the server build and false for the
client build.
A webpack integration can live entirely in project configuration without adding webpack to Doc Kit:
// webpack-bundler.mjs export const createWebpackBundler = webpackOptions => ({ getEntryId: api => `virtual:doc-kit/client/${api}.jsx`, async render({ entries, virtualImports, config }) { // Materialize or load the in-memory modules, run webpack's server target, // execute each emitted entry, and return Map<api, renderedHtml>. }, async build({ entries, virtualImports, pages, config }) { // Run webpack's browser target, inject its emitted assets into `pages`, // and write the HTML and assets to config.output. }, });
// doc-kit.config.mjs import { createWebpackBundler } from './webpack-bundler.mjs'; export default { html: { bundler: createWebpackBundler({ // Project-owned webpack configuration. }), }, };
When bundler is omitted, the generator imports and uses
createViteBundler() automatically. To customize Vite, import the adapter
directly and pass Vite's UserConfig to it:
// doc-kit.config.mjs import { createViteBundler } from '@doc-kit/generator-react/html/bundlers/vite'; import myVitePlugin from './my-vite-plugin.mjs'; export default { html: { bundler: createViteBundler({ plugins: [myVitePlugin()], define: { 'process.env.ANALYTICS_ID': JSON.stringify('UA-XXXXX'), }, resolve: { alias: { '@components': './src/components', }, }, css: { lightningcss: { targets: { chrome: 100 << 16, }, }, }, }), }, };
The generator owns the fields required to coordinate its builds: config-file loading, app type and base, virtual inputs, Preact compatibility aliases and automatic JSX runtime, the Lightning CSS transformer, output/write mode, SSR format and temporary output, and SSR dependency bundling. Values supplied for those fields are replaced after configuration is merged. User plugins are registered after the generator's virtual-module plugin; other Vite options are preserved.
Vite manifests are optional. Pass build: { manifest: true } or a manifest file
name to createViteBundler when another tool needs one. The generated HTML
already references the correct hashed scripts, stylesheets, imported assets,
and module preloads.
Function-valued plugins and hooks are supported because the html generator
runs on the main thread and does not serialize the bundler to a worker.
#theme/LogostringLogo rendered inside the navigation bar. Defaults to the built-inProjectNamecomponent, which rendersprojectas plain text.#theme/NavigationstringTop navigation bar. Defaults to the built-inNavBarcomponent.#theme/SidebarstringSidebar with version selector and page links. Defaults to the built-inSideBarcomponent.#theme/MetabarstringMetadata bar displayed alongside page content. Defaults to the built-inMetaBarcomponent.#theme/FooterstringOptional footer rendered at the bottom of each page. Defaults to the built-inNoOpcomponent, which renders nothing.#theme/LayoutstringOutermost wrapper around the full page. Defaults to the built-inLayoutcomponent.
Override any alias in your config file to swap in a custom component:
// doc-kit.config.mjs export default { html: { imports: { '#theme/Logo': './src/MyLogo.jsx', '#theme/Sidebar': './src/MySidebar.jsx', }, }, };
components registers custom JSX components so they can be used directly in
content (see JSX-in-MDX below). Each entry maps a JSX tag name to
an import descriptor ({ name, source, isDefaultExport? }, the same shape as the
built-in JSX_IMPORTS). A Tag: 'source' string shorthand expands to
{ name: Tag, source } with a default export. Registered components are merged
with the built-ins, and a matching imports alias resolves the source to a
real module path:
// doc-kit.config.mjs export default { html: { components: { // Shorthand — equivalent to { name: 'Hero', source: '#theme/Hero' } Hero: '#theme/Hero', // Full descriptor Stats: { name: 'Stats', source: '#theme/Stats' }, }, imports: { '#theme/Hero': './src/components/Hero.jsx', '#theme/Stats': './src/components/Stats.jsx', }, }, };
By default every input file is parsed as Markdown, where bare < and { are
treated literally (Node.js core docs use <string>-style type annotations). To
author real JSX — <Hero />, {expression} — use an .mdx file, or set
mdx: true in a file's --- frontmatter (frontmatter wins, so mdx: false
opts a .mdx file back out). MDX files are parsed with remark-mdx and skip the
API-doc type/signature parsing; headings, frontmatter, TOC, and sidebar still
work. Reference any component registered via components:
--- title: Welcome --- # Welcome <Hero title="Node.js" /> There are {stats.length} APIs documented.
The html generator provides a #theme/config virtual module that exposes pre-computed configuration as named exports. Any component (including custom overrides) can import the values it needs, and tree-shaking removes the rest.
import { project, repository, editURL } from '#theme/config';
string'Node.js'
).stringowner/repo
format, or
undefined
when none is configured.string'v22.x'
).Array{ url, label, major }
,
with labels and URL templates (only
{path}
remains for per-page use).string{path}
remains).Array[name, path]
tuples for sidebar navigation.Objectnavigation
(consumed by the
built-in
SideBar
and
NavBar
).booleanstringuseAbsoluteURLs
is
true
).stringremoteConfigUrl
(fetched
client-side by
RemoteLoadableBanner
to load announcement banners).booleanWhen overriding a #theme/* component, import only the config values you need:
// my-custom-sidebar.jsx import { pages, versions, version } from '#theme/config'; export default ({ metadata }) => ( <nav> <p>Current: {version}</p> <ul> {pages.map(([name, path]) => ( <li key={path}> <a href={`${path}.html`}>{name}</a> </li> ))} </ul> </nav> );
ObjectaddedIn
,
basename
,
path
, and any custom user-defined fields.Arraystring'5 min read'
).ComponentChildrenThe Layout component receives the props above. Custom Layout components can use
any combination of them alongside #theme/config imports.
The HTML template file (set via templatePath) uses JavaScript template literal syntax (${...} placeholders) and is evaluated at build time with full expression support.
string'File system | Node.js v22.x'
).stringstringstringstringstringObjectObjecthtml
generator configuration.string<meta>
/
<link>
/raw markup from the
head
config.Since the template supports arbitrary JS expressions, you can use conditionals and method calls:
<title>${title}</title> <script type="module" src="${entrypoint}"></script>
The configured adapter processes each populated page. It must replace or
resolve entrypoint, include that page's scripts and stylesheets, and write the
final HTML.