Introduction
Write React. Get a native page: HTML rendered by Rust from a compiled template, interactivity as a small React-free chunk, React only where the compiler proves it is needed.
What brust is
brust is a web framework for Bun. You write pages as ordinary React components and map them to paths in routes.tsx. brust build compiles every component ahead of time; brust start runs a Rust server (hyper on tokio) that serves the pages, with a pool of Bun workers for the code that has to run in JavaScript: your loaders, your actions, and the React islands.
There are no directives to write. No 'use client', no 'use server': the compiler reads each component and decides how it is served. A leftover 'use client' is ignored with a warning.
How a request is served
- Rust receives the request and matches the route.
- If the route is cached and the entry is fresh, Rust answers from memory (
x-brust-cache: HIT) without calling Bun. - Otherwise the route's loaders run in a Bun worker, root to leaf, and their results are merged into one object.
- Components that need JavaScript on the server (a computed value, a React island's HTML) run as jobs in the same worker call; Rust caches their results per component.
- Rust renders the compiled templates, leaf first, each into its layout's
Outlet, and adds only the scripts the page needs.
A page whose jobs are all cached, or that has no loader and no jobs, is served without waking Bun at all.
One component, three outputs
The compiler gives each component one of three tiers. static components read props and render markup: nothing ships to the browser. native components use local state, effects and handlers: they ship a small React-free chunk. react components need the real thing and become React islands. The card below is a native component on this page; its source is next to it.
Pikachu
electric- Use the switch or the button.
import { useState } from 'react'
export default function CatchCard({ no, name, art, shinyArt }) {
const [caught, setCaught] = useState(0)
const [shiny, setShiny] = useState(false)
return (
<article className="catch">
<img src={shiny ? shinyArt : art} alt={name} />
<h4>{no} {name}</h4>
<label>
<input type="checkbox" checked={shiny} onChange={(e) => setShiny(e.target.checked)} />
Shiny
</label>
<button onClick={() => setCaught(caught + 1)}>Catch · {caught}</button>
</article>
)
}The live card is the landing page's CatchCard (it also logs each signal write). useState, a controlled checkbox and a click handler are all native-tier features, so this card ships no React. The Rendering tiers page lists what lands where.
Where to go next
- Installation: requirements and the project generator.
- Quickstart: from an empty folder to a served page in four steps.
- Rendering tiers: static, native and react, and how to see which one you got.