Project structure
A brust app is a routes file, components, an optional actions app, and a brust.toml. This is the blank template.
The files
my-app/
├── routes.tsx
├── app.css
├── components/
│ ├── Layout.tsx
│ └── Counter.tsx
├── pages/
│ └── HomePage.tsx
├── actions.ts
├── public/
│ └── favicon.svg
├── brust.toml
├── wrangler.jsonc
├── package.json
├── tsconfig.json
├── README.md
└── .gitignorePick a file to see what it does.
routes.tsx Maps paths to components with defineRoutes. The entry brust build reads.
app.css Tailwind CSS v4: @import "tailwindcss" and a @theme of color tokens (bg-paper, text-ink, ...). The layout imports it; brust builds it and links it in <head>.
components/Layout.tsx The document root: <html>, <head>, and <Outlet/> where each page renders. It imports ../app.css.
pages/HomePage.tsx The page at /. A heading and the counter.
components/Counter.tsx A useState counter. The compiler makes it native: a signal in the browser, no React shipped.
actions.ts An Elysia 2 app built with the brust() adapter, one GET /hello route. Never call app.listen(): Rust owns HTTP.
public/ Static files, served as they are at /public/... (the layout links /public/favicon.svg).
brust.toml Server address and port, and an empty [actions] table that turns actions on with their defaults.
wrangler.jsonc Cloudflare bindings for the Workers target. brust owns the build keys (main, assets, compatibility_date, ...).
package.json The scripts below, @brust/core, React 19, the pinned Elysia packages, and Tailwind as dev dependencies.
README.md How to run the app, what each file is for, and how to call the hello action.
.gitignore Keeps node_modules/, dist/, .brust/ and .wrangler/ out of git.
Only routes.tsx is required by name, and you can pass another entry to brust build. The components/ and pages/ split is a convention: the compiler follows imports, not folders.
The scripts
{
"scripts": {
"dev": "brust dev routes.tsx",
"build": "brust build routes.tsx",
"start": "brust start",
"typecheck": "bun check",
"dev:cf": "brust dev routes.tsx --target cloudflare",
"deploy:cf": "brust build routes.tsx --target cloudflare && wrangler deploy -c dist/cf/wrangler.jsonc"
},
"devDependencies": {
"@tailwindcss/node": "4.3.3",
"@tailwindcss/oxide": "4.3.3",
"@types/bun": "^1.4.0",
"@types/react": "^19.2.0",
"tailwindcss": "4.3.3",
"typescript": "^7.0.2",
"wrangler": "^4.149.0"
}
}dev runs brust dev: it builds into .brust/dev, starts the server and rebuilds when you save, see Dev mode. The :cf scripts target Cloudflare Workers, see Deployment. tailwindcss, @tailwindcss/node and @tailwindcss/oxide are pinned dev dependencies at one version: the build uses them, the server does not.
What brust build writes
dist/
├── manifest.json every route and component, with its tier
├── jinja/<id>.jinja the compiled template of each component
├── client/*.js native chunks and React islands, content-hashed
├── client/*.css app.css and the Tailwind utilities you use, minified, hashed
├── jobs.js the server code: loaders and jobs
├── actions.js your actions app, when [actions] is on
├── public/ a copy of ./public
└── index.js bun dist/index.js starts the serverThe build writes to a temporary folder next to dist/ and swaps it in only when it succeeds, so a failed build leaves the last good one in place. Each page links the CSS its layout and page import from client/ in <head>; no JS bundle carries CSS. dist/ imports react and @brust/core at runtime: keep it inside the app, next to its node_modules.