So I wrote a bunch of LiveView, and Surface UI
and also kept using React on the side and my impressions recently are that LiveView/Surface really matured over the last few years but - even so - the choice between tech stack to use is not super obvious.
First off, it’s easier to find JavaScript developers, and also pure backend Elixir developers than developers who are comfortable and good doing both front-end and back-end with LiveView/Surface. So that’s a factor you have to take into consideration.
Secondly, if the objective is to reduce the cognitive load on the developer, by reducing the number of moving parts, LiveView/Surface are not exactly doing that, not if you’re building a moderate to big app at least. BTW, this was not the original LiveView use case, the authors originally said it’s not meant to replace complicated JS apps but here we are nevertheless, and often you don’t even know how the app/project will grow in it’s life time. When you write LiveView you have to deal with, and understand, something like the following stack:
- build system (be it default esbuild, bun or webpack) for JS/hooks compilation, minifying etc.
- JavaScript runtime for the Hooks and otherwise JavaScript front-end code you will need
- stateless Components life cycle/mounting/usage
- stateful Components life cycle/mounting/usage
- LiveViews life cycle mounting/usage
- message passing between Components of various types, and LiveView and Hooks
- ad-hoc JavaScript generation and execution with %JS{} stuff
- how the CoreComponents work and how to customize it/change/write your own
- how the Phoenix controllers work and how to expose some data through them or maybe API and how to mount LiveViews in it and maybe how to use Components from them
- how the layouts work, and when is what rendered, how do you update things like page title and meta tags
- how to trigger the navigation and manage the push state from LiveView, how to do something like that from Component, or Controller, and another way to do it from JavaScript Hooks
- how the Plug works
- how GenServers work and what is the handle_info and the message passing and the queue of messages and blocking and doing the stuff async and recovery from crashes
- maybe how the Cowboy handlers work in rescuing from exceptions
- how to manage the state on hand, including Cookies and Session state and just the UI state / data from the database, how to load it, keep around, pass betweeh LiveView, Components, Controllers or Plugs
- how to build forms that are backed up or not by database tables and how to handle them, how the handling differs between Components and Controllers and LiveView
generally, it’s a surprisingly large body of knowledge to both acquire and keep up to date with.
Now if you’re building a React single page app with an Elixir backend, you also end up with a big body of knowledge to acquire and keep up to date with but you get many benefits you do not have with LiveView, like the ability to reach towards numerous UI component libraries, commercials and free alike, use some excellent state management libraries / techniques, which IMHO are really nice since the invention of Hooks (React Hooks, not LV Hooks), use the finest HTTP client libraries, GraphQL libraries (Apollo!) and also push a lot of the CPU cycles and memory away from your servers to your client browsers.
There’s also more stuff you can do, not just complete offline mode, but handling weak/poor/changing network conditions, users suspending/resuming their laptops, or even things like server deployments or Kubernetes cluster rebalancing resulting in state loss/disconnections on LV and with a JS app this can be factored in in a seamless way.
A word about Surface, I actually like it more than pure LiveView. It’s features are leaking to LV too, which is good. It’s got some stub for a good state management with defined properties, data attributes and such, also more React/Web Components like interface when it comes to rendering children. But then you end up having to deal with another layer, on top of LiveView, that sits on top of all the other layers I discussed above and when you use some function it’s like okay where is it coming from and where in the documentation do I have to look for it and it may be in Plug or Phoenix or LiveView or in Surface or some other moving part and there’s just a lot of them.
I dabbled with Next.js about a 1 year ago exactly this time, around Christmas, and with React Server Components, which are not exactly the same as LiveView and, despite it being JavaScript and then quite buggy (I used the “app structure” that was in alpha/beta) the overall experience was more welcoming. It’s designed top-to-bottom, rather than bottom-to-top, and there’s like one main place for documentation, and the documentation is overall way shorter, there are fewer moving parts to wrap your head around. It’s quite funny, because we’ve been making fun of JavaScript ecosystem being fragmented, but there are also a very programmer-friendly environments there that do reduce the cognitive load of the programmer rather than increase it.
I think a Next.js-like solution for Elixir, maybe one that uses Surface or React as a rendering engine/layer, where the users can forget about the existence of Plug, Controllers and such things because they’re either implementation details and not exposed directly to them or are not used at all would be a step in interesting direction, but I haven’t seen anything like that yet.
There’s also a security consideration: with LV/Surface you end up with more code being executed on the server, with the full privileges of the BEAM process that calls these functions. A bug in either your own code or the code of a library could expose the whole VM and whatever it has access to on the database. A similar bug but on the client-side could end up with single user’s credentials/access to data being compromised more likely than a total hole in the system giving attacker access to everything. I don’t know what are the actual stats on what is actually happening in real life, but the vector of attack is just bigger when all of your functions run with the higher rather than lower privileges and access to data.
I actually don’t have the recommendation for you what to choose. If you go with LiveView/Surface, prepare yourself for having to deal more with a bunch of moving parts, and application state management may not be as clear and obvious as, say, in React with React Hooks or with Mobx or something like that. On the other hand, if you go with a JavaScript app, you have a different set of problems depending on the framework you choose, but will also have to come up with a clean data loading solution and the serialization, deserialization, etc. that will have to happen between the server and the front-end.