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 —
A simple lazy loader for our Ruby on Rails app, built with Hotwire, Turbo Frames and TailwindCSS.
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 —
Terminal
# generate a new Rails app, and include TailwindCSSrails new hotwire_turbo_lazy_loading_2 --css=tailwind &&cd hotwire_turbo_lazy_loading
# Scaffold a new Post modelrails g scaffold Post title:string
# Migrate the databaserails db:migrate
# Start our Rails appbin/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::lazydo%> 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.
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 —
posts/index.html.erb
<divclass="w-full"><pclass="text-xl font-bold">Lazy loading with Hotwire and Turbo Frames</p><%= turbo_frame_tag :posts_lazy,src: posts_index_lazy_path,loading::lazydo%><%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 —
PostsController.rb
defindex_lazy@posts= Post.all
end
Then we need to map our new Posts#index_lazy controller action to a route —
routes.rb
Rails.application.routes.draw do get 'posts/index_lazy',to:'posts#index_lazy' resources :postsend
And finally, we need to create a view for our new Posts#index_lazy controller action —
posts/index_lazy.html.erb
# this will be inserted into the turbo_frame_tag in posts/index.html.erb
<%= turbo_frame_tag :posts_lazydo%> 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.
Lazily loading 100,000 fake Posts in our Rails app with a Turbo Frame lazy loader.
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 and upsert_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.
A basic skeleton loader. They're usually just some flashing grey boxes, to remind your users that your app hasn't broken — it's just loading.... slowly.
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 —
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 —
Our finished lazy loader for our Rails app, combining our Turbo Frame with a skeleton loader.
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.