Mount on Mars
The same Middleware instance that astra build crawls can be mounted on any Mars server. This is how you ship documentation alongside a live MoonBit application from one binary.
Minimal mount
import "mizchi/astra" as astra
import "mizchi/astra/middleware" as middleware
import "mizchi/mars" as mars
async fn main {
let cfg = @astra.SsgConfig::default()
let mw = @middleware.create(cfg, cwd=".")
let app = @mars.Server::new()
app.all("/*", mw.handler())
app.listen(port=3000)
}
Middleware::handler() returns a Mars Handler that responds to GET on every page URL the document tree exposes plus every entry in @assets.list_asset_urls(). Middleware::list_urls() returns the union of those โ that is the same set the build crawler walks.
Mounting under a path prefix
To embed docs under /docs/*, use Mars's path prefix routing instead of /*:
let app = @mars.Server::new()
app.all("/api/*", api_handler()) // your application
app.all("/docs/*", mw.handler()) // astra-served docs
app.listen(port=3000)
Configure base: "/docs/" in astra.config.json so links inside generated HTML point at the prefixed paths instead of the root.
What the middleware actually serves
For each request:
Look up the URL in the resolved page tree (
docs_dirwalk + i18n fallback).If hit, render the Markdown / MDX through the cached document pipeline and return HTML.
Otherwise, check the asset list (
/assets/*.css,/scripts/*.js, fonts, images). If hit, return the bundled bytes.Otherwise, return 404.
There is no separate static-asset middleware to wire up. Everything astra needs to render the docs is enumerated by list_urls() and served by handler().
Build parity
astra build is a thin loop:
for url in mw.list_urls() {
let response = @testing.invoke(mw.handler(), path=url)
write_to_disk(out, url, response.body)
}
This is why dev and build always agree. If you find a divergence, treat it as a bug โ see astra/docs/parity-notes.md for known gaps (utility CSS extraction, sitemap generation, Cloudflare adapter manifests).
Hot reload
astra dev watches docs_dir and bumps the document tree's revision on change. The middleware sees the new revision on the next request and re-renders. There is no MoonBit recompile in the dev path โ Markdown changes are reflected as fast as the renderer runs.