Fix Pagination 404 When a Custom Post Type and Page Share the Same Slug

When a custom post type (CPT) and a regular WordPress page share the same slug, pagination breaks. Visiting /news/page/2/ returns a 404 because WordPress resolves the URL to the page rather than the CPT archive. This even happens with AJAX pagination — a direct URL load still 404s even though AJAX navigation works fine.

Problem: A custom post type is registered with the slug news, but a WordPress page also uses the slug news. Navigating to /news/page/2/ returns a 404 error.

Solution: The conflict arises because WordPress resolves the slug to the page, which has no pagination. Fix it by renaming either the page slug or the CPT rewrite slug so they no longer collide — then flush rewrite rules by visiting Settings → Permalinks.

Root cause. WordPress resolves /news/page/2/ against its rewrite rules. Because a page with the slug news exists, the query goes to the page, not the CPT, and the paged parameter is not recognised.

Solution. Add a custom rewrite rule at the top priority that maps /cpt-slug/page/N/ to the correct paginated page query, then register it on init. The function below does this for every public custom post type whose slug matches an existing page:

<?php
add_action( 'init', 'fix_cpt_page_pagination_conflict' );

function fix_cpt_page_pagination_conflict() {
    $custom_post_types = get_post_types(
        [ 'public' => true, '_builtin' => false ],
        'names'
    );

    foreach ( $custom_post_types as $slug ) {
        // Only add the rule when a page with the same slug exists
        if ( get_page_by_path( $slug ) ) {
            add_rewrite_rule(
                '^' . preg_quote( $slug, '/' ) . '/page/([0-9]+)',
                'index.php?pagename=' . $slug . '&paged=$matches[1]',
                'top'
            );
        }
    }
}

After adding this code, go to Settings → Permalinks and click Save Changes to flush the rewrite cache and register the new rule.

If you need to verify the rule was registered correctly, you can inspect the rewrite rules array:

<?php
// Temporary debug — remove after verification
$rules = get_option( 'rewrite_rules' );
foreach ( $rules as $pattern => $query ) {
    if ( strpos( $pattern, 'news' ) !== false ) {
        echo $pattern . ' => ' . $query . "
";
    }
}

NOTE: The cleanest long-term fix is to give the CPT and the page different slugs. Rewrite rule overrides can become hard to maintain as the site grows. Reserve this approach for situations where renaming the slug is not an option (for example, when external links already point to the existing URL structure).