Next.js and Fumadocs site for Stately's product and package documentation.
Requires Node.js 24 or newer and pnpm 11.
pnpm install
pnpm devThe Portless URL is printed in the terminal. pnpm dev syncs external docs and
rebuilds the search index before starting Next.js.
| Route | Description |
|---|---|
app/(home) |
The route group for the landing page and other pages. |
app/docs |
The documentation layout and pages. |
/api/search |
Public documentation search. |
/api/docs/chat |
Public documentation AI chat. |
This repo can ingest docs content from other statelyai/* repos and mount it
under a configured stately.ai/docs/... route.
The external docs manifest lives in docs-sources.json:
[
{
"name": "Agent",
"package": "agent",
"source": "agent",
"include": ["docs/**/*.md", "docs/**/*.mdx"],
"mode": "workspace",
"ref": "next"
},
{
"name": "Graph",
"package": "graph",
"source": "graph",
"mode": "workspace",
"ref": "main"
},
{
"name": "XState v6 alpha",
"package": "xstate-v6",
"source": "xstate",
"notice": {
"title": "XState v6 is in alpha",
"description": "APIs and behavior may change before the stable release.",
"type": "warning"
},
"include": [
"docs/**/*.md",
"docs/**/*.mdx",
"packages/xstate-react/docs/**/*.md",
"packages/xstate-react/docs/**/*.mdx",
"packages/xstate-vue/docs/**/*.md",
"packages/xstate-vue/docs/**/*.mdx",
"packages/xstate-svelte/docs/**/*.md",
"packages/xstate-svelte/docs/**/*.mdx",
"packages/xstate-solid/docs/**/*.md",
"packages/xstate-solid/docs/**/*.mdx",
"packages/xstate-store/docs/**/*.md",
"packages/xstate-store/docs/**/*.mdx"
],
"mode": "workspace",
"mounts": [
{ "source": "docs", "route": "" },
{
"source": "packages/xstate-react/docs",
"route": "react",
"title": "React"
},
{
"source": "packages/xstate-vue/docs",
"route": "vue",
"title": "Vue"
},
{
"source": "packages/xstate-svelte/docs",
"route": "svelte",
"title": "Svelte"
},
{
"source": "packages/xstate-solid/docs",
"route": "solid",
"title": "Solid"
},
{
"source": "packages/xstate-store/docs",
"route": "store",
"title": "Store"
}
],
"ref": "next",
"route": "xstate/v6"
},
{
"name": "SDK",
"package": "sdk",
"source": "viz/packages/sdk",
"mode": "snapshot"
},
{
"name": "CLI",
"package": "cli",
"source": "viz/packages/cli",
"mode": "snapshot"
},
{
"name": "MCP",
"package": "mcp",
"source": "viz/packages/mcp",
"mode": "snapshot"
}
]Each entry means:
name: display name in the docs sidebarpackage: stable source key and default route segmentsource: repo root or repo subpath to scan for docs contentnotice: optional source-wide callout shown on every pageref: Git branch or tag; defaults tomaininclude: optional Markdown glob allowlist relative tosourcemounts: optional source-directory to route-directory mappingsmode: optional;"workspace"compiles a locked GitHub checkout directly;"snapshot"commits generated docs for private sourcesroute: optional public route prefix under/docs; defaults topackages/<package>
The sync pipeline is implemented in scripts/docs-sync.mjs.
For a "workspace" source, pnpm docs:lock resolves ref to an immutable
commit in docs-sources.lock.json. Normal syncs check out that commit into
.cache/docs-sources/<repo>/<commit>. Fumadocs compiles the allowlisted files
directly as a workspace; the sync does not copy or rewrite Markdown. A clean
checkout already at the locked commit is reused without contacting GitHub.
pnpm docs:watch explicitly uses available sibling clones instead, so local
workspace documentation edits appear immediately during development.
Other sources use the compatibility pipeline: resolve ../<repo> locally,
scan the configured allowlist, flatten pages into a generated workspace, add
missing frontmatter, rewrite links/assets, and derive navigation. The app never
copies external docs into content/docs.
Snapshot sources are different: they write to external-docs/<package> so the
generated docs can be committed. In CI, if the private local source repo is not
available, the sync step uses the committed snapshot instead of cloning GitHub.
Use pnpm docs:lock intentionally to advance workspace sources to the latest
commit on their configured ref. Normal builds only consume the recorded commit,
so a branch update cannot silently change a deployment.
Workspace sources preserve their relative hierarchy under route. When
mounts are configured, each source directory maps to its mount route and its
README.md(x) maps to that route's index. Nested docs/meta.json files control
workspace navigation order and visibility.
Compatibility and snapshot sources flatten pages into their route namespace:
- Root
README.mdbecomes the route index. - Included nested
**/README.md(x)become their parent path:src/formats/adjacency-list/README.md->src-formats-adjacency-list.md
docs/**/*.{md,mdx}flatten into the same route namespace.- Optional frontmatter
slugoverrides the flattened route segment. - Duplicate flattened slugs fail the sync.
- A Fumadocs
docs/meta.jsonpagesarray controls navigation order and visibility using the flattened page slugs.
For synced external docs, the pipeline also derives:
titleif frontmatter is missingdescriptionif frontmatter is missingsourcePathandsourceUrlpointing to the original file in GitHub
The first H1 in synced pages is stripped so the page title only renders once.
Linked source files and directories stay on GitHub. Only embedded local images
are copied into _assets.
Sources can overlap inside the same repo. More specific sources win.
Example:
{ "name": "SDK", "package": "sdk", "source": "viz/packages/sdk" }{ "name": "Viz", "package": "viz", "source": "viz" }
In that case, viz automatically skips the packages/sdk subtree so the SDK
docs are not indexed twice.
pnpm docs:lock: update immutable workspace revisions, then syncpnpm docs:sync: sync external workspaces at their recorded revisionspnpm docs:generate: sync external workspaces and regenerate Fumadocs outputpnpm docs:watch: use and watch available sibling source repos, then regeneratepnpm dev: run the app after a sync passpnpm build: run sync and then a production build