← Back to context

Comment by lbreakjai

23 days ago

It feels like the worst of both worlds, what am I missing?

I get server-side rendering. I can boot my server, and everything is there. If my model changes, I can update the view. It's cohesive.

I get client-side rendering. The backend returns data, the frontend decides what to do with it. It's a clear separation. The data is just data, my mobile app can consume the same "user" endpoint.

This seems like a worst-of-both-worlds paradigm, where the backend needs to be intimately aware of the frontend context. Am I not getting it or is there a massive implicit coupling?

Now if I need to display the same "user" data twice, in different formats, on my website. Say, as a table in "My account", and as a dropdown in the menu bar. Do I need to write two endpoints in the backend, returning the same data in two different formats?

Yep. And that’s a good thing:

https://htmx.org/essays/splitting-your-apis/

  • Splitting application API and generic data API is orthogonal to HTMX. You still have issues compared to plain JSON, don't you?

    Imagine you need firstName/email in once place, firstName/email in another, and firstName/D.O.B in another.

    In a plain JSON world, I'd craft a single "user" endpoint, returning those three datapoints, and I would let the frontend handle it. My understanding is with HTMX, I'd have to craft (and maintain/test) three different endpoints, one per component.

    I feel like you would quickly end up in a combinatorial explosion for anything but the simplest page. I really don't get the appeal at all. Of course everything can be very simple and lightweight if you hide the complexity under the bed!

    • > let the frontend handle it

      via 3 different rendering logic, (such as JSX templates) same as the server

      > if you hide the complexity under the bed!

      which is what you just did by dismissing the reality that client-side requires the same 3 renderers that server-side requires! (plus serialization and deserialization logic - not a big deal with your simple example but can be a major bottleneck with complex, nested, native objects)

      2 replies →

    • ¯\_(ツ)_/¯

      any factoring you do on the front end you can do on the back end too, there's nothing magic about it and you don't need different end points: that can be a query parameter or whatever (if it's even a request, in most hypermedia-based apps you'd just render what you need when you need it inline with a larger request)

      it's a different way of organizing things, but there are plenty of tools for organizing hypermedia on the server well, you just need to learn and use them

    • > n a plain JSON world, I'd craft a single "user" endpoint, returning those three datapoints, and I would let the frontend handle it.

      The main problem is that this is extremely, extremely expensive in practice. You end up in Big Webapp hell where you're returning 4mb of data to display a 10 byte string on the top right of the screen with the user's name. And then you need to do this for ALL objects.

      What happens if a very simple page needs tiny bits of data from numerous objects? It's slow as all hell, and now your page takes 10 seconds to load on mobile. If you just rendered it server-side, all the data is in reach and you just... use what you need.

      And that's not even taking into account the complexity. Everything becomes much more complex because the backend returns everything. You need X + 1, but you have to deal with X + 1,000.

      And then simple optimization techniques just fall flat on their face, too. What if we want to do a batch update? Tsk tsk, that's not RESTful. No, instead send out 100 requests.

      What about long running tasks? Maybe report generation? Tsk tsk, that's not RESTful. No, generate the report on the frontend using a bajillion different objects from god knows where across the backend. Does it even make sense with the state and constraints of the data? Probably not, that's done at a DB level and you're just throwing all that away to return JSON.

      I mean, consider such a simple problem. I have a User object, the user has a key which identifies their orders, and each order has a key which identifies the products in that order. Backend-driven? Just throw that in HTML, boom, 100 lines of code.

      RESTful design? First query for the User object. Then, extract their orders. Then, query for the order object. For each of those, query for their products. Now, reconstruct the relationship on the frontend, being careful to match the semantics of the data. If you don't, then your frontend is lying and you will try to persist something you can't, or display things in a way they aren't stored.

      The backend went from one query to multiple endpoints, multiple queries, and 10x the amount of code. The frontend ballooned, too, and we're now essentially doing poor man's SQL in JS. But does the frontend team gets the bliss of not dealing with the backend? No, actually - because they need to check the database and backend code to make sure their semantics match the real application semantics.

      2 replies →

You need to get out of the mentality that there have be two states.

