Fixing Infinite Loops in WordPress Adjacent Post Navigation

A client reached out a few weeks ago with a weird one. They run a high-traffic WooCommerce store, and after a routine update, their “Next Product” links started acting possessed. On certain products—specifically those bulk-imported on the same day—the navigation was just reloading the current page. It was an infinite loop that made the site look broken to anyone browsing their catalog.

My first instinct was to blame the cache. It’s usually the culprit in these scenarios. I flushed Redis, purged the CDN, and even checked for race conditions in their custom fragments. Nothing changed. The “Next” link was stubbornly pointing to the same post ID. That’s when I realized the issue wasn’t in the delivery; it was in the query logic itself. Specifically, how WordPress handles adjacent post navigation since the 6.9 release.

The WordPress 6.9 Tiebreaker Change

Before version 6.9, the get_adjacent_post() function was a bit naive. It relied almost entirely on the post_date. If you had five posts published at the exact same second (hello, bulk imports), the database didn’t have a deterministic way to pick the “next” one. It was a coin flip that often led to navigation bugs.

WordPress 6.9 fixed this by adding the post ID as a tiebreaker. Now, if the dates are identical, it compares the IDs to decide the order. You can see the full discussion on Trac ticket #8107. This was a great fix for the core, but it broke a lot of custom code that was playing fast and loose with SQL filters.

The problem is that many themes and plugins—like the one my client was using—hook into the get_{$adjacent}_post_where filter to modify navigation. Usually, they do a simple str_replace on the date. But in 6.9, the WHERE clause changed from a simple date check to a nested comparison that includes the post ID twice. If your code only replaces the date and ignores the ID, the query gets confused and returns the current post again. Total nightmare.

Fixing the Loop with Better Filtering

If you’re modifying the navigation logic, you can’t just swap strings anymore. You need to account for the secondary ID comparison that Core now injects. Here is how I handled it for the client to ensure their products stayed in sync without the infinite loop.

/**
 * Fixes adjacent post navigation loops for identical post dates.
 */
function bbioon_fix_adjacent_navigation( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) {
    // Logic to find your custom target post
    $target_post = bbioon_get_custom_target( $post );

    if ( ! $target_post instanceof WP_Post ) {
        return $where;
    }

    // Replace the date comparison as usual
    $where = str_replace( $post->post_date, $target_post->post_date, $where );

    // Here is the kicker: we MUST handle the ID comparison for WP 6.9+
    $where = preg_replace(
        "/AND p\.ID (<|>) {$post->ID}\)/",
        "AND p.ID $1 {$target_post->ID})",
        $where
    );

    return $where;
}
add_filter( 'get_next_post_where', 'bbioon_fix_adjacent_navigation', 10, 5 );
add_filter( 'get_previous_post_where', 'bbioon_fix_adjacent_navigation', 10, 5 );

I used a regex here because the SQL structure can vary slightly depending on whether you’re looking for the “next” or “previous” post. This ensures that when the date matches, the tiebreaker logic is looking for the right ID relative to our target, not the current post.

So, What’s the Point?

This whole situation highlights a common trap in WordPress development: relying on string replacement for SQL manipulation. It’s quick, sure, but it’s brittle. When Core evolves—even for a good reason like fixing a decade-old bug—your “quick fix” can become a site-breaking liability. This is similar to the way WP_Query handles complex joins; if you aren’t precise, you’re going to have a bad time.

Look, this stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before. Trust me on this—it’s better to fix the foundation than to keep patching the cracks.

Have you run into issues with the 6.9 navigation changes yet? Most developers don’t notice it until they hit a bulk-import scenario, but once you see it, you can’t unsee it.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.

Leave a Reply

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