Skip to content

Full change to #4528#4535

Draft
metatoaster wants to merge 3 commits intoleptos-rs:leptos_0.9from
metatoaster:leptos_site_pkg_dir_route
Draft

Full change to #4528#4535
metatoaster wants to merge 3 commits intoleptos-rs:leptos_0.9from
metatoaster:leptos_site_pkg_dir_route

Conversation

@metatoaster
Copy link
Contributor

@metatoaster metatoaster commented Jan 10, 2026

See #4528 for the discussion, but the intent here is to split out the final two commits which include the breaking changes.

The new method will benefit from at least a test or two to verify that the route providing the assets remains working in the future, I'll get this in at some point.

Making this a draft request until the other non-breaking changes are merged, unless it was decided to do a full breaking change.

@metatoaster metatoaster marked this pull request as draft January 11, 2026 06:38
@metatoaster metatoaster force-pushed the leptos_site_pkg_dir_route branch from fc97c1e to 1235f95 Compare February 12, 2026 13:25
@metatoaster metatoaster marked this pull request as ready for review February 12, 2026 13:29
@metatoaster
Copy link
Contributor Author

metatoaster commented Feb 12, 2026

This change now provides the following shortcut methods for doing the site_pkg_dir and the error fallback:

Router::new()
    .leptos_routes(&leptos_options, routes, {
        let leptos_options = leptos_options.clone();
        move || shell(leptos_options.clone())
    })
    // this:
    .leptos_route_site_pkg_dir(&leptos_options, shell)
    .leptos_route_fallback(&leptos_options, shell)
    // instead of this:
    // .fallback(leptos_axum::file_and_error_handler(shell))
    .with_state(leptos_options),

Naturally, leptos_route_fallback isn't a strictly necessary addition as it's a shortcut for

    .fallback_service(ErrorHandler::new(shell, leptos_options))

Naturally we could go one step further to offer another method that combine the two to effectively replicate file_and_error_handler in one step, but maybe that's at the point where adopting the service builder pattern to customize each and every aspect of all the .leptos_routes* methods while also avoid repeating leptos_options and shell everywhere. I am open to doing this if that's desirable.

@metatoaster metatoaster force-pushed the leptos_site_pkg_dir_route branch 2 times, most recently from 0478a9e to 02db640 Compare February 13, 2026 02:16
- In `LeptosRoutes::leptos_routes`, the first argument `options` is set
  up as a context available for the application, while `app_fn` is just
  basically `Fn() -> impl IntoView`.  Note that the `options` is the
  axum state `S` that must be provided by `Router::with_state`.
- Note that the only restriction for `S` is that it must satisfy the
  bound `LeptosOptions: FromRef<S>` (necessary for SSG (Static Site
  Generation) as the configuration needs to know where things are).
- Often, the `app_fn` is actually passed as a closure that calls an
  `fn shell(options: LeptosOptions) -> impl IntoView`, where `options`
  is cloned into the closure.
- This `shell` function is typcially set up by the `start-axum` template
  so it's actually quite pervasive.
- Moreover, the `file_and_error_handler` also simply accept that exact
  `shell` function directly, with the type constrant directly in place,
  even though this rigid constraint isn't a given nor a requirement.
- Hence, it should be possible to have the `ErrorHandler` to accept the
  more generic `S`, given the `LeptosOptions:FromRef<S>` bound.
- One being `leptos_route_site_pkg_dir` for adding routes specifically
  for assets managed via `cargo-leptos`.
- The other being `leptos_route_fallback` which sets up `ErrorHandler`
  as a fallback service with less keystrokes.
- Also amended documentation with examples where applicable.
@metatoaster metatoaster force-pushed the leptos_site_pkg_dir_route branch from 02db640 to a9d7feb Compare February 15, 2026 10:49
@metatoaster metatoaster marked this pull request as draft February 15, 2026 11:01
@metatoaster
Copy link
Contributor Author

metatoaster commented Feb 15, 2026

I've been thinking about the axum integration (and actix to a much smaller extent), I think given how patterns have started to emerge I think I have a better idea to formalize everything into a configuration builder to get the axum::Router all set up in much less boilerplate for the default case with a configuration builder.

Moreover, now that I've stepped back out to take a grander view of this integration, I found that the typical shell (from the leptos-rs/start-axum template) and the file_and_error_handler are actually somewhat coupled when they really shouldn't be, as the shell that the template provides expects a LeptosOptions which feeds into file_and_error_handler as that also expects this, while the entire time the LeptosRoutes::leptos_routes and friends allow all kinds of S as long as it satisfies the bound LeptosOptions: FromRef<S>. Hence I pushed a change that cleaned up ErrorHandler so that it also doesn't require LeptosOptions at all.

Backtracking to the configuration builder, perhaps it may be beneficial to formalize that and provide documentation to show that by using the existing state can serve as an AppState that tracks additional common configuration values (e.g. analytics/firebase tokens), how to make that AppState usable (ensuring LeptosOptions: FromRef<S>) and that avoids having to use leptos_routes_with_context.

To expand a bit with an example:

In app.rs:

#[derive(Clone)]
#[cfg_attr(feature = "ssr", derive(axum::extract::FromRef))]
pub struct MyState { 
    pub leptos_options: LeptosOptions,
    // doesn't matter what this is, these are additional States for the router
    pub token: Token,
} 
        
pub fn shell(state: MyState) -> impl IntoView {
    todo!()
}

In main.rs:

async fn main() {
    let my_state = MyState { leptos_options, .. };
    let routes = generate_route_list(App);

    let app = Router::new()
        .leptos_routes(&my_state, routes, {
            let my_state = my_state.clone();
            move || shell(my_state.clone())
        })
        .fallback_service(ErrorHandler::new(shell, my_state));
        .with_state(my_state);
    todo!()
}

The configuration helper I am thinking of could simply configure the App, S (default to LeptosOptions), the shell, and then an optional extra context, and it might end up looking like (name pending):

let config = LeptosAxumConfig::default()  // or `LeptosAxumConfig::new()` to disable defaults
    .app(App)
    .shell(shell);
    // this could even be optional as `get_configuration` could be called automatically
    // .state(leptos_options);
let app = Router::new()
    .leptos_config(config);

With that we don't need to worry about calling generate_route_list(App) and setting up the default file handler and error handler, though naturally that should be possible to be toggled. The idea of this helper is to have less typing going forward, it will also basically use the existing configuration methods as they are working fine still.

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.

1 participant