For the last few months, I've been digging into the Hotwire framework. I was initially drawn to Hotwire on its promise of allowing me to build a SPA (Single-Page Application)-like experience using an MPA (Multi-Page Application); and, to do so with less effort. After several months of creating demos and migrating this ColdFusion blog over to using Hotwire, I feel like I have a much better sense of how Turbo Drive, Turbo Streams, and Stimulus work. But, I'm not quite sure that I want to use Hotwire when I start my next side-project.
CAUTION: This blog post is very much me talking to myself, trying to figure some things out.
I'm Happy That I Updated My Blog to Use Hotwire
I used this blog as a laboratory for learning about Hotwire. Prior to this, I had no framework at all. Sure, I had jQuery, which I eventually replaced with Umbrella JS; but, there was nothing really tying everything together. As such, moving to Hotwire somewhat forced me to organize my code better.
Plus, I do love the fact that Hotwire creates a persistent process, which reduces the amount of work each subsequent navigation event has to incur. Of course, this is a "blog" where the number of pages viewed is "1" in the vast majority of cases; so, the relevance of a "subsequent navigation event" is a matter of much debate.
In the end, if I were to build another public facing "content" site, I do believe that I would choose Hotwire. I also believe that I might choose it for any "simple" back-end administrative system.
Hotwire Doesn't Address Some Important Problems
I understand that a lot of the uphill battle with Hotwire - with anything new - is lack of experience. I've been building-up a completely new mental model, and it's not always obvious how things are supposed to be done. But, after a few months of experience, I'm beginning to see that Hotwire doesn't really address a certain set of problems. Here are some things that still seem fuzzy and unanswered in my mind:
Scoping of Names: Hotwire uses the DOM (Document Object Model) as the source of truth; and, maps DOM attributes to Stimulus Controllers. As such, every controller has to be uniquely named. As an application grows and evolves, I suspect that Controller names will have to get longer and more complex in order to remain unique. And, since controller names are included in every "action", "param", and "target" target as well, the DOM is going to get wordy!
ASIDE: I've see this with other Dependency-Injection (DI) frameworks as well. In fact, in my AngularJS 1.x apps, I've started to prefix my Component tag-names with
xis a globally-unique counter. Example
<m97-email-recipients>. This way, I can avoid naming collisions without having to create artificially complex names.
Scoping of CSS: Most of the Hotwire examples on the web seem to use Tailwind CSS. And, I suspect that some of the impetus behind this is a lack of any native CSS scoping. Since there's no inherent compilation step in a Hotwire application, there's no hook that allows Hotwire to inject template modifications that enable scoping.
Encapsulated Interfaces: While reusing templates for rendering is a core part of the Hotwire way, this doesn't directly address the need to encapsulate complex interfaces. Ruby on Rails has some helpers for this; but, if you're not using Rails, you're on your own.
Layered Routing: If your application is "flat," in that a URL route only ever maps to a single page, Hotwire "just works". But, if you want to start using "auxiliary routes" to represent page state - such as making a modal window deep-linkable from anywhere in the application - there's no a great native way to do this. You can use Turbo Frames with an
advanceaction to change the URL; but, this doesn't really address the page-refresh issue; or, the need to simply close the modal window and revert back to the previous URL.
Error Handling: Whether it be a
401 Unauthorizederror or a
500 Server Error, handling failures in Hotwire is non-obvious. You can hook into events and override rendering; however, since Hotwire is doing all the fetching for you, there's no clear place to put this kind of logic. And, since Turbo Frames and top-level requests seem to fail in different ways, handling errors is all the more complex.
Now, again, I have to stress that I only have a few months experience with Hotwire; so, take this all with some healthy apprehension. Plus, the Basecamp team has built Basecamp and HEY, both of which are highly dynamic, interactive applications. So, if you really know what you're doing, it's clear that Hotwire gives you the tools to get it done.
Is Hotwire Really Less Work?
Part of the Hotwire messaging is that building an application is going to be less work. But, I'm not sure it actually works-out that way. I believe that, more than anything, aspects of the control-flow just live in different places. But, I don't believe that there's actually less stuff to build.
Consider a "Contact" form. With a Contact form I need:
- A controller action to render the form.
- The form HTML itself.
- A controller action to receive the submission.
- The business logic to process the submission.
- A controller action to render the Thank you.
- The Thank you HTML itself.
With Hotwire, that's all done server-side. Without Hotwire, some of that is server-side and some of that is client-side. Furthermore, the server-side parts are likely spread out across page-controllers and API-controllers. But, there's nothing here that I would remove when using Hotwire; nor is there anything that I would add when using a single-page application. It's the same stuff, it's just in different places.
Of course, if you're using Hotwire because you get to use the server-side technology more often, then it's not the volume of "stuff" that matters, it's the implementation of the stuff that makes the difference for you.
Hotwire's "Back Button" is Fundamentally Better
One thing that Hotwire clearly gets right is the Back Button. When you hit the back button in a Hotwire application, Turbo Drive just pulls the page out of the cache and renders it instantly. This is something that no Single-Page Application can really do quite as well (or so it seems).
Hotwire Leads to More Reusable Widgets
If I were to choose Angular for my next side-project, then all the widgets I built would end up working in an Angular-only context. Which means, if I had something special - like a "Fancy Select Box" - I couldn't use it on any View outside of the Angular app.
With Hotwire, on the other hand, since everything is based on server-side partial reuse, any widget can be used anywhere (as long as you're loading Hotwire). Well, I mean, sort of - it depends on whether that widget was built to work via progressive enhancement.
It's All About Trade-Offs
There's no clear-cut winner here. The reason I'm even writing this post is because I'm conflicted; and, the writing helps me think-through the problem. I think there are some things that Hotwire does really well; and, I think there are some points of friction. The question then becomes, are the benefits worth the drawbacks?
Much to consider!