DEV Community

Cover image for Trying out Leptos: Fine-grained Reactive Framework for Rust
Rodney Lab
Rodney Lab

Posted on • Originally published at rodneylab.com

Trying out Leptos: Fine-grained Reactive Framework for Rust

🦀 Leptos and Fine-grained Reactivity

Leptos is Rust framework for full-stack web applications. I tried it out recently, when I built my own Linktree. In this post, I give you a quick introduction to Leptos, and offer some insight into what it does differently. We also run through the features I liked, as well as facets, which I need to explore more. If you have already tried Leptos yourself, I’m keen to hear your thoughts. Also, if you are considering trying it, let me know any of your burning questions that are missing here!

Full Stack Web-app Framework

Leptos is full-stack or isomorphic, meaning you use it to write code, which runs both on the server, and the front-end. It can create both server-side rendered (SSR) and client-side rendered (CSR) apps. For many applications, you will favour SSR, which is more SEO-friendly.

For building full-stack SSR apps, you might already be using Next.js, Remix or SvelteKit. Leptos shares features with these, though what sets it apart is that:

  • your server will send WebAssembly (WASM) code to the browser, rather than JavaScript;
  • Leptos uses fine-grained reactivity; and
  • for projects with a backend written in Rust for predictability, reliability or security, you can keep everything in the same language.

In common with the JavaScript frameworks mentioned, Leptos:

  • can be deployed from serverless environments;
  • supports “using the platform” and standard Web APIs; and
  • offers styling with CSS and Tailwind, among other options.

🌾 What is Fine-grained Reactivity?

Fine-grained reactivity is about running minimal code to effect precise DOM updates when some, relevant, piece of state changes. The Leptos approach is different to React’s; Leptos has no virtual DOM to update and reconcile. Instead, you add reactivity by supplying a function for Leptos to run, when state changes. Leptos runs the function efficiently, and only when one of the function’s inputs changes. In fact, this is important to remember. The app will not be reactive unless, for example, you provide a function to determine which branch of an if statement to render.

🧱 How to Create a Leptos App

Before you get going, you’ll face a steep learning curve, creating a Leptos project as your very first Rust project. Leptos concepts will not be too tricky to get used to, if you already know React. That said, I recommend building a CLI tool or creating an embedded Rust project, using microelectronics, before trying Leptos.

You have a couple of options for the underlying web framework to pair with Leptos: Axum or Actix. Axum seems to carry more favour currently, so we start with that. Assuming you already have Rust set up on your system:

  1. Install cargo leptos:
cargo install cargo-leptos
Enter fullscreen mode Exit fullscreen mode
  1. Get the Axum starter code:
cargo leptos new --git https://github.com/leptos-rs/start-axum
Enter fullscreen mode Exit fullscreen mode
  1. Change into the new project directory, then run the Leptos dev server in watch mode:
cargo leptos watch
Enter fullscreen mode Exit fullscreen mode

🌟 What I Built

There’s a lot of social media apps now, and my contacts page started to get a little too busy including them all. Instead of creating a Linktree profile to list all these accounts, I built my own, using Leptos.

I wanted to deploy the app serverlessly, opting for Deno Deploy hosting. The JS fetch example and the leptos-deno examples were handy to get me going.

I wanted to use cutting-edge CSS features like:

  • scroll-linked animation;
  • :has selectors; and
  • oklch colours.

I set up Lightning CSS to transform the CSS (adding polyfills for older browsers). For developer convenience, I wanted to have separate source CSS files, then bundle these into a single CSS file, for end-site speed and a better user-experience. At the time of writing, the Lightning CSS WASM version (for use with Deno) does not support bundling. I set up ESBuild to handle that (feeding ESBuild output into Lightning CSS for minification and transformation).

WASM Support

All popular browsers support WASM, however testing in Safari on macOS in Lockdown mode and also, on a hardened Chromium-based browser running on Android, I noticed the page was not hydrating. Enabling JavaScriptJIT manually in the Chromium browser. Though, ensuring the page to works if WASM and JavaScript are disabled would provide a more robust experience. Following the Leptos guidance of designing defensively, I set about implementing this.

Trying out Leptos: screenshot of microblogging widget shows Mastodon featured, with a link.  Below are buttons, each with just a logo.  The logos are for Bluesky, Nostr and X (previously Twitter)

I created a widget for each set of links in the app. For example, X (previously Twitter), Mastodon, Bluesky and Nostr share a widget. You only see full details for one link (initially Mastodon). Though, by clicking a button (for Bluesky, Nostr or X, for example) you promote that site’s details. This reactivity requires hydration.

