HanziHero is a Chinese character learning web application that leverages the powers of mnemonics and spaced repetition to help you learn thousands of Chinese characters!
It is written in Elixir using the Phoenix Framework. Earlier versions of the application used Phoenix LiveView before switching over to using a combination of small composable JavaScript libraries:
In this article, we will go over why we choose to go with this approach instead of using LiveView.
LiveView works by holding a stateful websocket connection. This means anytime you do a deployment, you will need a strategy to persist and restore that state. This can be done via encoding that information in the URL params or hash params, or saving the state in Redis or Postgres.
Failing to do so will result in a jarring user experience during deployment, as the LiveView will “reset” as through the user has visited the page for the first time once more. For our quiz component, we saved the quiz session state in Postgres and it worked well enough.
However, we still ran into some rough edges as we needed to evolve the state table schema and data within it during migrations.
In comparison, rolling deployments are trivial for traditional stateless applications. Simply have the reverse proxy route all new requests to the newer version of the app. Since there are no stateful websocket connections, there is no need to worry about draining old connections.
In short, one pays for LiveView in the form of deployment complexity.
LiveView has not reached 1.0 yet, so it is subject to frequent changes. For example, in the past year the recommended templating language changed from LEEX to HEEX. The HEEX is great and a huge improvement, but it was a bit of effort to make the changes to our existing templates. We could have kept the LEEX templates and still be supported, but most tutorials used HEEX, even before Phoenix 1.7 came out.
Related to the previous point, we also ran into an issue where a status code sent by the Caddy reverse proxy when doing rolling deploys would result in the Phoenix LiveView application completely breaking.
All of this is to be expected, and I think for many they are willing to accept paper cuts to stay close to the cutting edge.
Our JavaScript libraries are all more stable, but still not as much as we would like. Mithril being the shining example, which has had the same API more or less unchanged for over half a decade now.
Most changes in Phoenix LiveView requires a server-round trip to process. Therefore, it is suggested that LiveView should only be used for interactions that would need a server round-trip anyway, like fetching extra data or updating a form.
The main interactive widget on our site is our quiz page. There users are quizzed on items they previously learned, and they type in the answer to be graded.
We initially thought using LiveView would be fine for this, since we wanted to do a round-trip anyway to update the user data when they answer a question.
Unfortunately, the visible delay from hitting ENTER and the input turning red or green resulted in a degraded experience.
Given that the quiz widget is what our users interact with the majority of the time on our site, we felt that using a custom built JavaScript client was necessary, so used Mithril instead.
However, we think most websites don’t have latency requirements like this. For standard form updates and lazy loading data, LiveView is not going to be any slower than any SPA - in fact it may be faster.
Phoenix LiveView is pretty magical in what it can do. It does state change tracking to send down incremental updates over a websocket to update the client DOM.
Unfortunately, we personally think it is harder to understand and tinker with than the traditional MVC model. I don’t think this is anything inherit in the Phoenix LiveView model per se, but depends on the person. I’m sure there are others that think the opposite!
For example, if we hit an issue with HTMX AJAX-powered SPA-like navigation, looking through the HTMX code is usually pretty simple. After all, it is a single file with no transpiler. And I can easily reason about how things are working because it is just a thin layer on top of traditional stateless HTTP requests, which can be easily inspected and understood.
However, for LiveView, one needs to have a deep understanding of the LiveView lifecycle instead. In exchange, you have less code you need to write and maintain.
Picking LiveView or any other frontend approach really comes down to one’s own tastes and the requirements of the application.
For us, we prefer the more established REST MVC model with sprinkles of JavaScript. Additionally, the only part of our application that requires complex interactions is latency-sensitive which necessitated client-side code anyway, removing the main benefit of LiveView.
We think LiveView is cool technology, and have nothing but respect for the entirety of the Phoenix team. I can see it growing in popularity, especially after it hits 1.0. However, I do think there will be many, like us, who will stick with more traditional Phoenix applications.