From 1c3b2aa4df294779b35a7bbfe74e7267d9e6ff25 Mon Sep 17 00:00:00 2001 From: "Amar Sood (tekacs)" Date: Tue, 18 Nov 2025 00:33:00 -0500 Subject: [PATCH] Use ServeDir for fullstack dev static assets 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-patch-.wasm`; the CLI tells the browser to fetch `/wasm/lib-patch-.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. --- packages/fullstack-server/src/server.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/fullstack-server/src/server.rs b/packages/fullstack-server/src/server.rs index a8a1678ef8..925f61ca2e 100644 --- a/packages/fullstack-server/src/server.rs +++ b/packages/fullstack-server/src/server.rs @@ -407,7 +407,7 @@ fn serve_dir_cached(mut router: Router, public_path: &Path, directory: &Pa where S: Send + Sync + Clone + 'static, { - use tower_http::services::ServeFile; + use tower_http::services::{ServeDir, ServeFile}; let dir = std::fs::read_dir(directory) .unwrap_or_else(|e| panic!("Couldn't read public directory at {:?}: {}", &directory, e)); @@ -430,7 +430,19 @@ where ); if path.is_dir() { - router = serve_dir_cached(router, public_path, &path); + // In debug builds, serve directories dynamically so new files (like + // wasm patch modules) are immediately visible without rebuilding + // the router. In release, keep the previous cached traversal + // behaviour. + #[cfg(debug_assertions)] + { + router = router.nest_service(&route, ServeDir::new(&path)); + } + + #[cfg(not(debug_assertions))] + { + router = serve_dir_cached(router, public_path, &path); + } } else { let serve_file = ServeFile::new(&path).precompressed_br(); // All cached assets are served at the root of the asset directory. If we know an asset