Project Structure
AFW supports browser apps, fullstack apps, documentation sites and component libraries. The layouts below match the projects esdev create generates; see Installation. Apps and docs sites share the same app/ routing layout; libraries ship source components without an app/ folder.
SPA (browser-only)
A client-only app — deploy dist/ to any static host. No server files in the repo.
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, …) ├─ esdev.json # compiler plugin and build targets ├─ entry.js # client bootstrap ├─ index.html # mount point + module script └─ package.json
Add an optional afw.config.js at the root when you need site.url (for a prerender target), or i18n — see Configuration.
Fullstack (browser + server)
Same UI as an SPA, plus server modules the toolchain bundles separately — never shipped to the browser.
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/ ├─ esdev.json # browser and server targets ├─ entry.js # client bootstrap ├─ server.js # app-owned request pipeline ├─ index.html └─ package.json
Provide a native server entry to compose per-request rendering, loaders and API routes. See Server.
Documentation site
Docs projects add afw.config.js and an app/docs/ section for MDX content. Add app/blog/ when you need posts — see afw-docs → Blog.
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 / ├─ esdev.json # browser + prerender targets ├─ entry.js # client bootstrap ├─ ssg.js # staged release prerender step ├─ afw.config.js # site URL, docs/blog sections (@opentf/afw-docs) ├─ index.html └─ package.json
Library
Library projects export source components from the package root — no app/ folder:
my-lib/ ├─ index.js # package entry ├─ src/ │ └─ Counter.jsx ├─ tests/ │ └─ counter.test.js ├─ esdev.json # CSR compiler plugin (routes off) and test cleanup └─ 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)
| File | Role |
|---|---|
page.jsx / page.mdx | A route. The folder path is the URL (app/about/page.jsx → /about). |
layout.jsx | Wraps every route below it; nests with the folder tree. |
404.jsx | Rendered when no route matches. |
route.js | An 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.js | A 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.js | Middleware for its folder and everything nested under it (pages, API, loader data, SSR). |
_meta.js | Docs sidebar order and labels for a folder (@opentf/afw-docs). Supports .js, .mjs and .json today. |
Project root
| File / folder | Role |
|---|---|
esdev.json | Compiler plugins, build targets, output locations and development settings. |
entry.js | Client bootstrap: imports @afw/routes and calls mountApp. |
index.html | The HTML shell with an explicit module script. Your entry mounts into #app; esdev bundles the module script. |
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). |
afw.config.js | Optional for SPA/fullstack (site.url, i18n). Required for docs/blog production builds and drives @opentf/afw-docs (navbar, search, feeds). See Configuration. |
dist/ | Build output — created by esdev 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
Route pages and layouts need a default export. Reusable components can use named or default exports.
Components are PascalCase (
UserCard.jsx).You never import
$state,onMount, etc. — the compiler injects them.
Choose TypeScript at the scaffold prompt to get .tsx pages, .ts API routes and loaders, tsconfig.json, and macro typings (app/afw-env.d.ts). Docs sites keep afw.config.js and _meta.js as JavaScript — the toolchain reads those today.