A version of this first appeared in the Viget blog.
I came across Hotwire at the same time I was looking back at 10 years of being a front-end developer—because round numbers make us think back, and I guess I didn’t have much going on in December of 2020. Anyway, I am just old enough to have known a world before single page applications (SPAs). I can remember how awesome it was the first time I decided to make a CMS “headless” and built the front-end entirely in Angular. I loved how the browser didn’t need to reload for every page, adding interactivity was a breeze, and I was free from Drupal templates—I could use ✨components✨ now.
I swapped React for Angular and Craft for Drupal in my work, and went on to build many SPAs. I have mixed feelings.
On the one hand, working with frameworks like Next.js and Gatsby feels great. The tooling for developers is phenomenal, and the end user gets a great experience. On the other hand, I feel like I am maintaining two apps (because that’s literally what I am doing), and I am offloading the entire application on the client. In the end, I don’t know that this makes us any faster at building apps or websites, and I’d like a better way to give the end user a great experience without having to incur the cost of running an entire app’s business logic in their browser.
Enter Hotwire, an alternative to SPA architecture. It works by using HTML over the wire instead of JSON and allows us to build sites that function like SPAs but aren’t. Pairing Craft with Hotwire allows us to harness all the capabilities of the CMS and provide better experiences to users.
Jump to heading Craft now
Taking a step back, let’s look at how Craft sites are built today.
One way is to use Craft in its monolithic form. That is, Craft is your CMS and it also renders templates. This, I think, is the way Craft prefers to work. It’s certainly the way that has been thought about the most, and it’s the default. This way of working is great, you just add your CSS and sprinkle interactivity with JavaScript (if you are curious this is how we tend to sprinkle our interactivity).
Another way is to use Craft as an API for some SPA (or anything else) to consume on the front-end. This can be done with the Element API or more likely with the built in GraphQL API. The front-end is then entirely up to you, the most common solution is to use a framework like Next.js, Nuxt, or Gatsby to render the front-end.
Two valid approaches, both with downsides. The monolithic approach ties you to twig templates and any complex UI will be trickier to achieve. Meanwhile, with Craft as an API you can do whatever you want™, this means you have to build everything yourself, from routing to state synchronization.
It would be great if we could make use of all the things Craft does for us (routing, managing state, templating, etc) and give users an SPA-like experience (no page reloads, in-place searches, autocomplete, etc).
Friends, that’s the promise of Hotwire with Craft.
Jump to heading Hotwire’s parts
To understand Hotwire I recommend going to their site and watching the intro video before diving into the guides. Hotwire is made up of two frameworks (Turbo, and Stimulus), at a high level this is how I see each fitting into Craft sites:
Jump to heading Turbo
Adding Turbo to Craft immediately gives you an SPA-like experience when navigating between pages. That alone is a huge win, but we can go further. If you are into Craft you’ve probably heard about Sprig’s reactive twig components. We can use Turbo to build reactive twig components with Turbo Frames. A toy example looks like this:
{% set query = craft.request.getParam('query') ?? '' %}
{# This form submits and sets the query #}
<turbo-frame id="results">
<form>
<input class="border" type="text" name="query" value="">
<input type="submit" value="Search">
</form>
<div id="results">
{# Outputs the result if query is not empty #}
{% if query %}
{{ query }}
{% endif %}
</div>
</turbo-frame>
Going in depth into all you could do with Turbo Frames is out of the scope of this post but this simple example is something you could quickly test out. After the form inside <turbo-frame>
is submitted Turbo will only replace the contents on the frame. The first time the frame is rendered query
is empty and no results are shown. After the form is submitted the component will re-render with query
populated and the results will be displayed. VoilĂ a reactive twig component.
These components can be far more powerful. For example if you were to create a form with a POST action to a Craft module’s controller, you could re-render your component’s template with new data and have the Turbo Frame display that.
Jump to heading Stimulus
Stimulus is a framework for adding additional interactivity. The key difference between stimulus and vanilla JavaScript is the use of the Mutation Observer API. Stimulus watches for changes in the DOM and attaches controllers as needed. I wrote a deep dive that compares our vanilla JavaScript approach to Stimulus controllers.
Because Stimulus is always watching for DOM changes it makes it the perfect pairing for Turbo. Stimulus doesn’t care how the DOM is changed, it will attach any new controllers and update values in existing ones.
Turbo and Stimulus patterns tend towards minimal JavaScript. That is a radical departure from SPA’s where everything is JavaScript. Ultimately this plays to the strengths of the CMSs architecture and guides you to not duplicate logic on the client. The best example of the upside is: not having to validate forms on the client side, just display the errors server side and replace the form in place. If you’ve ever felt that validating a form in both the server and the client seemed like an unnecessary duplication you’ll be happy to not do that anymore.
Jump to heading But, don’t stop at Craft
What I am most excited about for the future of my work isn’t switching my approach to building Craft sites—although, that is exciting—but what is described in The Future of Web Software Is HTML-over-WebSockets feels life altering. Whether it’s building applications using Phoenix LiveView or using Rails, this old (but new again) idea of HTML over the wire feels like The Future™.
The promises of SPAs and CMSs as APIs has never been fully realized for me, and this new approach feels like the path we didn’t take ten years ago in front-end development when we saw a fork in the road. I am planning to try this approach out with Craft and other projects this year and will report back on what I find. All of my early explorations have convinced me that “this is the way”.