Skip to content

Further extend the axum API to support embedded files through RustEmbed. - #4715

Open
metatoaster wants to merge 11 commits into
leptos-rs:leptos_0.9from
metatoaster:axum_embed
Open

metatoaster wants to merge 11 commits into
leptos-rs:leptos_0.9from
metatoaster:axum_embed

Conversation

@metatoaster

Copy link
Copy Markdown
Contributor

In this change I also added a couple other changes that was omitted due to how the setup didn't include a way to also include favicon.ico without the rest of the assets, as this is a feature with Lepto's start-actix template.

Currently a draft as I haven't had time to finish up the service side of things and the tests. I really want to get this in before 0.9 is actually out. At the very least get the favicon.ico in to make it more parity with what people expected with the previous fallback.

@metatoaster
metatoaster force-pushed the axum_embed branch 2 times, most recently from 055bcf8 to ec9e254 Compare May 19, 2026 16:11
@metatoaster
metatoaster force-pushed the axum_embed branch 2 times, most recently from 5442007 to 24fc3f4 Compare July 10, 2026 15:18
Provide a placeholder mode to allow the service of site pkg from bytes
directly integrated into the binary.
Ensure `RouterConfiguration` is in fact feature parity with the combined
`file_and_error_handler` in the default configuration, where all assets
are served from the root (e.g. `favicon.ico` on the root is available).

However, it also be possible to change where the assets are served from,
or be able to selectively serve specific assets e.g. only `favicon.ico`
is made available.  Both these configurations may be found in the
`start-actix` template.
This avoids forcing the end users from dealing with mode types and just
rely on the method names to describe what's being configured.  The mode
types are now made private.

Added a new constructor that will provide the assets by default and that
results in a configuration that serves the site root at `"/"`.  The
existing `new` method will simply provide just the favicon (to be added
in a later commit).
- This is to potentially allow the default features to provide routes to
  embedded resources using the same type of configuration (just with the
  variants that uses `ServeDir`).
- Renamed `BuiltIn` to `Embed`.
- Still need to complete the `Service` implementation to serve the
  embeded content.
- As that contains the improvement to allow compilation without the
  environment variable LEPTOS_SITE_ROOT defined.
This is still very rough, but at least we got something that works and
can pass the test, however with a caveat that the front-end must first
finish building before the backend given how the files must be available
for embedding.
@metatoaster
metatoaster marked this pull request as ready for review September 12, 2026 14:41
@metatoaster

Copy link
Copy Markdown
Contributor Author

CI might fail or not depending on how cargo-leptos (well, cargo) builds the test server - if the frontend does finish first the embedding of the static files will be done correctly, otherwise it obviously can't be included when the site pkg isn't available. Will need to change something, but for now I might change how that binary is built.

- Instead of implementing a substandard file service, take advantage of
  `ServeDir` directly and implement `Backend` and also implement the
  required traits for wrappers around the types returned by `RustEmbed`.
- This required a version bump of `tower-http` to 0.7.
This is a necessary change to avoid a race condition between the two
concurrent builds that run concurrently during `cargo leptos build`.
Currently there is no way to ensure the frontend pkg artefacts are
available before the server is finished building, a major consequence is
that the embedding of artefacts in the server build may simply not
include the complete frontend when embedding is specified.

In the interim, simply have the frontend be built separately before the
backend, but `cargo-leptos` also has another issue where it will
unconditionally delete `LEPTOS_SITE_ROOT` for all builds, even if the
`--server-only` was specified.  For this the additional workaround to
use `cargo` directly was done.

Though this change also brought an improvement to nextest configuration.
As the setup scripts feature now supports the specification of
environment variables as of 0.9.131 (brought into existence by yours
truly), the batch and shell scripts may now be removed and have what
needs to be executed simply be included in the `nextest.toml` file.
@metatoaster

Copy link
Copy Markdown
Contributor Author

I've cleaned up the potential for CI failures by building the frontend before the server, though I had to avoid using cargo leptos build for the server part directly due to cargo-leptos unconditionally removing LEPTOS_SITE_ROOT even for server builds, which obviously breaks everything when the frontend is ultimately needed.

In the mean time this embed feature should be considered experimental, as the standard workflow using cargo leptos build currently cannot guarantee a correctly working build of a server binary with the frontend built in. If that issue is fixed (e.g. by providing a flag to do frontend then server, and/or fix the removal of LEPTOS_SITE_ROOT for server only builds), or that the build is done manually like how the test is currently set up, the resulting binary well have the frontend fully self-contained.

