Files
dioxus/packages/fullstack-server
Amar Sood 8c1911c268 Use ServeDir for fullstack dev static assets (#4983)
Without this patch, simply instantiating the Jumpstart template in fullstack mode and making _any_ hot-patch change would result in a 404 on fetch and a client WASM panic.

This changes the fullstack server static asset routing so that, in debug builds, directories under the `public` tree (including `/wasm`) are served via `tower_http::services::ServeDir` instead of being eagerly expanded into a fixed set of routes at startup.

Today `dioxus_server::DioxusRouterExt::serve_static_assets` walks the `public` directory once at launch via `serve_dir_cached`, registering a separate route for every file it sees. That works fine for pre-baked assets, but it breaks down when dx/subsecond writes new files into `public` during a dev session. In a fullstack web app, wasm hot patches are emitted as timestamped files like `public/wasm/lib<name>-patch-<ts>.wasm`; the CLI tells the browser to fetch `/wasm/lib<name>-patch-<ts>.wasm` immediately, but the fullstack server never registered a route for that path, so the fetch returns 404 even though the file already exists on disk.

Non-fullstack dev builds do not have this problem because the dx devserver serves `public` directly using `ServeDir`, which consults the filesystem at request time. Fullstack dev, by contrast, proxies asset requests to the inner server, and that server only knows about whatever was in `public` at the moment its router was built.

This change narrows the gap by making `serve_static_assets` dynamic for directories in debug builds while preserving the existing behaviour elsewhere:

- For debug builds (`cfg(debug_assertions)`), `serve_dir_cached` now uses `ServeDir::new(&path)` when it encounters a directory. This means `/wasm`, `/assets`, and any other subdirectories are backed by a live directory listing, so new files like wasm patch modules and freshly emitted hashed CSS become immediately visible without restarting the fullstack server or rebuilding its router.
- For non-debug builds, the previous behaviour is preserved: `serve_dir_cached` continues to recurse and register one route per discovered file, still using `ServeFile::precompressed_br()` and `cache_response_forever` for hashed, cache-busted filenames.

This is a no-op for release builds, but in debug fullstack it's necessary for wasm hot patches and other dynamically-created assets that could 404 until the server happened to be restarted. It also aligns fullstack dev behaviour more closely with the plain web devserver, which already uses `ServeDir` for the `public` tree.
2026-01-16 17:42:27 -08:00
..
2025-12-05 12:14:23 -08:00

Dioxus Fullstack

Crates.io MIT licensed Build Status Discord chat

Website | Guides | API Docs | Chat

Fullstack utilities for the Dioxus framework.

Features

  • Integrates with the Axum server framework with utilities for serving and rendering Dioxus applications.
  • Server functions allow you to call code on the server from the client as if it were a normal function.
  • Instant RSX Hot reloading with dioxus-hot-reload.
  • Passing root props from the server to the client.

Example

Quickly build fullstack Rust apps with axum and dioxus.

use dioxus::prelude::*;

fn main() {
    dioxus::launch(|| {
        let mut meaning = use_action(|| get_meaning("life the universe and everything".into()));

        rsx! {
            h1 { "Meaning of life: {meaning:?}" }
            button { onclick: move |_| meaning.call(), "Run a server function" }
        }
    });
}

#[get("/meaning/?of")]
async fn get_meaning(of: String) -> Result<Option<u32>> {
    Ok(of.contains("life").then(|| 42))
}

Axum Integration

If you have an existing Axum router or you need more control over the server, you can use the DioxusRouterExt trait to integrate with your existing Axum router.

First, make sure your axum dependency is optional and enabled by the server feature flag. Axum cannot be compiled to wasm, so if it is enabled by default, it will cause a compile error:

[dependencies]
dioxus = { version = "*", features = ["fullstack"] }
axum = { version = "0.8.0", optional = true }
tokio = { version = "1.0", features = ["full"], optional = true }
dioxus-cli-config = { version = "*", optional = true }

[features]
server = ["dioxus/server", "dep:axum", "dep:tokio", "dioxus-cli-config"]
web = ["dioxus/web"]

Then we can set up dioxus with the axum server:

#![allow(non_snake_case)]
use dioxus::prelude::*;

// The entry point for the server
#[cfg(feature = "server")]
#[tokio::main]
async fn main() {
    use dioxus_server::DioxusRouterExt;

    // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address
    // and we use the generated address the CLI gives us
    let address = dioxus::cli_config::fullstack_address_or_localhost();

    // Set up the axum router
    let router = axum::Router::new()
        // You can add a dioxus application to the router with the `serve_dioxus_application` method
        // This will add a fallback route to the router that will serve your component and server functions
        .serve_dioxus_application(dioxus_server::ServeConfig::new(), App);

    // Finally, we can launch the server
    let router = router.into_make_service();
    let listener = tokio::net::TcpListener::bind(address).await.unwrap();
    axum::serve(listener, router).await.unwrap();
}

// For any other platform, we just launch the app
#[cfg(not(feature = "server"))]
fn main() {
    dioxus::launch(App);
}

#[component]
fn App() -> Element {
    let mut meaning = use_signal(|| None);

    rsx! {
        h1 { "Meaning of life: {meaning:?}" }
        button {
            onclick: move |_| async move {
                if let Ok(data) = get_meaning("life the universe and everything".into()).await {
                    meaning.set(data);
                }
            },
            "Run a server function"
        }
    }
}

#[server]
async fn get_meaning(of: String) -> ServerFnResult<Option<u32>> {
    Ok(of.contains("life").then(|| 42))
}

Getting Started

To get started with full stack Dioxus, check out our getting started guide, or the full stack examples.

Contributing

  • Report issues on our issue tracker.
  • Join the discord and ask questions!

License

This project is licensed under the MIT license.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Dioxus by you shall be licensed as MIT without any additional terms or conditions.