Introduction to Hotwire and Turbo
Discover how to add front-end features in Rails using Hotwire and Turbo without heavy JavaScript. Learn to handle dynamic page updates with server-rendered HTML, reduce client-side logic, and improve app responsiveness while leveraging Rails conventions.
We'll cover the following...
In the last chapter, we set up Webpacker and TypeScript. In this chapter, we’re going to start creating front-end applications with Rails by doing something that may initially seem odd. That is, we’re going to add front-end features without writing JavaScript. In fact, we are not going to write much JavaScript at all. Instead, we’re going to use Hotwire and Turbo to build client-side interactivity into our page.
The Hotwire way
Hotwire is the generic name for the client-side toolkit written by the Basecamp team to power the Hey email application. The goal of Hotwire is to support an application in which most of its dynamic nature happens by making typical HTTP requests to the server, receiving HTML for a part of the page, and inserting that HTML in the correct place in the DOM for a page update.
The name “Hotwire” is derived from the phrase “HTML over the wire.”
The idea is that, by moving all the logic to the server, we can replace much of the complicated and specific client-side code with a small set of generic client-side actions that handle the retrieval and management of HTML from the server. We still have to write the server-side logic, but we are preventing some duplicate logic on the client and the server by making the server the source of truth. Turbo allows us to reuse view code that we have already written for greater interactivity. Writing the complex logic in Ruby and Rails will likely be easier than writing it in the JavaScript ecosystem.
The big picture:
-
The server communicates with the client by sending rendered HTML, not raw data. This HTML response may contain some metadata about where to put it when it arrives.
-
Any business logic the application might need should be on the server, not on the client.
-
Specific client logic should be limited only to interface items that the server won’t care about.
-
Where possible, client logic should be handled through the addition and removal of CSS classes. Doing so allows for a lot of client logic to be written generically.
The Hotwire team claims that about 80 percent of their client interaction is manageable via HTML over the wire, and at least half of the client-side code is just manipulating CSS. Applying this ratio to our application too will result in much less client-side complexity, and we’ll be able to leverage Rails features to keep the overall complexity low. Hotwire works particularly well if we use Rails conventions for partial-view files and especially if we use the ActiveRecord naming convention for partial files.
Installing Turbo
Hotwire currently consists of two parts: Turbo, which manages HTML requests to the server and directs the responses correctly, and Stimulus, which is a minimal JavaScript library well suited to the interactions Turbo can’t handle.
Turbo is the successor to Turbolinks. Its purpose is to make it easier to direct user actions into server requests. These requests then return partial HTML, which Turbo then inserts into the page or part of the page.
Hotwire and the asset pipeline
The Hotwire team put a bit of effort into making Hotwire work with or without a JavaScript build tool. We use Webpacker in this course to support TypeScript and React, but if you want to use Hotwire with the older Rails asset pipeline, you can still do so using the
hotwire-railsgem. If Webpacker is not installed, the gem installs Turbo and Stimulus using the asset pipeline, including the Stimulus auto-load behavior we’ll talk about later. There’s also a gem calledtailwindcss-railsthat will do a default Tailwind installation in the asset pipeline or Webpacker. For a simple app, this is a low-overhead way to go.
To install Hotwire, we use a gem called hotwire-rails, which is aware of whether we are using Webpacker or not and adjusts its installation accordingly.
- In the
Gemfile, replace theturbolinksgem withhotwire-rails. - Run
bundle install. - Run
rails hotwire:install. This will use npm and Webpacker to install Hotwire (both Stimulus and Turbo).
Specifically, the hotwire:install command does the following:
-
Adds
@hotwired/turbo-railsandstimulusto ourpackage.jsonand removesturbolinks. -
Runs
yarnto install the new package. -
Makes a configuration change to ActionCable that we’ll talk about in Immediate Communication with ActionCable.
-
Changes our
application.jsfile to remove Turbolinks, add theturbo-railsgem, and import thecontrollersdirectory that Stimulus uses. The following is an edited version; the installer puts theimport controllersat the end of the file:
We’ll talk more about this file in the Stimulus lesson when we start writing JavaScript.
Before, though, let’s briefly cover some background info about Turbo Drive.
What is Turbo Drive?
Turbo Drive is the new name for Turbolinks. When Turbo Drive is active, it captures every clicked link or form submission on a page, receives the response in the background, extracts the body of the request, and replaces the existing page body with the new response. It also does some behind-the-scenes bookkeeping to keep the Back button working, manage caches, and so on.
Turbo Drive expedites page navigation because large assets in the header of the page aren’t reloaded. Navigation is also perceived as faster by the user because the page doesn’t flicker or go blank. If we look at the previous application in Introduction to the Course and try the “Make Favorite” feature, you’ll probably find it more responsive because Turbo Drive now redraws the page without much flicker.
However, there is a downside to Turbo Drive, which is why many Rails applications have chosen not to use Turbolinks. Because the browser does not see a Turbo Drive change as a full page load, it does not send page-load related events, so Turbo Drive can interfere with other JavaScript libraries or code that depend on the page-loaded or DOM-loaded events. Turbo does have its own page-load event that we can watch for called turbo:load, but it can be tricky to integrate with existing JavaScript libraries that depend on document load.
Turbo Drive is useful, but it’s not nearly enough to build a robust application on its own. For that, we need to turn to Turbo Frames and Turbo Streams.