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.

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, …)
├─ 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.

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/
├─ 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.

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 /
├─ 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:

TEXT
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)

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/afw-docs). Supports .js, .mjs and .json today.

Project root

File / folderRole
esdev.jsonCompiler plugins, build targets, output locations and development settings.
entry.jsClient bootstrap: imports @afw/routes and calls mountApp.
index.htmlThe 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.jsOptional 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.

TypeScript

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.

Last updated on
Edit this page