Ultimately the frontend state cannot exist without the backend, where data is persisted. Most apps don't need the frontend state, all it really gives you is maybe a better UX? But in most cases the tradeoff in complexity isn't worth it.

  • This isn't about state, is it? In a classic react app, if I need to display the name of a user, then I fetch it (from the server) once, and display it as many times as I need, in as many forms as I need. There's only server state.

    I don't see how it's any simpler to shift partial presentation duties to the backend. Consider this example:

    https://htmx.org/examples/active-search/

    The backend is supposed to respond with the rows to fill a table. You have an extremely tight coupling between the two. Something as simple as changing the order of the columns would require three releases:

    - A new version of the backend endpoint - A frontend change to consume the new endpoint - A version to delete the old endpoint

    I'm not trying to be obtuse, but I fail to see how this makes any sense.

    Consider something as simple as an action updating content in multiple places. It happens all the time: changing your name and also updating the "Hello $name" in the header, cancelling an order that updates the order list but also the order count ...

    There's _four_ ways to do it in HTMX. Each "more sophisticated" than the previous one. Which is one really wants, sophistication, isn't it?

    https://htmx.org/examples/update-other-content/

    I really struggle to decide which example is worse. Not only the backend needs to be aware of the immediate frontend context, it also needs to be aware of the entire markup.

    In example two, a seemingly innocuous renaming of the id of the table would break the feature, because the backend uses it (!) to update the view "out of band".

    I'm really trying to be charitable here, but I really wonder what niche this is useful for. It doesn't seem good for anything complex, and if you can only use it for simple things, what value does it bring over plain javascript?

    • > This isn't about state, is it? In a classic react app, if I need to display the name of a user, then I fetch it (from the server) once, and display it as many times as I need, in as many forms as I need. There's only server state.

      No, there's two states here: the frontend state, and the backend state.

      The name example is trivial, but in real applications, your state is complex and diverges. You need to constantly sync it, constantly validate it, and you need to do that forever. Most of your bugs will be here. The frontend will say the name is name, but actually it's new_name. How do you do fix that? Query the backend every so often? Maybe have the backend send push updates over a websocket? These are hard problems.

      Ultimately, there is only one "true" state, the backend state, but this isn't the state your users see. You can see this in every web app today, even multi-billion dollars ones. I put in some search criteria, maybe a check a few boxes. Refresh? All gone. 90% of the time I'm not even on the same page. Push the back button? Your guess is as good as mine where I go. The backend thinks I'm one place, but the frontend clearly disagrees.

      SSR was so simple because it FORCES the sync points. It makes them explicit and unavoidable. With a SPA, YOU have to make the sync points. And keep them in sync and perfect forever. Otherwise your app will just be buggy, and they usually are.

      3 replies →

    • > Something as simple as changing the order of the columns would require three releases

      Just change that example to just return the entire table element on /search. You could even add/remove columns with a single route response change, vs the multiple changes required to sync the front/backend with a JS framework.

Not a problem with Jinja (or any server-side templating). Both the table and dropdown render from the same context variable in one template pass. One endpoint, one data fetch, two presentation formats.

The "two endpoints" concern assumes you're fetching fragments independently. If you're composing a full page server-side, the data is already there

  • So now you have presentation logic, tightly coupled, spread over two places. You need to jump between two codebases to even have a clue about what is rendering on the page.

    There's an example on their website where the header of a table is defined in the frontend, and the body is returned by the backend. If I wanted something as simple as switching the order of the columns, I'd actually need to create a new version of my backend endpoint, release it, change the frontend to use the new endpoint, then delete the old one. That sounds crazy to me.

    • So what if you have presentation logic in two places, if it isn't necessary, remove the second instance?

      There are so many gains by not using a frontend. You've greatly reduced your site size, removed duplicated logic, a shitload of JS dependencies, and an unnecessary build step.

    • I use nested templates the same way React uses nested components. The key is cascading the context (like props) down the hierarchy. Column order change? One edit in the table template; everything that includes it gets the update.

      The "header frontend / body backend" split is a choice, not a requirement. I wouldn't make that choice.

      2 replies →