Loading...
Loading...
The React side of a route renders the UI. It receives typed data from the route's Rust handler, composes with layouts, and navigates on the client - while the data loading stays in Rust.
A page.tsx default-exports a React component. Its props are whatever the route's
page.rs handler returns, spread directly onto the component. To type them,
annotate the component with OssidoPage from the generated @ossido-labs/ossido/types module:
OssidoPage<Path> takes the route's URL path as its type argument - '/',
'/documentation', '/pokemons/[pokemon]' - and resolves to a component whose
props match that route's handler return value. The available paths are generated
from your src/routes tree, so the argument autocompletes and an unknown path is
a type error. A page with no page.rs handler simply has empty props. (See
TypeScript integration for how these types are
generated.)
A layout.tsx wraps the routes beneath it and renders their children. The root
layout owns the document shell; nested layouts wrap a subtree.
For a layout that only wraps its children, type it with OssidoLayoutProps from
@ossido-labs/ossido:
A layout can also load its own data with a layout.rs handler. When it does, type
the component with OssidoLayout<'/path'> from @ossido-labs/ossido/types instead - it gives
you the handler's data and children, both typed:
The <Path> argument works just like OssidoPage's - it's the route path, and
the props resolve to that layout's handler data. See
Layout handlers for the layout.rs
side.
Use Link from @ossido-labs/ossido for in-app navigation - it keeps you in the SPA (fetching
just the next route's props) instead of a full page load:
Link accepts the usual anchor attributes plus:
preload (default true) - load the target route when the link scrolls into
view. Set false to opt out.scroll (default true) - scroll to the top on navigation; false keeps the
offset.replace (default false) - replace the history entry instead of pushing.useRouter exposes the current location, programmatic navigation, and a way to
refresh the current page's data:
push adds a history entry; replace swaps the current one. Dynamic segment
values (from [param] routes) reach the page through its server props rather than
a hook - have the handler include them in what it returns.
refetchProps() re-runs the current route's page.rs handler and re-renders the
page with the fresh data - without a full navigation. Reach for it after a
mutation, when the values the page was server-rendered with have changed and you
want it to reflect them. The current page stays on screen while the refetch is in
flight (no loading.tsx flash), and isRefetching is true until it resolves:
This is React 19, so document metadata is native: render <title>, <meta>, or
<link> anywhere in a component and React hoists them into <head>. Set
site-wide defaults in the root layout, and per-page tags in the page itself:
loading.tsx is rendered as a <Suspense> fallback while a route's data
resolves.not-found.tsx for unmatched routes.On first load the server streams fully-rendered HTML, then the client bundle
hydrates it into a live React app. To hydrate without re-fetching, the server
injects a payload - window.__OSSIDO_SERVER_PAYLOAD__ - carrying the initial
location and the route's data; the OssidoScripts element in the root layout
emits it along with the client entry. Because the client renders from that same
data, its output matches the server's markup.
Every route is code-split and lazily loaded, so the browser only downloads the
code for the pages it visits. Link warms the next route automatically when it
scrolls into view (the preload prop), and preloadRouteChain lets you preload a
route's code manually.
For component-level lazy loading, dynamic wraps React.lazy + Suspense: