Production Build

otfw build compiles every route and component (JSX/MDX → native DOM), bundles the result, and writes a deployable site to dist/.

Shell
bun run build
# → otfw build

What it produces

dist/
  index.html          the app shell, with the hashed bundle injected
  assets/
    bundle-<hash>.js   the entry bundle (code-split per route)
    <chunk>-<hash>.js  lazily-loaded route chunks
    <name>-<hash>.css  compiled stylesheets
    <worker>-<hash>.js  bundled web workers (new Worker(new URL(…)))
    <asset>-<hash>.wasm binary assets (new URL(…, import.meta.url))

Routes are code-split: visiting a page loads only its chunk. Filenames are content- hashed, so they can be cached forever. Web workers and files referenced with new URL(…, import.meta.url) are emitted here too — see Assets & Workers.

How route code splitting works

otfw build generates a small entry bundle plus one lazy chunk per route (and shared chunks such as layout.jsx). The entry calls mountApp with a map of dynamic imports — each route is () => import("…/page.jsx") — and Rolldown emits separate hashed files under dist/assets/.

On first navigation to a route, the browser fetches that route's chunk; other routes stay unloaded. In dev, the same model applies on demand: the first visit to a path compiles /app/.../page.jsx into a /__route/<id>.js module instead of bundling the whole app up front.

Prefetch is not built yet

Chunks load lazily on navigation today. Automatic prefetch (for example on link hover or when a <Link> enters the viewport) is planned but not implemented in the router yet.

Single-page output

A plain build emits one index.html; the client bundle renders every route. Configure your host to serve index.html for unknown paths so deep links resolve:

NGINX
location / {
  try_files $uri $uri/ /index.html;
}
Pre-render for SEO

Add --ssg to also emit a static .html for every route — see Static Generation. Crawlers then get full markup without running your JS. Per-route <head> tags come from Metadata & SEO exports on your pages and layouts.

Base URL

Set site.url in otfw.config.js so the build can emit absolute canonical URLs, a sitemap, and (when a blog is configured) RSS/Atom feeds. Production docs/blog builds fail loudly when no site URL is configured:

JavaScript
export default defineDocsConfig({
  site: { url: "https://example.com" },
});

You can override it at build time with --base-url=https://example.com.

LLM context

Docs/blog builds also write /llms.txt and /llms-full.txt from the filesystem router. The compact file links to key docs, API, and blog routes; the full file includes cleaned Markdown content for MD/MDX pages. Drop public/llms.txt or public/llms-full.txt to override either generated file.

Previewing locally

dist/ is plain static files — serve it with any static server to smoke-test the production output before deploying:

Shell
bunx serve dist