Infinite scroll loads new content dynamically as users scroll down the page. Instead of clicking through pagination links, visitors see the next batch of posts appear automatically when they reach the bottom.
This approach works well for blogs and content-heavy sites where you want to keep users engaged without interrupting their browsing flow. In this tutorial, you’ll learn how to implement infinite scroll in WordPress using JavaScript and AJAX, without a plugin.
“Infinite scrolling is a web design technique that loads content continuously as the user scrolls down the page, eliminating the need for pagination.” – MDN Web Docs, Web Design Patterns.
If you’d prefer a button-based approach instead of automatic loading, check out the guide on adding an AJAX Load More button.
How It Works
The implementation has four parts:
- A PHP function that handles AJAX requests and returns the next batch of posts
- A script enqueue that passes WordPress data (AJAX URL, current page, max pages) to the frontend
- A JavaScript file that detects when the user scrolls near the bottom and fires an AJAX request
- An HTML container that wraps the post list so new posts can be appended to it
All PHP code goes in your child theme’s functions.php file.
Step 1: Create the AJAX Handler
This function receives the page number from the frontend, runs a WP_Query, and outputs the post HTML:
function savvy_infinite_scroll_handler() {
check_ajax_referer( 'infinite_scroll_nonce', 'nonce' );
$paged = isset( $_POST['page'] ) ? intval( $_POST['page'] ) : 1;
$query = new WP_Query( array(
'post_type' => 'post',
'posts_per_page' => 6,
'paged' => $paged,
) );
if ( $query->have_posts() ) :
while ( $query->have_posts() ) : $query->the_post();
get_template_part( 'template-parts/content', get_post_format() );
endwhile;
endif;
wp_reset_postdata();
wp_die();
}
add_action( 'wp_ajax_infinite_scroll', 'savvy_infinite_scroll_handler' );
add_action( 'wp_ajax_nopriv_infinite_scroll', 'savvy_infinite_scroll_handler' );The two add_action calls register the handler for both logged-in and logged-out users. The check_ajax_referer() call verifies the nonce for security.
Always verify the nonce with check_ajax_referer() in your AJAX handler. Without it, anyone can send requests to your endpoint. Also use wp_die() instead of die() – it triggers proper WordPress shutdown hooks and is the recommended way to terminate AJAX responses.
Step 2: Enqueue the JavaScript
Next, enqueue the JavaScript file and pass the required parameters using wp_localize_script():
function savvy_enqueue_infinite_scroll() {
wp_enqueue_script(
'infinite-scroll',
get_stylesheet_directory_uri() . '/js/infinite-scroll.js',
array( 'jquery' ),
'1.0.0',
true
);
wp_localize_script( 'infinite-scroll', 'infinite_scroll_params', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'infinite_scroll_nonce' ),
'current_page' => get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1,
'max_page' => $GLOBALS['wp_query']->max_num_pages,
) );
}
add_action( 'wp_enqueue_scripts', 'savvy_enqueue_infinite_scroll' );Note the use of get_stylesheet_directory_uri() instead of get_template_directory_uri(). This ensures the file is loaded from your child theme.
Step 3: Create the JavaScript File
Create a file named infinite-scroll.js inside your theme’s js folder:
jQuery(function($) {
var canLoadMore = true;
var currentPage = parseInt(infinite_scroll_params.current_page);
var maxPage = parseInt(infinite_scroll_params.max_page);
$(window).on('scroll', function() {
if (
$(window).scrollTop() + $(window).height() >= $(document).height() - 300
&& canLoadMore
&& currentPage < maxPage
) {
canLoadMore = false;
currentPage++;
$.ajax({
url: infinite_scroll_params.ajax_url,
type: 'POST',
data: {
action: 'infinite_scroll',
nonce: infinite_scroll_params.nonce,
page: currentPage
},
success: function(response) {
if (response) {
$('.post-list').append(response);
canLoadMore = true;
} else {
canLoadMore = false;
}
},
error: function() {
canLoadMore = false;
}
});
}
});
});The script listens for the scroll event and fires an AJAX request when the user gets within 300 pixels of the page bottom. It increments the page number with each request and appends the response HTML to the post list container.
Step 4: Set Up the HTML Structure
Wrap your post loop in a container with the class post-list. This is the element the JavaScript targets when appending new posts:
<div class="post-list">
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
<?php get_template_part( 'template-parts/content', get_post_format() ); ?>
<?php endwhile; endif; ?>
</div>Make sure the class name matches what’s in your JavaScript. You can also add a loading indicator and an end-of-content message:
<div class="infinite-scroll-status" style="display:none; text-align:center; padding:20px;">
Loading more posts...
</div>Performance Optimization
The scroll event fires very frequently, which can cause performance issues. Use requestAnimationFrame() to throttle it:
var ticking = false;
$(window).on('scroll', function() {
if (!ticking) {
window.requestAnimationFrame(function() {
if (
$(window).scrollTop() + $(window).height() >= $(document).height() - 300
&& canLoadMore
&& currentPage < maxPage
) {
// Trigger AJAX load here
}
ticking = false;
});
ticking = true;
}
});This ensures the scroll check runs at most once per animation frame, preventing jank and unnecessary processing.
Adding a Pagination Fallback
Always include a fallback to traditional pagination for users with JavaScript disabled and for search engine crawlers:
<noscript>
<?php
if ( function_exists( 'the_posts_pagination' ) ) {
the_posts_pagination();
}
?>
</noscript>Wrapping the pagination in a <noscript> tag means it only appears when JavaScript is unavailable. This way users always have a way to navigate your content.
SEO Considerations
Infinite scroll can make it harder for search engine crawlers to discover all your content, since they may not trigger the scroll-based loading. Keep these points in mind:
- Always include the pagination fallback so crawlers can follow page links
- Make sure each paginated URL (
?paged=2,?paged=3) returns valid content when accessed directly - Use a sitemap to ensure all posts are discoverable regardless of how they’re loaded on the frontend
FAQs
Common questions about implementing infinite scroll in WordPress:
$.ajax() with the native fetch() API and use document.addEventListener('scroll', ...) instead of jQuery's scroll handler. The logic stays the same - you just swap the syntax. Modern browsers support fetch natively without any library.check_ajax_referer().posts_per_page value in the WP_Query arguments inside your AJAX handler function. For example, set it to 10 to load 10 posts per request. Keep it reasonable - loading too many posts at once defeats the purpose of lazy loading.wp_die(). It fires proper WordPress shutdown hooks and is the recommended way to terminate AJAX responses. Using die() or exit bypasses these hooks, which can break functionality that depends on them, like object caching or logging.Summary
Implementing infinite scroll in WordPress requires four pieces: an AJAX handler that queries the next page of posts, a script enqueue that passes page data to the frontend, a JavaScript file that fires requests on scroll, and an HTML container to append the results to.
Always include nonce verification in your AJAX handler for security, use wp_die() instead of die(), and throttle the scroll event with requestAnimationFrame() for better performance.
Keep a pagination fallback for SEO and accessibility – crawlers and users without JavaScript need a way to navigate your content too.

