- Published on
- ← Blog
Simple Lazy Loading in Rails with Hotwire and Turbo Frames
- Authors
- Name
- Harrison Broadbent
- @hrrsnbbnt
Hotwire and Turbo Frames make it easy to add lazy-loading into our Rails apps — I'm talking only 12 lines of code! Plus, we can sprinkle in some TailwindCSS to create a basic skeleton loader for our Turbo Frames.
This is what we're going to build —
Table of Contents
So — you want to add lazy loading to your Rails app.
In this article I'm going to walk you through exactly how to do that, and I think you'll be surprised by how easy it is (we can do it in 12 lines of code!).
Hotwire and Turbo Frames make lazy loading super simple — with a couple of Turbo Frames and a few small adjustments, your Ruby on Rails app will be able to lazy-load content from anywhere.
Let's get started!
Scaffolding a sample Rails app
I'll be adding lazy-loading to a sample Rails app, which we generate in this section.
These commands will generate everything we need, and then in the next section, we'll add lazy-loading with some turbo_frame_tag
elements.
To generate the sample Rails app, run these commands in your terminal —
# generate a new Rails app, and include TailwindCSS
rails new hotwire_turbo_lazy_loading_2 --css=tailwind && cd hotwire_turbo_lazy_loading
# Scaffold a new Post model
rails g scaffold Post title:string
# Migrate the database
rails db:migrate
# Start our Rails app
bin/dev
If everything went well, you should be able to visit https://localhost:3000/posts, and see the Posts#index
page.
Now we can move on to the good stuff — adding lazy loading to our Rails app, with the help of some Turbo Frames and Hotwire!
Lazy loading with Hotwire and Turbo Frames
Rails makes lazy-loading easy with its turbo_frame_tag
element.
If you're not familiar with a turbo_frame_tag
, here's a simple example —
<%= turbo_frame_tag :frame_name, src: src_path, loading: :lazy do %>
Temporary content while we're loading...
<% end %>
What's going on here?
It's really simple — this Turbo Frame will load the contents of a different Turbo Frame, and overwrite its current content with the new content.
The new content comes from a Turbo Frame with a matching :frame_name
, located at the src_path
.
By setting loading: :lazy
, we can make our Turbo Frame do this loading lazily (duh). While we're waiting, the contents inside the current turbo_frame_tag
will be displayed — this makes it easy to add a skeleton-loader to our Turbo Frame.
Essentially, our Turbo Frame will transplant data from a different Turbo Frame (which is probably in another Rails view), into the current Turbo Frame.
You can read more about Turbo Frames in the official Hotwire handbook.
To add lazy-loading into our Rails app, we just need to do two things —
- First, we need to add a Turbo Frame to a view and set it up correctly (the most likely views we want to lazy-load in are
#index
views, since getting all the records from the database can be slow, or views which render data from external APIs). - Then, we need to create another view, controller action and route to provide the data for our Turbo Frame to render.
If this sounds a bit complicated, don't worry — just read the code below! We add the entire lazy-loading view for our Posts#index
method in 12 lines of code.
To create a basic lazy loader, adjust your posts/index.html.erb
to include a turbo_frame_tag
—
<div class="w-full">
<p class="text-xl font-bold">Lazy loading with Hotwire and Turbo Frames</p>
<%= turbo_frame_tag :posts_lazy, src: posts_index_lazy_path, loading: :lazy do %>
<% end %>
</div>
This turbo_frame_tag
is going to lazily call the posts_index_lazy_path
route, and try to find another turbo_frame_tag
called :posts_lazy
.
This should make our next steps obvious — we need to create the posts_index_lazy_path
, and add a Turbo Frame at that path called :posts_lazy
, which renders the content for our Posts#index
view.
To do that, in your PostsController
, you need to add a new controller action for our view —
def index_lazy
@posts = Post.all
end
Then we need to map our new Posts#index_lazy
controller action to a route —
Rails.application.routes.draw do
get 'posts/index_lazy', to: 'posts#index_lazy'
resources :posts
end
And finally, we need to create a view for our new Posts#index_lazy
controller action —
# this will be inserted into the turbo_frame_tag in posts/index.html.erb
<%= turbo_frame_tag :posts_lazy do %>
Count: <%= @posts.count %>
<%= render @posts %>
<% end %>
That's it for our simple lazy loader! It's just a couple of Turbo Frames, alongside a new Posts#index_lazy
route to load our Posts
.
If we seed our database with some fake data, we can see our lazy-loader in action —
Note 1: I seeded my database with 100,000 test
Posts
to help test out our lazy loader. You can check out my article on seeding with the faker gem andupsert_all
if you need some help seeding your database.Note 2: I adjusted the
_posts
partial to be shorter, which is why the GIF from above might look a bit different to yours.
So we've got our basic lazy loading Turbo Frame working — nice!
Right now though, we just have a blank screen while we wait for our content to load. That's some pretty terrible UX!
In the next section, we fix this by combining our Turbo Frame with some TailwindCSS to build a simple skeleton loader.
Building a skeleton loader with TailwindCSS
Before we start — what is a skeleton loader?
It's just a UI placeholder for your content. A skeleton loader gives your user some visual feedback while they wait for your content to lazily load in the background.
Skeleton loaders are great for user experience! One (small) study found that using skeleton loaders will leave your users happier than blank screens or spinners, and make your users perceive your loading times as shorter!
Let's make one ourselves — we can combine our Turbo Frame with some basic HTML and TailwindCSS to make a simple skeleton loader for our Posts
.
Here's a basic skeleton loader I whipped up by combining our Turbo Frame with some TailwindCSS —
<div class="w-full">
<p class="text-xl font-bold">Lazy loading with Hotwire and Turbo Frames</p>
<%= turbo_frame_tag :posts_lazy, src: posts_index_lazy_path, loading: :lazy do %>
<div class="animate-pulse w-1/12 mt-6">
<div class="p-2 w-full bg-gray-500 mb-2 rounded-md"></div>
<div class="p-2 w-full bg-gray-500 mb-2 rounded-md"></div>
<div class="p-2 w-full bg-gray-500 mb-2 rounded-md"></div>
</div>
<% end %>
</div>
The main thing happening here is inside our turbo_frame_tag
. The content inside the turbo_frame_tag
will be rendered initially, and then replaced with the lazily-loaded Posts
once they've all loaded.
Reload the page, and we can see our skeleton loader in action —
The animate-pulse
class is very useful for showing a pulsing skeleton loader. You can also move your skeleton loader into its own partial, to make it easier to manage.
Conclusion
I hope you found this article useful!
Adding lazy loading to a Ruby on Rails is surprisingly easy — Hotwire and Turbo Frames make the whole process pretty clean and flexible.
I also encourage you to check out this excellent guide on lazy loading from the BoringRails blog, which helped me write this post.
I've also written another article about the turbo_frame_tag, Hotwire and lazy-loading which you might find interesting!
Handy Resources:
Join 1050+ Ruby on Rails lovers
Join 1050+ Ruby on Rails lovers and get our weekly newsletter, no spam. Just interesting Rails articles, from here and around the web.