Why we chose Turbolinks instead of building an SPA

jerodsanto profile image Jerod Santo ・5 min read

Note: I originally wrote this in 2016, but I re-read it today and it's still super relevant. We're still using Turbolinks with no plans of switching. Enjoy!

Changelog.com is not a Rails app, but it is a Turbolinks app. Think about that for a moment.

That means we aren't using Turbolinks because it's Omakase. We aren't using Turbolinks because we forgot to delete it from our Gemfile. We actively chose Turbolinks, installed it, and integrated it in to our application. I think that makes us pretty unique.

The number one question we've received since open sourcing the site is, "why Turbolinks?"1

Here's why we chose it (and maybe you should too).

It comes down to one word: Courage Pragmatism

It's easy to get swept up by the New Hotness. I'm even more prone to this than most, since we talk to creators of the New Hotness all about their New Hotness on a weekly basis. But there's a big difference between a) keeping up with open source, and b) hitching your cart to every horse that struts by with shiny new shoes.

I find I'm most effective as a developer when I'm informed, yet cautious of trends. One trend I've long been skeptical of is the De Facto Single Page App. Don't get me wrong, SPAs are a legit (and often best) architecture for many apps2. However, when an application is built as an SPA when it doesn't need to be, bad things happen to good people.

In my experience, we're too often wooed in to asking ourselves "why shouldn't this application be an SPA?" instead of asking ourselves "why should this application be an SPA?" The application's needs should drive the architecture, not the developer's desires.

The tricky thing I was faced with when asking myself, "why should the new changelog.com be an SPA?" is that we have one (important) feature that just screams, "SPA!"

A persistent audio player that stays in the footer and continues to play as you navigate pages.

persistent audio player action shot

But that's it. There is no other feature or need that we have imagined that would necessitate an SPA. Did I really want to bifurcate the codebase, increase our JS payload, and < gasp>choose a front-end framework</gasp> all so we could have a persistent audio player?

The technologist inside of me absolutely did want to. The pragmatist knew there was a simpler way.

Things have changed in Turbolinks land

When Turbolinks first shipped with Rails 4.0 back in 2012, it caused a lot of headaches for developers around the world.

Some people thought it was a bad idea altogether, but that didn't resonate with me because GitHub was using pjax (Turbolinks' inspiration) with much success.

Other people gave it a chance until they ran in to edge cases, frustrating bugs, and a more complex mental model than advertised. I fell in to this camp.

In other words, Turbolinks didn't live up to its promise, so most folks wrote it off and moved on with their lives (some deriding any mention of its name). But the team behind the library continued to work at it, improving things along the way. It's four years later now, and Turbolinks has changed quite a bit. Here's a quick list of features and changes to get you up to speed:

  • It underwent a complete rewrite for Turbolinks 5 (see Turbolinks Classic)
  • It doesn't require server-side request detection or alternate rendering
  • It doesn't depend on jQuery or any other library
  • It includes a CSS-based loading progress bar
  • It can reload when assets change
  • It can persist elements across page loads
  • You can install it with npm/yarn and load it with webpack

Those last two bullet points provided exactly what we needed for our persistent player. If you haven't looked at Turbolinks since version 5 was released, I recommend you do.

Using it with Phoenix took less time than reading its README

I falsely presumed that using Turbolinks outside of Rails would be tough. Here's what it took:

  1. Add Turbolinks to a dependency in our package.json
  2. Import Turbolinks at the top of app.js
  3. Call Turbolinks.start(); at the bottom of app.js
  4. Add data-turbolinks-permanent to the player div

With that, I was done and it Just Worked. That being said, Turbolinks isn't completely gotcha free. There are a few things to keep in mind when writing your application's JavaScript. Make sure to read and understand the Building Your Turbolinks Application section of the README and you should be just fine.

As a wise senior developer once said: It Depends

I know, I know. Balanced reasoning about which tools and practices we should use doesn't make for popular writing, but it does make for good systems. I'm not here to hand wave and proclaim that you should ditch your current thing and use Turbolinks.

