CLI
The otfw command (from @opentf/web-cli) drives the toolchain. Projects expose it through package.json scripts (bun run dev, bun run build).
| Command | Description |
|---|---|
otfw dev | Start the dev server with on-demand compile and reload-on-save (full-page refresh). |
otfw build | Compile and bundle to dist/ — per-route lazy chunks under assets/. |
otfw build --ssg | Build and pre-render every route to static HTML. |
otfw serve | Build, then run the per-request SSR server (--port to override). |
otfw dev # development otfw build --ssg # production, static-rendered otfw serve # production SSR + API routes
Both otfw dev and otfw serve serve your API routes (route.{js,ts} files); otfw build emits the handler bundle to dist/server/api.js.
Scaffolding
Create a new project with the create initializer:
bun create @opentf/web my-app npm create @opentf/web@latest my-app pnpm create @opentf/web my-app yarn create @opentf/web my-app
The scaffolder (@opentf/create-web) is interactive:
| Prompt | Options |
|---|---|
| Project type | SPA (browser-only) · Fullstack (browser + server) · Documentation site · Library — see Installation |
| Language | JavaScript or TypeScript (.tsx pages, .ts API routes, tsconfig.json) |
| Styling | Plain CSS or Tailwind CSS (SPA, Fullstack, and Library only) |
Before writing files, it resolves every @opentf/* dependency in the generated package.json to ^<latest> from npm. Scaffolding aborts if the registry is unreachable. After the project is written, it runs <pm> install with the package manager that invoked the command.
SPA, Fullstack, and Docs projects suggest bun run dev (or the equivalent for your package manager). Library projects suggest bun test — see Library template.
Dev server notes
Serves your
public/directory at the site root (e.g./logo.png).Compiles
.cssentries (including Tailwind) on request.Surfaces compile errors in an overlay.
Reload on save (not module HMR)
otfw dev watches app/ and, on a supported file change, drops cached route chunks and signals the browser over /__hmr to location.reload() — a full tab refresh, not in-place hot module replacement.
| Works on save | Limitations |
|---|---|
app/**/*.{jsx,tsx,mdx,md,css} (existing routes) | New page.* in a new folder may need a dev-server restart |
| Imported components, layouts, loaders | Plain app/**/*.js (e.g. _meta.js) is not watched |
route.js / loader.js API and data bundles | otfw.config.js and index.html need a restart |
Module-level HMR (swap a changed module without reloading the page) is on the roadmap.