The lectio CLI
The lectio-docs package ships a lectio command that turns a repo's markdown
into a static, searchable docs site — with no app code to write.
npx lectio-docs dev # collect + serve locally, with live reload on the UInpx lectio-docs build # collect + build a static site into ./dist
The npm package is
lectio-docs; the command it installs islectio. Once it's a dependency of your repo you can runlectio dev/lectio builddirectly.
Try it in seconds
Run dev in any repo that has markdown in it:
npx lectio-docs dev
With no docs.config present, it writes a starter one and opens a local
server, so you see your docs as a site immediately. The starter follows the
repo's shape: root docs/ comes first, then app readmes and docs under
Apps, then Packages and Libs. Directory-name variants land in the
same group — apps/ or Applications/ both become /apps, and libs/ or
Libraries/ become /libs. A repo with none of those directories gets a
whole-tree **/*.md sweep instead. Source order is sidebar order, so
regrouping is just editing the generated docs.config.ts and re-running.
Configure your sources
A docs.config.ts (or .js / .mjs) at the repo root describes what to gather
and how to brand it:
export default {output: '.lectio',sources: [{ glob: 'docs/**/*.md', target: '/' },{glob: 'libs/*/README.md',target: '/libraries',titleFromPackageJson: true,sectionTitle: 'Libraries',},],editUrl: 'https://github.com/your-org/your-repo/edit/main/{path}',sourceUrl: 'https://github.com/your-org/your-repo/blob/main/{path}',site: {title: 'Developer Docs',githubUrl: 'https://github.com/your-org/your-repo',},};
sources— each is a glob plus thetargetpath it mounts under. Globs are relative to where you run the command;node_modules,dist,.nextand dotfiles are skipped automatically. Addorder: ['getting-started', 'guides']to a source to fix the order its entries appear in — the names are path segments, so a page orders the same way a folder does.editUrl— an "edit this page" link template;{path}is filled per page.sourceUrl— where a file lives in the repository. Used for links to files the site doesn't publish; see Configuration.site— the title and GitHub link shown in the header.
See Configuration for how targets become slugs.
The config file itself
It is a module with a default export — a plain object, nothing to install. Two things about the file, both of which lectio now handles or explains rather than failing at:
- TypeScript needs Node 22.18 or later, which strips the types on import.
On an older Node, name the file
docs.config.mjsand it runs as it is. - Keep imports type-only if you run lectio without installing it
(
npx lectio-docs dev).import type { DocsConfig }disappears with the types; a value import likedefineDocsConfighas to resolve at runtime, and in a repo with nonode_modulesit can't.
Nothing needs to change in your package.json — a config in a CommonJS repo, or
in a repo with no package.json at all, is read as the ES module it is.
Build and deploy
npx lectio-docs build
This prerenders every page to static HTML in ./dist, with a search index at
/search-index.json. Deploy dist/ to any static host — Cloudflare Pages,
Netlify, GitHub Pages, an object store:
# Cloudflare Pages, for examplenpx wrangler pages deploy dist --project-name my-docs
See Deploy to Cloudflare for the full walkthrough — fallback pages, CI and custom domains.
dev vs build when there's no config
devscaffolds a starterdocs.config.tsand runs — it's the on-ramp for trying things out.buildnever writes files on its own: it asks first when interactive, and errors in CI. A build stays deterministic.