2 unstable releases

0.2.0 May 2, 2024
0.1.0 Feb 19, 2024

#209 in HTTP server

Used in leptos-use


755 lines

Spin/Leptos integration library

THIS IS A WORK IN PROGRESS. Actually 'in progress' probably oversells it. It is an early draft with a lot of learning as we go.

This library provides integration services for running Leptos server-side applications in Spin. It plays a role similar to Leptos' Actix and Axum integrations, bridging Spin's implementation of concepts and Leptos', and abstracting away common functionality to routing requests to Leptos views and server functions.

At the moment, this library is entirely experimental. It has known gaps, names and APIs will change, and Leptos experts will no doubt have much to say about its design!

Installing and running the template

The leptos-ssr template can be installed using the following command:

spin templates install --git https://github.com/fermyon/leptos-spin

Copying remote template source
Installing template leptos-ssr...
Installed 1 template(s)

| Name         Description                                    |
| leptos-ssr   Leptos application using server-side rendering |

Once the template is installed, a mew leptos project can be instantiated using:

spin new -t leptos-ssr my-leptos-app -a

Before building and running the project cargo-leptos needs to be installed:

cargo install cargo-leptos

To build and run the created project, the following command can be used:

cd my-leptos-app
spin build --up

Now the app should be served at

Special requirements

  • All server functions (#[server]) must be explicitly registered (see usage sample below). In native code, Leptos uses a clever macro to register them automatically; unfortunately, that doesn't work in WASI.

  • When using a context value in a component in a feature = "ssr" block, you must call use_context not expect_context. expect_context will panic during routing. E.g.

fn HomePage() -> impl IntoView {
    #[cfg(feature = "ssr")]
        if let Some(resp) = use_context::<leptos_spin::ResponseOptions>() {
            resp.append_header("X-Utensil", "spork".as_bytes());

    view! {
        <h1>"Come over to the Leptos side - we have headers!"</h1>


use leptos::ServerFn;
use leptos_spin::{render_best_match_to_stream, RouteTable};
use spin_sdk::http::{ResponseOutparam, IncomingRequest};
use spin_sdk::http_component;

async fn handle_request(req: IncomingRequest, resp_out: ResponseOutparam) {
    let mut conf = leptos::get_configuration(None).await.unwrap();
    conf.leptos_options.output_name = "sample".to_owned();

    // A line like this for every server function

    let app_fn = crate::app::App;

    let mut routes = RouteTable::build(app_fn);

    render_best_match_to_stream(req, resp_out, &routes, app_fn, &conf.leptos_options).await


~580K SLoC