brust 2.0.0-alpha.5
Guide / Rendering tiers

Rendering tiers

The compiler reads each component and picks the cheapest output that still behaves the same. You never pick it; you can always see what it picked.

Three tiers

TierWhenThe browser gets
staticNo hooks, no handlers, no refs. Props in, markup out.HTML only. No JavaScript.
nativeOnly the supported hooks, and event handlers.HTML, plus a small React-free chunk: signals bound to the server's markup.
reactEverything else: context, useReducer, useDeferredValue, Suspense, a component from a package, browser globals read during render.HTML rendered by React on the server, then a React island hydrates it.

The tier is decided per component, bottom-up. A React child inside a native parent becomes an island and the parent stays native, so one island never turns the whole page into React. A page made only of static components gets no <script> tag at all.

The three, live

These are the landing page's three demo components. The tier and the shipped chunk under each one come from this build's manifest, read by the page's loader.

PokemonCardtier static · client none
Bulbasaur#001

Bulbasaur

grasspoison
Charmander#004

Charmander

fire
Squirtle#007

Squirtle

water
CatchCardtier native · client client/catchCard_2b1c6e39-47e2b1d8f9.js
Pikachu
#025

Pikachu

electric
Signalsno re-render
  1. Use the switch or the button.
PokemonSearchtier react · client client/react-pokemonSearch_d9a2c844-ef4e1bde88.js
  • #001Bulbasaur
  • #002Ivysaur
  • #003Venusaur
  • #004Charmander
  • #005Charmeleon
  • #006Charizard
  • #007Squirtle
  • #008Wartortle

What decides the tier

You useTier
Props only, or useIdstatic
useState, useMemo, useCallback, useEffect, useLayoutEffect, useRef, useId, memo(), event handlers, useStore, and a custom hook whose body uses only these (the compiler inlines it)native
useReducer, useContext, useDeferredValue, useTransition, useSyncExternalStore, use(promise), cloneElement, forwardRef and other HOCs, spread props, lazyreact

In a native component, effect dependency arrays are not needed: signals track what an effect reads. A missing dependency still prints effect-deps as a warning, and the tier stays native.

Note

Falling back to React is never a build failure. Some things are: passing a function prop to a React child (island-prop), server-only code reachable from the browser (server-only-in-client), and a list without keys (list-key).

See what you got

Every fallback is printed once per component by the build, with the rule and the line. This is the pokedex example's build:

~/pokedex
$ bun run build
warning hook-unsupported components/TeamBuilder.tsx:12:26 hook useReducer is not supported
warning hook-unsupported components/UnifiedIsland.tsx:11:25 hook useReducer is not supported
[brust] built dist in 73ms

The full answer is in dist/manifest.json: each component has a tier, the client chunk it ships (null when nothing ships) and its server jobs.

dist/manifest.json
"pokemonSearch_<hash>": {
  "tier": "react",
  "client": "client/react-pokemonSearch_<hash>-<hash>.js",
  "jobs": [{ "kind": "ssr", … }]
}