There are many reasons why our application is different than yours, some of which makes us a good fit for this solution. I'll list a few so you don't have to email us in anger:

  • Ultimately, we're a content-first site. Very few content sites require rich enough interactions to necessitate an SPA.
  • We don't have much JavaScript and very few (public facing) form submissions, so we haven't hit some of the more complex scenarios where Turbolinks may fall down.
  • As our needs grow, we may come to a point where we need client-side routing. I doubt that highly, but it's a possibility.

In the end, we accomplished our goal with minimal engineering effort and launched our site to much acclaim. People love our persistent player and I love Turbolinks for helping us pull it off with such aplomb. If you've written Turbolinks off, now might be a good time to give it a second chance.

  1. The #2 question is, "How is it so fast?"  Turbolinks helps with that, but it's mostly Elixir's fault.

  2. My first SPA was in 2010 when I helped Grooveshark (RIP) switch from Flash to a web app.


Editor guide
rhymes profile image

Nice article, thank you for the perspective! I've built two SPAs this year and I'm already tired ahhaha :P

The #2 question is, "How is it so fast?" Turbolinks helps with that, but it's mostly Elixir's fault.

Well, now you have to write an article about the backend :D

jerodsanto profile image
Jerod Santo Author

I'm happy to write more about it. Anything in particular that you're interested in hearing/reading?

thomasjunkos profile image
Thomas Junkツ

Oh, I would be interested in hearing, how the choice came upon elixir. What was before that? And how was the transition to a functional paradigm? Okay, Elixir is not Haskell 😂 But I assume there were the one or the other hoopla you went through?

Besides: wasn't the site rewritten from x (ruby?) to Go (some years ago)? At least I thought so.

rhymes profile image

How you made Changelog fast and your pros and cons of using Elixir in the day to day. I have zero experience in it so it's interesting to me to read professional opinions.

You seem to have a balanced view which helps ;-)

sinni800 profile image

Writing an Elixir Webapp right now and I would also love to see some more insights.

ben profile image
Ben Halpern

As we've chatted about before, dev.to is built similarly and it has been lovely.

Sometimes it would have been easier to go the SPA route, but having done that, I know the feelings are way worse in the other direction.

The thing is, it's now been a couple years since I've even tried writing an SPA, and while some things have improved I think in the ecosystem, a lot of the same pain persists.

changelog.com wonderfully accomplishes its goals.

powerc9000 profile image
Clay Murray

Late to the party here but I have tried moving to turbolinks and server side rendering on my project. It's just so great and a breath of fresh air to just make server side templates. Then Turbolinks keeping things speedy.

derekjhopper profile image
Derek Hopper

I've been looking into how Changelog has been handling form submissions, specifically validation errors. It looks like you have some custom handling with JavaScript and a couple of the Turbolinks APIs. How has that been working for you? Any notable concerns or pitfalls?

jerodsanto profile image
Jerod Santo Author

It all works pretty well! As you noticed, there are a few bits we have to twiddle to make Turbolinks follow redirects correctly and stuff like that (which is transparent in Rails because they tightly integrate the two), but nothing too crazy.

authanram profile image
Daniel Seuffer

Thanks for sharing your experiences. I appreciate that much. Pretty nice article. I've realized one more time while reading, no matter for how long you do development stuff, you constantly have to observe yourself and double check latest decisions, to not succumb to your own desire. Let's give it a shot...

missingno15 profile image
Kenneth Uy

As soon as I saw this title, I knew this was the Changelog

jerodsanto profile image
Jerod Santo Author

I don't know if I should interpret that as a good thing or a bad thing 😜

missingno15 profile image
Kenneth Uy

I've reread that same article on the Changelog site many times before - The Changelog, like dev.to, are examples of the practicality and effectiveness of Turbolinks when it comes to making a fast website and I think it's even more awesome that you've paired it with Phoenix. Also thank you for open-sourcing The Changelog source code - I use it as reference a lot when I build my own Phoenix apps

Thread Thread
jerodsanto profile image
Jerod Santo Author

Happy we could be of some help to you! Thanks for the kind words, Kenneth 🤗

bayuangora profile image
Bayu Angora

Is there any Turbolinks alternative that more lightweight with less than 10kb?

marcusatlocalhost profile image

Yes, htmx which relatively new and the successor of intercooler.js htmx.org/
Trimmings is very lightweight: github.com/postlight/trimmings

bayuangora profile image
Bayu Angora