I'm having hard time understanding how to embed SPA (single page application) files with rust-embed
and axum
.
I have no trouble without rust-embed
using a single line of code with axum
(from here):
app.fallback(get_service(ServeDir::new("./app/static")).handle_error(error_handler))
It works because all files are correctly downloaded. But:
FIRST PROBLEM
What is missing for a properly SPA handling is the redirect on the index.html
if for example the user reloads the page on a SPA nested route.
Example: I'm on the page: /home/customers
which is not a file nor a dir but just a fake javascript route and if I reload the page axum gives me 404
(Not found).
SECOND PROBLEM
I need to embed those files in my final executable. In Golang this is "native" using embed:
directive.
I saw that in Rust this is well done with rust-embed
but I cannot complete my task for SPA.
The need is that every path typed by the user (and that is not an existent file such as .js
or .css
which obviously must be downloaded by the browser) leads to the "index.html
" file in the root of my static
dir.
If I use the example axum code I can see the route:
.route("/dist/*file", static_handler.into_service())
which has /dist/*file
and I don't need that /dist
because the index.html
calls many files with custom paths, such as /_works
, menu
, images
.
If I remove the dist
part I get this error:
thread 'main' panicked at 'Invalid route: insertion failed due to conflict with previously registered route: /index.html'
Can you help me understand how to properly accomplish this task?
Thanks.
11 Answer
I had a similar issue, building with Vue and Axum/Rust. Here's how I solved Problem one
tower_http
crateaxum::routing::get_service
to serve the build SPA.//example implementation ... //static file mounting let assets_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("views"); let static_files_service = get_service( ServeDir::new(assets_dir).append_index_html_on_directories(true), ) .handle_error(|error: std::io::Error| async move { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Unhandled internal error: {}", error), ) }); ...
//mount the app routes and middleware let app = Router::new() .fallback(static_files_service) .nest("/api/v1/", routes::root::router()) .layer(cors) .layer(TraceLayer::new_for_http()) .layer(Extension(database));
Check out the full source code here. Another thing is, Axum seems to have breaking changes in subsequent versions as I found out here, so you might need to check the doc/example that corresponds to the version of Axum you are using :)
ncG1vNJzZmirpJawrLvVnqmfpJ%2Bse6S7zGiorp2jqbawutJobmxsZmmBeIWOoaawZaSkeqm70q1krKiRYrOquMSsZJqmlGKyrq7EnWStp59ixKrAx2aYsa2dYq6vsIyrrKysXZq6o7HD