To design defensively, when the page first loads, I created all the buttons as anchor tags. They only change to buttons when the page hydrates. This let me try out more Leptos features, managing state to display the correct details. That is, while keeping the page accessible and functional to users with JavaScript disabled or WASM blocked.

Leptos Reactivity

Many features will look familiar to React, as well as Svelte users. As an example, you let Leptos know a variable is reactive by assigning a function to it, similar to Svelte 5. Typically, you will use Rust closures for these functions. The APIs have names like create_effect, which almost sounds familiar if you already know React!

Here is an example code snippet:

#[component]
pub fn LinkGroup(link_group: LinkGroupStruct) -> impl IntoView {
    let (hydrated, set_hydrated) = create_signal(false);
    create_effect(move |_| {
        request_animation_frame(move || set_hydrated.set(true));
    });

    if hydrated.get() {
        view! {
            <TreeLink /* TRUNCATED...*/ />
        }.into_view()
    } else {
        view!{
            <a href=url aria-label=description >
                {get_icon( &icon)}
            </a>
        }.into_view()
    }
}
Enter fullscreen mode Exit fullscreen mode
  • createSignal in line 3 is a Leptos API used to create and update state
  • create_effect is line 4 is a Leptos API, which runs in the browser; here we update set_hydrated automatically, so the DOM updates reactively on hydration
  • Rust does not let you return different expression types from the two arms of an if statement. into_view is added to convert the anchor tag and the TreeLink component to the same type

Notice we call a function (hydrated.get), to determine the current state of the app, and decide which branch to render. Again, Leptos uses functions to update reactive elements efficiently.

🧡 What I Liked

  • Leptos compiles the app to WASM, giving you some choice over where you deploy. You can set up a Docker container for a long-running service, or use Deno Deploy, for example, if you prefer to go serverless
  • although you work in Rust, you have access to JavaScript Web APIs via the web_sys crate. I used that to call window.open, from Rust, to implement sharing the page on social networks.
  • Leptos uses the web primitives you are used, meaning you don’t have to learn new tools. I used ESBuild to minify emitted JavaScript code ready to serve on Deno Deploy and SVGO to minify the SVG sprite sheet for the app’s icons.

🧐 What I need to Explore More

Overall, Leptos provides a great developer experience. Anecdotally, I noticed a slight delay in the WASM initially loading, compared to Svelte apps I created using Astro. I have done no scientific measurement though, and once WASM has loaded the page is super snappy. Here are a few more aspects I need to explore further.

  • Hot reloading is slower than with modern JavaScript-based frameworks such as Astro, Remix and SvelteKit. I need to see if there are some optimizations for shortening the feedback loop.
  • I like concentrating on just getting the code down (albeit poorly formatted), then hitting save to get it looking pretty. I missed this for the web markup in Leptos, and need to look into what options are available for automatically formatting web markup.
  • I mentioned you can use the web_sys crate to access regular JavaScript Web APIs, however, these will probably always be a step behind on the latest APIs. If you need to have bleeding edge APIs, you might need to add implementations yourself to web_sys.

🥏 What's Coming

Islands architecture is on its way! The recent Leptos 0.5.0 release introduced experimental support the islands of interactivity with frameworks like Deno Fresh and Astro use to serve your pages at warp speed. The feature is still experimental, though demos show it can reduce your WASM bundle size by up to 80%. As well as Islands architecture, you can now create static routes with Leptos. Looking forward to exploring these new features.

🙌🏽 Trying out Leptos: Wrapping Up

In this post, we saw some key Leptos features and why you should consider it for your Rust web app. More specifically, we saw:

  • what fine-grained reactivity is;
  • how to create a Leptos app; and
  • some Leptos features you might like, such as support for JavaScript Web APIs.

I plan to open source code for a Linktree app, lie the one I created. You can see the live version at links.rodneylab.com. Let me know what you plan to build with Leptos, also whether you go down the Docker deployment route, give Deno a go, like I did.

🙏🏽 Trying out Leptos: Feedback

If you have found this post useful, see links below for further related content on this site. I do hope you learned one new thing from the video. Let me know if there are any ways I can improve on it. I hope you will use the code or starter in your own projects. Be sure to share your work on Twitter, giving me a mention, so I can see what you did. Finally, be sure to let me know ideas for other short videos you would like to see. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please consider supporting me through Buy me a Coffee.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on X (previously Twitter) and also askRodney on Telegram. Also, see further ways to get in touch with Rodney Lab. I post regularly on Deno as well as Search Engine Optimization among other topics. Also, subscribe to the newsletter to keep up-to-date with our latest projects.

Top comments (0)