Loading...
Loading...
The Rust side of a route owns data and request handling. A page.rs produces a
page's props and a layout.rs produces a layout's data - both are #[handler]
functions, and this page covers them along with everything you reach for inside
one: the request, typed data, returning responses, and cookies. Application state,
dependency injection, and logging are covered in
Ossido Application.
#[handler] attribute#[handler] marks the async function that loads a page's data. It receives the
request and returns either a struct marked #[Props] (which becomes the page's
props) or a Response:
The same #[handler] also powers layouts. A layout.rs next to a
layout.tsx is a layout handler: it returns a #[Props] struct that becomes the
layout's data, just as a page.rs does for a page.
The paired layout.tsx receives that data spread as props alongside children,
typed with OssidoLayout<'/path'> (see
React frontend). A layout with
no layout.rs simply receives children. Everything below - the Request, typed
data, cookies - applies to layout handlers exactly as it does to page handlers.
Injected state and logging (see Ossido Application)
work the same in both.
A single request can involve several handlers: the page's, plus one for each layout wrapping it. Every one is self-contained - it declares what it needs and loads it itself, and none depends on another or on the layout hierarchy.
Because of that there's no fixed order. Ossido runs them as soon as it can, in parallel where possible, so an outer layout's data fetch and the page's fetch overlap instead of waiting on each other. Don't assume an outer handler ran first: if a handler needs a value, it must load that value itself rather than rely on an ancestor having prepared it.
This keeps layout handlers a good home for section-wide data - navigation, the current user or session, breadcrumbs - loaded once per request for every page under the layout, concurrently with the page's own data.
Request extractorThe first parameter is the Request. It carries the parts of the incoming
request - dynamic params, the URI, headers - and offers helpers for parsing the
body. The fields and methods below are the ones you'll reach for most.
params - dynamic route segmentsreq.params is a map of the route's dynamic segments, keyed by the name in the
folder ([pokemon] → "pokemon"):
uri - the request URIreq.uri is the full Uri,
so you can read the path and raw query string directly:
headers - request headersreq.headers is the request's HeaderMap. Look up any header by name:
location() - the parsed locationreq.location() returns a Location mirroring the client-side shape; use
.pathname() for the request path without the query string:
body::<T>() - parse a JSON bodyreq.body::<T>() deserializes a JSON request body into T, returning a
Result<T, BodyParseError>:
form_data::<T>() - parse a form-encoded bodyreq.form_data::<T>() deserializes an application/x-www-form-urlencoded body
(an HTML <form> submission). It checks the Content-Type and returns the same
Result<T, BodyParseError>:
Props & Type macrosTwo attribute macros connect a Rust struct to the frontend:
#[Type] bundles serde::Serialize and serde::Deserialize onto a struct
or enum and generates a matching TypeScript type, so the page component is
typed end to end (see TypeScript integration).#[Props] does everything #[Type] does, plus marks the struct as a
route's page props, allowing you to directly return them.Because the macros inject the serde derives for you (crate-pinned), you don't add
#[derive(...)] or import serde yourself:
Only the top-level route struct uses #[Props]; nested structs it references just
use #[Type].
A handler can return its props struct directly, or a Response when it needs more
control - redirects, or a specific status:
Any value that can convert into a Response also works with .into(), so you can
return data on the happy path and a status on the error path from the same
function.
Beyond the Request, a handler can declare a Logger or fields of your
application state as extra parameters, injected by name. That machinery - and where
application state is defined (src/app.rs) - is covered in
Ossido Application.
#[api(METHOD)] defines an HTTP endpoint with no React view, under
src/routes/api. These have their own page -
API Handlers.
Handler parameters are the Request, a Logger, or application-state fields - not
arbitrary axum extractors - so read cookies from the request headers with the
re-exported CookieJar:
To set a cookie, add it to the response's Props and return a
Response::Props:
The Authentication with cookies guide walks through a full session flow.
Handlers don't need explicit error plumbing: a panic is caught and surfaced as a rich error (not a crash), and you can hook into every error to report it or override the response. It's covered in full on its own page - Error Handling.
Pre-rendering dynamic routes for a static build uses #[static_paths], covered
alongside the output modes and the render pool in
SSR, SSG & Streaming.
Back to Ossido Application · Middleware · API Handlers · Error Handling