Lustre v5.5.0 released!

Things have been quiet over here for the last few months, but we’re launching into 2026 with a release that makes Lustre even faster and plugs some gaps that improve its production-readiness.

Optimising render performance with element.memo

Like many other frameworks, Lustre works by implementing a virtual DOM. This means when your view function is called, instead of constructing HTML elements directly we instead produce a lightweight representation of the DOM and calculate the minimal set of DOM updates necessary to bring things up to date.

Lustre’s vdom implementation is highly optimised and should not be a problem for many production workloads. For performance-sensitive applications, however, other frameworks often provide ways to hint to the runtime when rendering can be skipped. In React this is hooks like useCallback or useMemo. In Elm, these are functions in Html.Lazy.

Lustre now also gains this ability, with the element.memo and element.ref functions. When used effectively, these functions can massively cut down Lustre’s render time: adding a single element.memo call to our TodoMVC benchmark app improved performance by 60%!

And let’s take a look at the code added to the benchmark app:

  fn view_entry(entry: Entry) {
+   use <- element.memo([element.ref(entry)])
    let Entry(description:, id:, completed:, editing:, ..) = entry
  
    html.li(
      [attribute.classes([#("completed", completed), #("editing", editing)])],
      [...],
    )
  }

element.memo requires two things to work: a list of dependencies - constructed with element.ref - and a view function that takes no arguments and returns a normal Lustre Element.

The first time Lustre renders a memoised element, it immediately evaluates the view function and caches the list of dependencies. During subsequent renders, Lustre will first check each dependency in the list with the new dependency list for reference equality. This is a special kind of equality in JavaScript that doesn’t just check if two values share the same structure but also if they share the same location in memory, making it a very quick check to make.

If all dependencies are reference equal, Lustre does nothing else: it does not need to call the view function and diff the vdom and instead it can move on to the rest of the diff.

In cases like TodoMVC where we render many of the same element in a list but only one changes at a time, element.memo means Lustre spends almost no time at all on parts of the vdom that haven’t changed.

You might be tempted to slap element.memo on everything and reap infinite performance benefits, but it’s not quite that simple. Specifically, there are three main points to take note of when considering element.memo:

  1. Values in the dependency list are checked for reference equality only. This is an important departure from Gleam’s semantics which only has structural equality defined. That means if you construct something like a new custom type ever render, even if the values contained don’t changed the reference to the underlying JavaScript object does change, invalidating the memo cache.

  2. For dependencies that regularly change, element.memo becomes pure overhead; incurring a memory cost of storing those dependencies and runtime cost to regularly invalidating the cache and re-evaluating the view.

  3. It is very easy to accidentally leave out a dependency from the list and this can be tremendously difficult to debug!

With that in mind, most apps can happily continue to work without ever needing to reach for element.memo. But now the few apps that need this extra performance have the tools they need to squeeze it out in a pinch. A huge thanks to rebecca~ for getting this done, it took us many attempts!

Better virtualisation

While we’re thinking about the vdom, let’s talk about virtualisation. When you mount a client Lustre application onto a DOM node that already has some content inside of it, Lustre will convert the existing DOM into a vdom tree that the first view result can diff against.

This is a technique used to make sure Lustre doesn’t waste time reconstructing elements that already exist and works great when an app is prerendered on the server, but our implementation wasn’t perfect.

In particular, using element.fragment or element.map in the prerendered HTML would end up virtualising to a slightly different vdom as those elements don’t exist in the HTML at all! This new release now produces comments to mark where fragment, map, and memo nodes start and end meaning virtualisation is much more accurate.

Once again we have rebecca~ to thank for landing these improvements as part of her work on element.memo!

Tighter integration with gleam/otp

Lustre is a universal framework, capable of not just rendering HTML strings on the server but also running full applications that stream UI updates over a WebSocket. These are called server components and when running on the Erlang target they spawn a new process when started.

One of the BEAM’s biggest strengths (that’s the virtual machine Erlang and Gleam run on) is it’s fault tolerance achieved through a tree of supervisors: special processes that can isolate crashes and restart failed parts of an application without affecting other parts.

Until now a server component started on the Erlang target has been unable to participate in supervision due to Lustre’s constraints as a framework capable of running on all of Gleam’s targets. Lustre v5.5.0 remedies this by introducing three new functions specifically for participating in OTP and supervision:

These three additions make Lustre’s server components much more suitable for use in production. We look forward to seeing what folks build!

And the rest

Users rendering SVG elements to string in Lustre should see significantly less-noisy output as Lustre is now much smarter about when to include xmlns attributes. A handful of missing attributes have been added – if you spot any more, PRs are always welcome! And finally, internally Lustre has begun migrating it’s JavaScript FFI code to use Gleam’s newer data representation.

Search Document