Bifrost

Documentation development

Write, preview, test, and deploy the documentation site with Bun.

The documentation is a Next.js App Router site using Fumadocs, built and served on Bun. Every page is prerendered from MDX in this repository, so the running server only hands out what the build produced, including a search index per language at /api/search/en and /api/search/es. Queries run in the browser. Only /sitemap.xml reads the deployment origin at runtime. The site supports English and Spanish.

Preview changes

From the repository root, install dependencies and start the documentation app:

bun install
bun run --filter @boelabs/docs dev

Open the address it prints (port 3000 by default, or DOCS_PORT). To check the production output — including full-text search, which is built from the same page tree:

bun run --filter @boelabs/docs build
bun run --filter @boelabs/docs start

start reproduces the container rather than approximating it: it copies the static assets beside Next's standalone server and runs it under Bun, which is exactly what the image does.

Write a page

Pages live in apps/docs/content/docs, under one of two switcher roots — the dropdown at the top of the sidebar:

RootHolds
(docs)Getting started, deploying, routing, access, providers, operating, extending
(api)The wire format, every endpoint, and every value the gateway reads

Inside a root, each (group) folder is extracted below a visible section heading using "---Section title---" and "...(group)" in the root meta.json. Add an MDX file with a descriptive title and a short description in YAML frontmatter, then list its filename in that folder's meta.json. Standard Markdown is enough for most pages.

A parenthesised folder never appears in the URL — it is a route group, exactly as in Next — so (docs)/(routing)/routing.mdx is served at /docs/routing. That is what lets the navigation be reorganised without breaking a single link, and it is why the whole switcher could be added without moving a page.

Two things to know when adding a section, both of which a test enforces:

  • every .mdx file and every nested (group) must be listed in its folder's meta.json, and nothing may be listed that is not there;
  • a root needs at least one page directly inside it. Fumadocs takes the switcher entry's link from the root's index page, falling back to its first direct page child — a root holding only folders produces no link and is dropped from the switcher without a word.

Use one page per task or reference area. Start with its purpose and prerequisites, explain steps in execution order, and finish with verification or relevant next steps. Keep credentials as placeholders and distinguish public model names from upstream model IDs.

Translate a page

English pages keep their existing filenames and URLs. Add Spanish content beside each page as name.es.mdx, with a matching meta.es.json for navigation labels. English uses /docs and Spanish uses /es/docs. There is no language-detection middleware or silent English fallback.

Translate the prose, page titles, descriptions, and navigation. Keep code examples, API fields, commands, filenames, and technical terms in English. Keep the English heading IDs with Fumadocs' [#original-id] suffix so links to a section survive a language switch. Localize internal links and card destinations; leave external URLs unchanged.

In prose, prefer "artificial intelligence" in English and "inteligencia artificial" in Spanish. Use "AI" or "IA" only when an abbreviation is necessary, and preserve official product names such as Vercel AI SDK.

Use cards for entry points, steps for procedures, tabs for alternative examples, and callouts for constraints that affect the reader's next action. Keep one purpose per page and link to shared reference material instead of duplicating it.

The language tests require a translated sibling for every page and check navigation, code examples, and links. The build verifies both language trees, document languages, anchors, and static search indexes. Navigation and search labels live in src/lib/i18n.ts.

Validate

bun run check
bun run typecheck
bun run test
bun run --filter @boelabs/docs build

The build ends by checking every internal link and #anchor in the prerendered HTML, so a renamed page or a heading that no longer exists fails the build rather than shipping. Check both light and dark appearance, mobile navigation, and search before publishing.

Deploy

The image is published to GHCR alongside the gateway and the dashboard, and the Compose files pull it. To build it yourself:

docker build -f apps/docs/Dockerfile -t bifrost-docs .
docker run --rm -p 3000:3000 bifrost-docs

It carries no configuration: the same image runs anywhere, and no database, Redis instance, provider key, or gateway environment file is required to serve the documentation.

Set DOCS_SITE_URL to the public origin to publish a sitemap at /sitemap.xml:

docker run --rm -p 3000:3000 -e DOCS_SITE_URL=https://docs.example.com bifrost-docs

It is read at run time, not baked in, so one image can serve staging and production. Without it the site works exactly the same and no sitemap is generated.

On this page