• Skip to content
  • Skip to primary sidebar
  • Skip to footer

Foliovision

Main navigation

  • Weblog
    • FV Player
    • WordPress
    • Video of the Week
    • Case Studies
    • Business
  • About
    • Testimonials
    • Meet the Team
    • We Support
    • Careers
    • Contact
    • Pricing
  • Products
  • Support
    • FV Player Docs
    • Pro Support
  • Login
  • Basket is empty
Affordable VAST/VPAID for Wordpress has arrived. Serve ads with your videos starting today!

How to improve Ajax pagination in WordPress

6 June 2023 / Martin / Leave a Comment

The big problem with the archive pagination in WordPress is that it’s based on a fixed number of posts. So when you open a homepage of a busy news site it shows last 10 articles. If you go to the second page it shows next 10 articles and so on.

The issue starts when you remain on the homepage for a bit longer. If a new article was published in the meantime going to the second page will move the last article from the first page to the second page of the archive.

Here’s a video demonstrating the issue on a website with a “Load More Posts” button. The Ajax call which loads these posts is simply using standard WordPress paged archive. So the “AFC East Notes: Allen, Van Ginkel, Patriots” post appears the second time as a new blog post had been published before we hit the button:

Clicking "Load More Posts" loads the same post again
play-sharp-fill

Clicking "Load More Posts" loads the same post again

The solution here is to switch “Load More” entries to count by date. So instead of the standard:

/page/2

We are using:

/page/2?load_after=2023-06-05-12:52:33

Here’s what the code looks like:

/**
 * Template function:
 * Add ?load_after to the link, using the date of the last post in the loop
 */
function fv_archive_paging_load_after( $link ) {
  global $post;

  // Skip the search pages
  if ( stripos( $link, '?s=' ) !== false || stripos( $link, '/search/' ) !== false ) {
    return $link;
  }

  $link = remove_query_arg( 'load_after', $link );
  $link = remove_query_arg( 'load_before', $link );

  return add_query_arg( 'load_after', str_replace( ' ', '-', $post->post_date ), $link );
}

function fv_archive_paging_load_after_cb( $next_link ) {
  $next_link = preg_replace_callback(
    '~href="(.*?)"~',
    function( $match ) {
      return str_replace(
        $match[1],
        fv_archive_paging_load_after( html_entity_decode( $match[1] ) ),
        $match[0]
      );
    },
    $next_link
  );

  return $next_link;
}

/**
 * Respect ?load_after and ?load_before URL query arguments
 */
add_action(
  'pre_get_posts',
  function( $query ) {

    if ( ! $query->is_main_query() ) {
      return;
    }

    // Get rid of standard paging variables, but save them for later
    // as we need to retain the original pagination number of be able to figure out
    // if there are more pages of content
    if ( ! empty( $_GET['load_after'] ) || ! empty( $_GET['load_before'] ) ) {
      $query->set( 'fv_revert_paged', $query->get( 'paged' ) );

      $query->set( 'paged', false );
    }

    if ( ! empty( $_GET['load_after'] ) ) {
      if ( preg_match( '~([0-9]{4}-[0-9]{2}-[0-9]{2})-([0-9]{2}:[0-9]{2}:[0-9]{2})~', $_GET['load_after'], $match ) ) {
        $query->set(
          'date_query',
          array(
            'before' => $match[1] . ' ' . $match[2]
          )
        );
      }
    } else if ( ! empty( $_GET['load_before'] ) ) {
      if ( preg_match( '~([0-9]{4}-[0-9]{2}-[0-9]{2})-([0-9]{2}:[0-9]{2}:[0-9]{2})~', $_GET['load_before'], $match ) ) {
        $query->set(
          'date_query',
          array(
            'after' => $match[1] . ' ' . $match[2]
          )
        );

        // Later we have to flip the order of the posts loaded - see the_posts filter
        $query->set( 'order', 'ASC' );
        $query->query_vars['fv_revert_order'] = 'DSC';
      }
    }
  }
);

/**
 * Revert order of posts if we loaded posts using ?load_before
 */
add_filter(
  'the_posts',
  function( $posts, $wp_query ) {
    if ( $wp_query->get( 'fv_revert_order' ) ) {
      $posts = array_reverse( $posts );
    }
    return $posts;
  },
  10,
  2
);

