Hot Reloading in Rust? Subsecond and Dioxus to the rescue!


I recently set out to finally get hot reloading working for my Rust GraphQL API, after getting annoyed by the usual cargo watch flow, which would kill my server in between code changes and it finishing the rebuild + setup.

I had previously played around with subsecond from Dioxus, while it was still in beta, but hadn’t had much success. Sometime in August last year (2025), a few improvements got added in PR #4588, namely convenience functions for serving async functions with hot reloading. I did wish their own docs on hot reloading would have mentioned it ๐Ÿ˜ฎโ€๐Ÿ’จ

So it was time to give it another try!

The process was pretty straightforward:

  1. Add dependency: cargo add dioxus_devtools --features serve
  2. Wrap your entry point with dioxus_devtools::serve_subsecond_with_args
  3. Install dioxus-cli: curl -sSL https://dioxus.dev/install.sh | bash (see docs)
  4. Run: dx serve --hot-patch
  5. ๐ŸŽ‰

I’ll add a few more details to each of the steps below.

The only dependency we need is the dioxus-devtools crate (version 0.7.3 as of writing this).

Importantly we need to add the serve feature to it (cargo add dioxus_devtools --features serve). This is what gives us dioxus_devtools::serve_subsecond_with_args.

The restructure of our application’s entry point was one of the less obvious things, at least until #4588 got merged. We’ll do it in three simple steps:

  1. Split out any setup of env, database, tracing, etc, so its done first
  2. Split out your main async server function
  3. Pass the setup and server function to dioxus_devtools::serve_subsecond_with_args

This could look something like this, with your main entry point being:

1#[tokio::main]
2async fn main() {
3 let app_env = setup_app_env().await.unwrap();
4 dioxus_devtools::serve_subsecond_with_args(
5 app_env,
6 |s| async { server(s).await }
7 ).await;
8}

And your setup_app_env looking like this:

1#[derive(Clone)]
2struct ApplicationEnv {
3 port: i32,
4 main_db: ArcDb>,
5}
6
7async fn setup_app_env() -> ResultApplicationEnv, Error> {
8 let port = env::var("MS_GRAPHQL_PORT")
9 .map(|p| p.parse::i32>().expect("MS_GRAPHQL_PORT must be a number"))
10 .unwrap_or(3065);
11 let main_db = Arc::new(Db::new(main_url, main_token).await?);
12 Ok(ApplicationEnv { port, main_db })
13}

and server could look something like:

1async fn server(config: ApplicationEnv) -> Result(), Error> {
2 use axum::{Router, routing::get};
3
4 let ApplicationEnv { port, main_db } = config;
5 let app = Router::new().route("/", get(test_route));
6 let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)).await.unwrap();
7 println!("Server running on http://localhost:{}", port);
8 axum::serve(listener, app.clone()).await.unwrap()
9}

You’ll of course need to adjust this to your actual code, but I thought I’d give a slightly more complex example to illustrate the point.

Finally, we’ll be running the hot reloading server with using the dioxus cli which takes care of coordinating with subsecond for rebuilding our app. Follow their instructions to install it (or run curl -sSL https://dioxus.dev/install.sh | bash), and then you should now have access to dx serve --hot-patch which will start the server with hot reloading, looking something like this:

โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ /:more โ•ฎ
โ”‚  App:     โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”  ๐ŸŽ‰           Platform: MacOS                     โ”‚
โ”‚  Bundle:  โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”  ๐ŸŽ‰           Features: ["local"]                 โ”‚
โ”‚  Status:  Serving ms-graphql ๐Ÿš€                    Server at: no server address        โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Beautiful! ๐ŸคŒ

You can now code away happily and speed up your fullstack work!

I’ve been using this for a few days now for our main GraphQL API serving the backend for Yaay, and it’s been a quite nice addition as a DX improvement.

A big thanks to the amazing team at Dioxus for making subsecond possible to use outside of the Dioxus framework, and for pushing the frontier of Web dev-like DX in Rust!

As mentioned earlier, you probably don’t want this out in production, so I’d recommend putting it behind a feature flag. I’ve personally gone with local.

You’ll need a small change to your entry point:

1#[cfg(not(feature = "local"))]
2#[tokio::main]
3async fn main() {
4 let app_env = setup_app_env().await.unwrap();
5 server(app_env).await;
6}
7
8#[cfg(feature = "local")]
9#[tokio::main]
10async fn main() {
11 let app_env = setup_app_env().await.unwrap();
12 dioxus_devtools::serve_subsecond_with_args(
13 app_env,
14 |s| async {
15 server(s).await
16 }
17 ).await;
18}

and you can also adjust your dependencies to only include the dioxus-devtools crate when the local feature is enabled:

1
2[features]
3
4local = [
5 "dep:dioxus-devtools",
6]
7
8[dependencies]
9dioxus-devtools = { version = "0.7.3", optional = true, features = ["serve"] }
10

Some links that were helpful in piecing all of this together:



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *