Fullstack Error Handling
Errors are unfortunately inevitable in software development. Even in Rust, apps might not behave as expected and user requests might be malformed.
In these cases, you might want to show an error page to the user while also returning an appropriate status code.
Creating an Error
Recall in the Server Functions chapter, that all server functions must return a Result<T>:
#[post("/api/user/login")] pub async fn login() -> Result<()> { // ... }
Because server functions are called from the client, they need some way of expressiong both a request failure and a server failure. If the user is offline, we want to reliably return an offline error and status in the UI.
Also recall that server functions can return several different error types:
- ServerFnError: A broad error type encompasing request failures and server failures
- anyhow::Error: A general-purpose error type that downcasts its inner value
- CapturedError: A cheaply-clonable- anyhow::Errorwrapper
- StatusCode: A specific HTTP status code
- HttpError: A specific HTTP status code and a message
- Custom Errors: User errors that implement Serialize + Deserialize + AsStatusCode
Dioxus will attempt to downcast server function errors and captured errors into status codes such that the returned page receives an appropriate HTTP status.
If an error does not downcast to a known status-code-like error type, then the request will default to a  500 INTERNAL SERVER ERROR.
#[post("/api/user/login")] pub async fn login() -> Result<()> { // This will return a 500 status code return Err(anyhow::anyhow!("Bad request!").into()); // ... }
The  OrHttpError error type makes emitting status codes quite simple with its extension methods on  Result<T>,  Option<T>, and  bool.
#[post("/api/user/login")] pub async fn login() -> Result<()> { authenticate_user() // this method comes from `OrHttpError` .or_unauthorized("You must be logged in to view this resource")?; // .. }
Error Status Codes Bubble
In the event of an error, Dioxus extracts the status code for the response by downcasting errors that bubble to root component.
For example, this example app does not provide a root error boundary, and thus all errors will bubble up to the root:
fn app() -> Element { let post_data = use_loader(move || get_post())?; rsx! { p { "{post_data}" } } } // This endpoint always throws an error #[get("/api/post/")] async fn get_post() -> Result<String, HttpError> { HttpError::not_found("Post not found") }
If you  curl the app, you'll notice that it returns a  404 status code.
If we want to catch the error and provide a nice fallback UI, we can use an  ErrorBoundary. Note that when we catch the error, we must
fn app() -> Element { rsx! { ErrorBoundary { handle_error: move |err| { // To ensure the HTTP status is still set properly, we need to call `commit_error_status` let http_error = FullstackContext::commit_error_status(err.error().unwrap()); // and then we can render some pretty fallback UI rsx! { "An error occured! {http_error:?}" } }, Post {} } } } fn Post() -> Element { let post_data = use_loader(move || get_post())?; rsx! { p { "{post_data" } } }
The  commit_error_status function on  FullstackContext extracts the HTTP status from a  CapturedError and then modifies the outgoing response accordingly.
Error Layout with Router
If you're using the Dioxus Router for your app's routing, then it might not be immediately clear how to integrate custom error pages into your app.
In these cases, we'd take a similar approach with an  ErrorBoundary. We could either wrap our  Router {} component in an error boundary, or add a layout to our app that renders the fallback UI.
// A router definition with `ErrorLayout` layout #[derive(Routable, PartialEq, Clone, Debug)] enum Route { #[layout(ErrorLayout)] #[route("/")] Home, #[route("/blog/:id")] Blog { id: u32 }, } // And then our Outlet is wrapped in a fallback UI #[component] fn ErrorLayout() -> Element { rsx! { ErrorBoundary { handle_error: move |err: ErrorContext| { let http_error = FullstackContext::commit_error_status(err.error().unwrap()); match http_error.status { StatusCode::NOT_FOUND => rsx! { div { "404 - Page not found" } }, _ => rsx! { div { "An unknown error occurred" } }, } }, Outlet::<Route> {} } } }
Using layouts for error pages is extremely powerful. You can isolate fallback UI to specific parts of the page while also properly setting the returned status code.
For example, the GitHub UI retains most of its UI while isolate the 404 message to just the source code viewer:

It's a better user experience to render a web page that is visually consistent while also still delivering the appropriate status code.