Deferred Javascript in Rails

Article summary

Loading javascript at the top or middle of a page can prevent it from loading smoothly. This can make the page appear to freeze halfway through the rendering. You can significantly improve the perceived performance of your application by moving the javascript to the bottom of the page, below all the content it would have blocked. In a complicated web application, this can be a lot of work, but a simple trick can make it a lot easier.

Why does javascript interrupt the rendering process? While a browser is fetching a referenced javascript file, it stops rendering content until it has dealt with the javascript. The browser doesn’t know whether the script will alter the DOM, and if it does, that could change what other elements the browser has to render. The user sees the page appear to stop loading, and the damage is done.

The solution is a simple concept—move the javascript includes to the very bottom of the page, just inside the closing body tag. As a result, the page will load smoothly before it runs the javascript. That looks something like this:

1
2
3
    <%= javascript_include_tag :all, :cache => true %>
  </body>
</html>

Many application developers are using a javascript framework like Prototype or JQuery to make our lives easier, and while it would be really easy to put the above code in our layout files, that means the frameworks aren’t available in our templates. That’s clearly not good enough, but what if we could defer our javascript? Some browsers support a “deferred” attribute on javascript tags, which means they can feel free to load the scripts later, at a convenient time. This is non-standard and Firefox does not support it, but we can fake it in our Rails applications quite simply. Here is how:

Step 1: Write a helper method to store javascript for later.

1
2
3
4
5
6
module ApplicationHelper
  def deferred(&block)
    @deferred_content ||= ""
    @deferred_content << capture(&block)
  end
end

Step 2: At the end of the request, put that javascript in the bottom of the layout, right after including our external files.

1
2
3
4
    <%= javascript_include_tag :all, :cache => true %>
    <%= @deferred_content %>
  </body>
</html>

Step 3: Defer the javascript in your views.

1
2
3
<% deferred do %>
  <%= javascript_tag "..." %>
<% end %>

Now all your javascript runs at the end of the page load, and the application feels just a little bit snappier. For the most part, you get to write your javascript exactly how you’d like, but it will be held back to the end.

Caveats

You will most likely need to defer all or most of your javascript, which can be a lot of work if it’s done late in a project. Start early in the project if you can.

A user can potentially click a link or other interactive element before the javascript is ready to handle it. This can be a doozy, especially if you’re integrating with a slow-loading advertising partner or something similar. One solution would be to make your site work even with javascript turned off, so if they click early it’s not broken. Unobtrusive javascript is a valuable tool here. Another option is to disable interactive elements until the page finishes loading.

You cannot use document.write unless you’re fine with it writing out to the bottom of the page. If you integrate with other services, particularly advertising services, they may use document.write and you may not be able to defer those scripts. That’s alright. Those services should not be counting on your frameworks anyway, so you won’t get too much pain there. It may take back a little of your performance increase, but it should be a net positive.

As with all performance tips, your mileage may vary, and you should try it for yourself to see how well it works.

Conversation
  • Tim Segraves says:

    Brilliant! I was looking to do exactly this and it worked perfectly. Thanks.

  • Ryan Fogle says:

    Glad to have helped!

  • I think this was introduced after this post was created here is how it could be implemented with ease

    and

    where I need to add js at the end of the page

    • missing code sections
      1) yield :body_end –> add this in the application.html file
      2) content_for :body_end use in any other file

  • Comments are closed.