Hotwire Discussion

Hotwire + TailwindCSS Performance

Our team rebuilt an application using Hotwire + TailwindCSS.

  • 30+ stimulus controllers
  • lots of UI
  • fairly small front-end payload (CSS - 12kb compressed, JS - 60kb compressed)

Navigating through pages feels very fast on desktop and mobile Very happy with Google Chrome > Lighthouse benchmarks.


What were the main obstacles?

I’m thinking of doing the same.

1 Like

I’m also considering tailwind and set up a new rails app with tailwind installed to experiment with.

I think the main obstacle is a learning curve. I moved from Zurb Foundation a year or so ago and went with a plain vanilla W3-css. It took a few days to match what I knew to how tailwind works. Tailwind is quite powerful and I’ve just been trying converting basic stuff. Things like w3 grid to tailwind grid or flex.

I may just try using both and convert a model/view at a time. Probably after setting up the main theme in layouts. I think after you do a few, it won’t be that difficult.

1 Like

@Diogomartf — is your question about the main challenges for getting TailwindCSS to work, or switching our JS library?

1 Like

My question is about hotwire, I already use Tailwind (and love it).

What were the main challenges of upgrading your rails app to use hotwire?

Is there anything that I should pay more attention to?

1 Like

Perhaps a longer answer than you wanted, but here’s my take


Perhaps the main challenge was not reusing the majority of the previous Javascript. Most Javascript libraries manage state in memory (jQuery, React, Angular, VueJS, etc). Trying to make these libraries work with Turbo often causes unexpected behavior, which often in the end won’t work. I personally attempted to use the older Turbo library (turbolinks) with in-memory JS frameworks and ran into many of the same challenges. With the Stimulus best practice of storing state in data attribute tags, it works very well with Turbo.

Some of our developers still struggled with state using turbo+stimulus at first, until they realized that they were saving state in memory instead of in html attributes. You want to consider doing this in both HTML and Javascript.

  1. HTML
<div data-controller="search" ... data-search-last-search-value=""></div>
  1. JS (set/update data-search-last-search-value)

Stimulus values:

this.lastSearchValue =; //=> updates data-search-last-search-value=""

or custom attributes

this.element.dataset.lastSearchValue = myVar; //=> updates data-last-search-value=""

We’ve had little problems or complaints since reaching this paradigm shift.


We also had to overcome a few of our own assumptions or “myths” before moving forward.


Even with stimulus targets, we thought we couldn’t live without convenience libraries like jQuery or Lodash. They “speed up development”. The challenge with these libraries is they tend to be large and include a lot of code, and we hoped to have small and fast javascript files.


Turns out you can now to the majority of what these convenience libraries do with the latest browser version. We googled “you don’t need (XYZ Library)” and found lots of resources showing how to accomplish the same thing using vanilla JS, and it does not always take as much code as you think. We no longer feel we need these convenience libraries.


Some libraries you do not want to rewrite. For example, we use the zxcvbn: Low-Budget Password Strength Estimation library to help our users create stronger passwords. However, this library is more than 400k compressed and would make our payload much larger. We wanted to dynamically load this library only on pages that use the stimulus controller which uses this library. We assumed we would have to add a custom javascript tag on the page to load it. Our previous JS stack helped us do this without even thinking about it.


We found the following article which shows us how to dynamically import the module.

It worked perfect, exactly the way we hoped.


Before starting, we felt concerned that using Hotwire instead of JS libraries with a larger community and more shared components would results in a slower development time. We did an exercise to rewrite the overall layout and one page using Hotwire with TailwindCSS.

We then measured the “effort” of prior vs hotwire stack using the “lines of code” to see how much more code we needed to write in order to write our own components using Hotwire and TailwindCSS vs the previous library (which included JS and CSS). (Note: I recognize lines of code as a horrible way to calculate effort, but we did not have any other way to measure).


To our, surprise the layout + one page (which required multiple custom components) contained almost the same number of lines in custom JS, CSS and HTML as our prior stack. It did not result in more (as we assumed). Even better, a good percentage of this custom code existed in stimulus components which we planned to re-use in future pages.


We performed the same exercise on a second page and again compared the amount of new code written. Because we reused the components we wrote for page 1, the amount of new code was significantly less.


For our application, Hotwire used approximately the same amount of effort to write pages with custom components as it was to use a prior stack with existing components. After the initial components, the amount of effort decreased dramatically as we re-used components. Note: one of the key strategies to properly writing an re-using components came from this article:


Thanks for your great feedback! These are very valuable insights.

Would you mind telling us a little more about your backend stack? If you were using a SPA framework on the frontend side, I assume you were mostly sending JSON from a backend API.

I understand moving to Turbo means using some form of template engine on the backend side. Did you have to do so?

I love many aspects of the Hotwire stack. You stated that on the frontend side it takes no more effort than a SPA, which is fine. But how much additional effort does it take on the backend side?

We use Ruby on Rails for our backend.

Correct, we previously returned JSON from a backend API. With the Hotwire app.

Ruby on Rails includes a templating engine, so yes we updated our code with a templating engine. Note: I included the amount code we needed to write in my effort comparison I shared in a previous comment.

Pretty much the same effort, just returning JSON instead of HTML. We still handle GET, POST, DELETE, etc. just as before.

We often wrote business logic twice (once in the backend and then again in the front-end). We found this difficult to avoid. This also meant unit tests for both front-end and backend business logic. With the Hotwire approach, we only wrote business logic on the backend (assuming you follow the practices suggested in Writing better Stimulus controllers). This also meant only writing business logic unit tests once for the backend.

Ok so when writing “Hotwire” you included backend code in your effort comparison. As the main topic was about frontend, I assumed you were focusing on frontend part only.

Thanks again, I was looking exactly for this kind of comparison! This is great news.

This is an awesome comparison, and an awesome outcome for your users! Thanks so much for sharing.


Thanks for the write up. Would you be open to sharing your site so we can check it out and see how it feels when using it?

Also, would you be open to coming on my podcast to chat about your tech stack, how it was built and deployed, etc… There’s almost 100 episodes. I’d love to get more Rails episodes on the show.

@nickjanetakis — I can share the site someone in the next few months. After that, I’d be open chat about it on your podcast if you are still interested. Thanks for the invite.

Sure no problem, there’s a “become a guest” button near the nav bar on the site. Feel free to fill that out to get the ball rolling and we’ll set a loose date for some time in a couple of months.