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
| Tier | When | The browser gets |
|---|---|---|
| static | No hooks, no handlers, no refs. Props in, markup out. | HTML only. No JavaScript. |
| native | Only the supported hooks, and event handlers. | HTML, plus a small React-free chunk: signals bound to the server's markup. |
| react | Everything 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.
Bulbasaur
Charmander
Squirtle
Pikachu
electric- Use the switch or the button.
#001Bulbasaur
#002Ivysaur
#003Venusaur
#004Charmander
#005Charmeleon
#006Charizard
#007Squirtle
#008Wartortle
What decides the tier
| You use | Tier |
|---|---|
Props only, or useId | static |
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, lazy | react |
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.
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:
$ 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 73msThe 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.
"pokemonSearch_<hash>": {
"tier": "react",
"client": "client/react-pokemonSearch_<hash>-<hash>.js",
"jobs": [{ "kind": "ssr", … }]
}