@gbj gbj left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the direction of this is really good! And I definitely want to include it in 0.9. I left some notes on things I noticed while reviewing.

On the cargo-leptos front... I think either of your suggested fixes sounds fine. (Not removing LEPTOS_SITE_ROOT for --server-only seems like a good idea; I'll put together a cargo-leptos PR for that at the very least, and I don't think it needs to block this PR)

}
}

// TODO FIXME rename this to serve_site_root_service

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No time like the present if you want to rename it :-)

impl<APP, CX, SH, S, IV1, IV2> traits::RouterConfiguration<S>
for RouterConfiguration<APP, CX, SH, S>
#[cfg(feature = "default")]
impl<APP, CX, SH, S> RouterConfiguration<APP, CX, SH, S> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this impl block needs an SR generic, otherwise it defaults to SR = NoEmbedSiteRoot and the builder methods aren't available for the embed mode? I assume it's just an oversight that didn't get flagged by the compiler because of the default generic. Worth checking for any other instances.

@metatoaster metatoaster Sep 19, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good catch on that one, also no idea why I didn't make some of the methods not public, going to need to fill out some of these tests to figure out why I did what I did...

Edit: Oh right, I didn't make the underlying mode public because this is configured elsewhere.

Comment thread integrations/axum/src/service/embed.rs
// The favicon is included.
assert_favicon_ico(&service).await?;

// However robots.txt is not included to be served (even though it may be embedded).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand this part -- why is it desirable that robots.txt 404 even if it's embedded?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default setup is limited in what is routed, only routes the site pkg and the favicon, much like the existing behavior of the usual integration provided by the start_actix template. There is another configuration option that should route the whole site root, but that is covered in the non-embedded case but I really should also include that with the embedded configuration to be comprehensive. Again this was an oversight when I can't remember my trains of thoughts when development was spread over too many weeks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revisiting this review while I am not on a phone sitting outside somewhere: this specific case/limitation here is due to how this implementation limits the "scope" (in terms of the amount of files) being presented or routed to the end user, namely the approach is not a generic fallback handler but rather specific routes, and so resource at LEPTOS_SITE_ROOT may be embedded wholesale but only the pkg specific subroutes (plus the favicon) are actually routed. Naturally the other configuration will be needed.

Also, while continuing on this train of thought, I think it might also be beneficial to rather than exposing RustEmbed directly (albeit through a pub reexport), it might be good to introduce two new derive macro to cover both these cases and have the setup to automatically pick up on it. This one I might have to think a bit more about how to best approach it. Basically, rather than:

#[derive(Clone, Copy, Embed)]
#[folder = "$LEPTOS_SITE_ROOT"]
#[prefix = "/"]
struct SiteRoot;

Which has the end user having to remember to include all of those very specific set of derive macros and attributes, this probably is much more preferable (actual name tbd):

// Embed just the site pkg.
#[derive(EmbedLeptosSitePkg)]
struct LeptosSitePkg;

// Embed the whole site root.
#[derive(EmbedLeptosSiteRoot)]
struct LeptosSiteRoot;

Both will have other different private traits from which the config helper method may be implemented to do the correct thing for the relevant derive so that the correct sets of routes and/or fallback service may be set up on the underlying axum::Router.

Naturally, this can come later, I don't want this additional work to block the release of 0.9.0 needlessly (e.g. if I can't find time again because the infant/toddler catches influenza again). Since the traits have been sealed we should have the flexibility to introduce additional setup method without causing a semver breaking change to include this addition, though I think I might need to better present the API for the generic RustEmbed, perhaps be a bit clear with the documentation as I do want to simply have the config builder call .embed(...) and the correct thing just happens. Though I am not opposed to having .embed_site_root() and .embed_site_pkg(), too, as this also would help make it clear at a glance. Need to think about this more.

Comment thread Cargo.toml
dioxus-devtools = { default-features = false, version = "0.7" }
wasm_split_helpers = { default-features = false, version = "0.2.2" }
rust-embed = { default-features = false, version = "8.12" }
mime_guess = { default-features = false, version = "2.0" }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds mime_guess but I don't think the code actually uses it -- maybe stale? It appears in the workplace Cargo.toml and in the integrations/axum Cargo.toml but not in code except in the hackernews_islands_axum example

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that should be removed, it was part of the naive implementation that I had previously done before realizing that tower_service introduced the Backend trait for ServeDir a bit over a month ago.

Comment thread integrations/axum/src/config.rs
@gbj

gbj commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

leptos-rs/cargo-leptos#682 for my humble attempt at a cargo-leptos fix

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants