Project Structure

create-web scaffolds four project types — SPA, Fullstack, Documentation site, and Library. Apps and docs sites share the same app/ routing layout; libraries ship source components with no app/ folder.

SPA (browser-only)

A client-only app — deploy dist/ to any static host. No server files in the repo.

TEXT
my-app/
├─ app/
│  ├─ page.jsx        # route: /
│  ├─ layout.jsx      # shared layout wrapping all routes
│  ├─ 404.jsx         # not-found page
│  ├─ about/
│  │  └─ page.jsx     # route: /about
│  └─ global.css
├─ public/            # static files copied to dist/ as-is (favicon, images, …)
├─ index.html         # mount point + injected bundle
└─ package.json

Add an optional otfw.config.js at the root when you need site.url (for otfw build --ssg), dev proxy, or i18n — see Configuration.

Fullstack (browser + server)

Same UI as an SPA, plus server modules the toolchain bundles separately — never shipped to the browser.

TEXT
my-app/
├─ app/
│  ├─ page.jsx
│  ├─ layout.jsx
│  ├─ loader.js       # route loader for / — data → router.data
│  ├─ _middleware.js  # middleware for the whole app tree
│  ├─ 404.jsx
│  ├─ about/
│  │  └─ page.jsx
│  ├─ api/
│  │  └─ hello/
│  │     └─ route.js  # API endpoint: GET /api/hello
│  └─ global.css
├─ public/
├─ index.html
└─ package.json

Run otfw serve for per-request SSR, loaders, and API routes in one process. See Server.

Documentation site

Docs projects add otfw.config.js and an app/docs/ section for MDX content. The scaffolder can optionally add app/blog/ — see web-docs → Blog.

TEXT
my-docs/
├─ app/
│  ├─ page.jsx            # marketing landing page
│  ├─ layout.jsx
│  ├─ docs/
│  │  ├─ layout.jsx       # docs shell (sidebar + TOC)
│  │  ├─ _meta.js         # sidebar order + labels
│  │  └─ page.mdx         # /docs introduction
│  └─ global.css
├─ public/                # logos, og images — served at /
├─ otfw.config.js         # site URL, docs/blog sections (@opentf/web-docs)
├─ index.html
└─ package.json

Library

Library projects export source components from the package root — no app/ folder:

TEXT
my-lib/
├─ index.js           # package entry
├─ src/
│  └─ Counter.jsx
├─ tests/
│  └─ counter.test.js
├─ test-setup.js
├─ bunfig.toml
└─ package.json

See Library template for publish and consumption details.

Special files

Files the toolchain recognizes beyond ordinary components. Extensions .js, .ts, .jsx, and .tsx are interchangeable unless noted.

Routing (App & Docs)

FileRole
page.jsx / page.mdxA route. The folder path is the URL (app/about/page.jsx/about).
layout.jsxWraps every route below it; nests with the folder tree.
404.jsxRendered when no route matches.
route.jsAn API endpoint. The folder is the URL (app/api/hello/route.js/api/hello). A folder holds a page.* or a route.*, never both.
loader.jsA route loader sibling to a page.*. Runs server-side before the page renders; its return value is router.data. Requires a matching page.* in the same folder.
_middleware.jsMiddleware for its folder and everything nested under it (pages, API, loader data, SSR).
_meta.jsDocs sidebar order and labels for a folder (@opentf/web-docs). JavaScript only today.

Project root

File / folderRole
index.htmlThe HTML shell. The toolchain mounts the app into #app and injects the bundle.
public/Static assets copied to dist/ at build time and served at the site root (public/favicon.svg/favicon.svg). Overrides generated files when names collide (e.g. a hand-written public/robots.txt).
otfw.config.jsOptional for SPA/fullstack (site.url, proxy, i18n). Required for docs/blog production builds and drives @opentf/web-docs (navbar, search, feeds). See Configuration.
dist/Build output — created by otfw build. Not checked in; safe to delete and rebuild.

Every other PascalCase .jsx/.tsx file under app/ is a reusable component — no registration needed (see Components).

Conventions

  • .jsx/.tsx files must have a default export.

  • Components are PascalCase (UserCard.jsx).

  • You never import $state, onMount, etc. — the compiler injects them.

TypeScript

Choose TypeScript at the scaffold prompt to get .tsx pages, .ts API routes and loaders, tsconfig.json, and macro typings (app/otfw-env.d.ts). Docs sites keep otfw.config.js and _meta.js as JavaScript — the toolchain reads those today.