Axum Tower ServeDir not serving subdirectories

I've got a monorepo of Axum as server and a Vite app as frontend. Vite bundles some resources in ui/dist directory like this:

img/
fonts/
assets/
index.html

I'm trying to serve this directory with ServeDir like:

    let serve_dir = ServeDir::new("ui/dist");
    let rest_routes = rest_api::get_router(app_state);

    let app = Router::new()
        .route("/", get_service(serve_dir))
        .nest("/api", rest_routes);

But except index.html I get 404s from anything under subdirectories like img, fonts.

I'm confused because ServeDir docs point out it should work with subdirectories.

Individual subdirs start working if I add:

let serve_assets = ServeDir::new("ui/dist/assets");

and

        let app = Router::new()
            .route("/", get_service(serve_dir))
            .nest("/api", rest_routes)
            .nest_service("/assets", serve_assets);

When I add a capture everything goes 404 including index.html:
.route("/{anything_goes}", get_service(serve_dir))

I checked this Axum example in Github, but I'm not sure it fits my use case with index.html at the root and subdirectory resources.

Do I have to add every subdirectory as a different service and use nest_service?

The line where you add the service isn't correct:

            .route("/", get_service(serve_dir))

The service will only get called for serving the root path.

I was encountering something related today and the path I took was just making it a fallback service.

        let app = Router::new()
            .fallback_service(ServeDir::new("_site"))

I actually first tried nesting the service on the root first, but axum panicked with the message Nesting at the root is no longer supported. Use fallback_service instead.

1 Like

You're right. This is the solution. A bit weird to use fallback_service to serve an app's main assets but it worked. Thanks!

You can avoid using Router all together by passing a MethodRouter like get_service directly to axum::serve.

let listener = TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, get_service(ServeDir::new("_site")).await?;

I guess it was a security concern, for example when you publish .war, some directories are excluded from servicing. I also met the problem writing own web server in Rust. Why should it service sub directories?

Digging deeper I found this and this.

It seems the issue is conflict with an existing fallback implementation rather than security related.

The first link mentions axum::routing::Router::merge can also be used instead. Didn't try that one.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.