What are the benefits and drawbacks of rendering HTML on the server with LiveView (after the first load)?

All UI interactions can be divided into two categories: those that need to fetch data from the server and those that don’t. Presumably, even LiveView agrees that the latter need not go through the server, since it provide ways to handle them on the client with hooks and commands.

The point of disagreement with SPA frameworks is where the HTML that depends on the server’s data should be rendered. SPAs prefer to receive the data as JSON and handle its presentation on the client, whereas LiveView does it on the server.

Setting aside the problems of SEO and first contentful paint, which SPAs have more or less solved, is one way better than the other for post-page-load interactions?

Of course, there’s the convenience of not having to fiddle with the communication between the front and the back ends or divide your tech stack, but you can achieve similar developer experience with React’s and Leptos’ server functions; and though I’ve not used it, I believe Inertia serves the same purpose. (Note: I’ve only used Leptos’s server functions and am guessing React’s do the same thing. If it doesn’t, please don’t rake me over the coals.)

This isn’t criticism—I’m just trying to learn which way to take for what project.

2 Likes

I’d say that, even with Inertia and similar tools that abstract the communication layer for you, you still have one additional step, which is to transform your database entries into a format that is safe and efficient to send to the client.

For example, if you are rendering posts summaries, you want to avoid sending the whole body as well as some admin fields you need to filter out. So you need to do “DB → JSON Render → HTML Render”. Of course, those tools do the job of piping the JSON into props and similar, but you still need to care about that intermediate representation (and getting it wrong is often a security issue). LiveView (and other server-side solutions) allow you to skip that whole intermediate representation. You go from DB → HTML Render. That said, if you want to use Phoenix and you need to integrate with existing client-side frameworks, I am a fan of Inertia.js’ approach.

Furthermore, in most client and server-side solutions, you have the problem of when the HTML is sent. Some clients may send it upfront (so you load markup that you will never use) and some servers send the whole HTML over and over again. While more tools are trying to automate this, LiveView is afaik top of the class when it comes to optimizing and sending data when needed if needed.

One downside of LiveView is that, similar to all SPAs, the initial server response requires hydrating (in our case, over websockets). But since you are worried about post-page-load interactions, the above should be a good summary.

10 Likes

Oh, wow, didn’t expect to see a reply from you! Thanks for taking the time to explain it to me. Could I trouble you for a few clarifications?

For example, if you are rendering posts summaries, you want to avoid sending the whole body as well as some admin fields you need to filter out. So you need to do “DB → JSON Render → HTML Render”. Of course, those tools do the job of piping the JSON into props and similar, but you still need to care about that intermediate representation (and getting it wrong is often a security issue). LiveView (and other server-side solutions) allow you to skip that whole intermediate representation. You go from DB → HTML Render.

Do you mean that data that’s not referenced in a template will by definition be skipped and not sent to the client? If so, I think this is a problem only because (if I understood it right) Ecto auto-generates types from a schema, and by serializing and sending that type, you might be revealing sensitive information. On the other hand, if the type is defined manually (either instead of auto-generation or to complement it as a kind of view model), that shouldn’t happen, right?

Furthermore, in most client and server-side solutions, you have the problem of when the HTML is sent. Some clients may send it upfront (so you load markup that you will never use) and some servers send the whole HTML over and over again. While more tools are trying to automate this, LiveView is afaik top of the class when it comes to optimizing and sending data when needed if needed.

Yeah, I’ve read and bookmarked that article! Do you mean that, given this example view:

<%= if @counter == 0 do %>
  <p>Nobody clicked the button yet.</p>
<% else %>
  <p>counter: <%= @counter %></p>
<% end %>

<%= render_button(@counter) %>

If the user never increments the counter, they only receive <p>Nobody clicked the button yet.</p>, whereas in an SPA, the whole thing would be compiled into the JS and sent over the wire? In your experience, roughly how much bandwidth does this save in practice? I’m coming up blank trying to remember apps with big chunks of the UI hidden behind conditionals.

On the other hand, using the Twitter example, there’s no difference, right? The server would merely send new like count and so on.

Any application with authorization rules will have more fields in the database than the ones shown to the user. For a given page, it doesn’t really matter if you outline the fields that you want to retrieve by defining types, by writing queries, etc, in both server and client side frameworks, you have to write logic in the template that says “do not try to show this information, because this user does not have access to it”. But in the client case, you also to need to make sure you don’t send the data. This is not a concern for server ones. If your client framework avoids some boilerplate around this, by reusing types, then it gets a :+1:, but it is something you still need to be aware (and messing it up can be a large problem). Server side ones do not have the duplication.

In your example, when counter is zero, it sends <p>Nobody clicked the button yet.</p>. If the page is re-rendered and it is still zero, then it sends nothing. If the counter changes to 1, it sends <p>counter: 1</p> the first time around. Any further increment sends only the counter. But this is a simplistic example, it applies to many more cases, as outlined in the article. :slight_smile: Most other server side frameworks will send large amount of contents, both “statics” and “dynamics”, on every update.

6 Likes