/**
 * Put the original paging back to make sure pagination controls sense the last page of posts properly
 * Also set the max_num_page to indicated there are more posts
 */
add_action(
  'wp_head',
  function() {
    global $paged, $wp_query;
    if ( $wp_query->get( 'fv_revert_paged' ) ) {
      $paged = $wp_query->get( 'fv_revert_paged' );

      $wp_query->max_num_pages = $paged + 1;
    }
  }
);

/**
 * Since WordPress fails to detect a paged URL and adds the trailing slash we need to ensure it understands it is indeed paged and only use trailing slash if permalinks require that
 */
add_filter(
  'redirect_canonical',
  function( $redirect_url ) {

    if ( get_query_var( 'fv_revert_paged' ) > 0 ) {
      $redirect = wp_parse_url( $redirect_url );
      // Replace the URL path with the URL path with the traling slash removed and then added if needed
      $redirect_url = str_replace( $redirect['path'], user_trailingslashit( untrailingslashit( $redirect['path'] ), 'paged' ), $redirect_url );
    }

    return $redirect_url;
  }
);

Just add that code to your theme functions.php file.

To finally use this in your template you have to wrap your get_next_posts_link() call in fv_archive_paging_load_after_cb():

$next = get_next_posts_link();
if ( $next ) {
  echo fv_archive_paging_load_after_cb( $next );
}

Unfortunately get_next_posts_link() does not come with any filter for the href="..", see WordPress core code. So you really need to put that into your template.

Overall this is a bit of work to be done and we wish it would be a simple plugin which could be used on any website. Well, at least with the traditional WordPress themes and not Block or Elementor themes. These might need a slightly different solution.

Martin Vicenik

Martin Viceník

Martin graduated as an engineer in Computer Science from Slovak Technical University in Bratislava. He grew up in Liptovský Mikuláš in northern Slovakia next to the beautiful Tatra mountains. He is the developer behind our FV Player.

Categories: WordPress

Related Posts

  1. Comment Caching Models to improve WordPress Core Performance

  2. Improve your WordPress speed by Caching Comments

  3. Post pagination

    Post pagination

Reader Interactions

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You can click here to Subscribe without commenting

Primary Sidebar

Categories

  • Business
  • Camera Reviews
  • Case Studies
  • Design
  • FV Player
  • Internet Marketing
  • IT
  • Life
  • SEO
  • Slovak
  • Video of the Week
  • WordPress

Footer

Our Plugins

  • FV WordPress Flowplayer
  • FV Thoughtful Comments
  • FV Simpler SEO
  • FV Antispam
  • FV Gravatar Cache
  • FV Testimonials

Free Tools

  • Pandoc Online
  • Article spinner
  • WordPress Password Finder
  • Delete LinkedIn Account
  • Responsive Design Calculator
Foliovision logo
All materials © 2025 Foliovision s.r.o. | Panská 12 - 81101 Bratislava - Slovakia | info@foliovision.com
  • This Site Uses Cookies
  • Privacy Policy
  • Terms of Service
  • Site Map
  • Contact
  • Tel. ‭+421 2/5292 0086‬

We are using cookies to give you the best experience on our website.

You can find out more about which cookies we are using or switch them off in .

Powered by  GDPR Cookie Compliance
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.

Necessary Cookies

Strictly Necessary Cookie allow you to log in and download your software or post to forums.

We use the WordPress login cookie and the session cookie.

If you disable this cookie, we will not be able to save your preferences. This means that every time you visit this website you will need to enable or disable cookies again.

Support Cookies

Foliovision.com uses self-hosted Rocket.chat and self-hosted Freescout support desk to provide support for FV Player users. These cookies allow our visitors to chat with us and/or submit support tickets.

We are delighted to recommend self-hosted Rocket.chat and especially Freescout to other privacy-conscious independent publishers who would prefer to self-host support.

Please enable Strictly Necessary Cookies first so that we can save your preferences!

3rd Party Cookies

This website uses Google Analytics and Statcounter to collect anonymous information such as the number of visitors to the site, and the most popular pages.

Keeping this cookie enabled helps us to improve our website.

We reluctantly use Google Analytics as it helps us to test FV Player against popular Google Analytics features. Feel free to turn off these cookies if they make you feel uncomfortable.

Statcounter is an independent Irish stats service which we have been using since the beginning of recorded time, sixteen years ago.

Please enable Strictly Necessary Cookies first so that we can save your preferences!