
Not really the best-looking pagination if you are a perfectionist like me. I mean, it can do but why not improve it, right?! For the purposes of this article, I will use my personal site for reference, particularly, the POST
resource on how to go about implementing infinite scrolling.
Get started
To get started, you need to install Hotwire Turbo in your app. For Rails apps, you need the gem, this will be important as we’ll be usingturbo_streams
. Next, you will need to include the Pagy Countless subclass which is useful for saving one count query per request, this will be important later in the implementation.
require 'pagy/extras/countless'
PS: Remember to restart your server, I had errors until I restarted the server.

In your posts_controller.rb
, include Pagy.
class PostsController < ApplicationController
include Pagy:: Backend
end
In your Index method, set up Pagy’s Countless subclass.
class PostsController < ApplicationController
def index
@pagy, @posts = pagy_countless(Post.all.order('created_at DESC').where(is_published: true), items: 5)
respond_to do | format|
format.html
format.turbo_stream
end
end
end
respond_to
block is a helper method on the superclass PostsController
that takes |format|
as the argument to the block. This Rails helper method references the response that will be sent to the View (i.e. on the browser).
From here, in your views, particularly, the index view, declutter your view and use partials as below in the _posts.html.erb
:
<% if @posts.empty? %>
No results found for your search.
<% else %>
<% @posts.each do |post| %>
<% if post.image.attached? %>
<%= link_to post do %>
<%= image_tag post.image, :alt => "#{post.title} Image",
:class => "h-24 w-24 rounded-full object-center w-full object-cover object-center hvr-float" %>
<% end %>
<% else %>
<%= link_to post do %>
<%= image_tag "post_placeholder.jpg", :alt => "#{post.title} Placeholder",
:class => "h-24 w-24 rounded-full object-center w-full object-cover object-center hvr-float" %>
<% end %>
<% end %>
<%= link_to post.title, post, class: "text-thubz-primary hover:text-white hvr-float animate__animated animate__fadeInDown break-all" %>
<%= post.created_at.strftime("%d %B %Y") %>
<%= truncate(strip_tags(post.content.to_s), length: 300) %>
<%= link_to post, class: "inline-flex items-center text-thubz-primary hover:text-white hvr-float mt-4 animate__animated animate__fadeInDown" do %>
Read More
<% end %>
<% end %>
<% end %>
I use Tailwind CSS for my projects, in case you use Bootstrap, etc. you can discard my classes. What is most important here is including the id
of posts (highlighted above) which is what turbo will be used to reference the posts on each query.
Back in the index view, you need to set up the rendering of the above partial view (you can read more on partials here) as well as the turbo_stream_tag
helper to create a turbo stream.
<%= render 'posts' %>
<%= turbo_frame_tag :pagination, loading: :lazy, src: posts_path(format: :turbo_stream) %>
Here, you will notice that the div
wrapping the posts partial has an id
which is important. This id
is named according to your resource, e.g. articles
or towns
. The turbo_frame_tag
is lady loaded and most importantly, pulls the queries from the posts_path
and displays the queries in the turbo_stream
format we defined on the posts_controller
respond_to
helper method.
I hope you are still following and everything is starting to come together.
We further will need to create the index.turbo_stream.erb
view for turbo.
<%= turbo_stream.append :posts do %>
<%= render 'posts' %>
<% end %>
<%= turbo_stream.append :posts do %>
<% if @pagy.next.present? %>
<%= turbo_frame_tag :pagination, loading: :lazy, src: posts_path(format: :turbo_stream, page: @pagy.next) %>
<% end %>
<% end %>
Here, we have to wrap the posts
partial in a turbo_stream and append it with the posts
resource. The turbo_frame_tag
is similar to that in the index.html.erb
and does the same thing. To prevent the loading to be infinitely looped, you will have to include page: @pagy.next
which checks the size of the paginated collection (@records
) in order to know if it is the last page or not.
There you have it, you now have infinite scrolling.
