0

I have custom post type called News and a static page that uses a custom template (page-news.php) and indexes all the news posts under homepage.com/news url.

The problem is that I want to paginate the news page so that homepage.com/news/page/2 would show more posts etc. but it returns a 404 error.

This is how I'm changing the pagination rules in functions.php:

function my_pagination_rewrite() {
    add_rewrite_rule('page/?([0-9]{1,})/?$', 'page-news.php?category_name=blog&paged=$matches[1]', 'top');
}
add_action('init', 'my_pagination_rewrite');

And here is my custom query in the page-news.php:

<?php
    $paged = ( get_query_var('paged') ) ? get_query_var( 'paged' ) : 1;
    $query = new WP_Query(array(
        'post_type' => 'news',
        'post_status' => 'publish',
        'posts_per_page' => '4',
        'paged' => $paged
    ));
    $temp_query = $wp_query;
    $wp_query   = NULL;
    $wp_query   = $query;
    while ($query->have_posts()):
        $query->the_post();
        
    <!-- ECHOING THE NEWS POSTS-->

    endwhile;
    wp_reset_postdata();

    echo '<div class="pagination">';

            echo paginate_links( array(
                'base'         => str_replace( 999999999, '%#%', esc_url( get_pagenum_link( 999999999 ) ) ),
                'total'        => $query->max_num_pages,
                'current'      => max( 1, get_query_var( 'paged' ) ),
                'format'       => '?paged=%#%',
                'show_all'     => false,
                'type'         => 'plain',
                'end_size'     => 2,
                'mid_size'     => 1,
                'prev_next'    => true,
                'prev_text'    => sprintf( '<i></i> %1$s', __( 'Newer Posts', 'text-domain' ) ),
                'next_text'    => sprintf( '%1$s <i></i>', __( 'Older Posts', 'text-domain' ) ),
                'add_args'     => false,
                'add_fragment' => '',
            ) );

    echo '</div>';
    
    $wp_query = NULL;
    $wp_query = $temp_query;
?>
2
  • 1
    You can't add rewrite rules for other files, it must to be index.php. WP rewrite rules aren't like Apache or Nginx rules, they're just a regex mapping of pretty URLs on to query variables on index.php ( the same query variables that WP_Query uses ). Also that index.php is not the index.php in your theme, it's the index.php at the root of the WP site, but for rewrite rules it's just a formality, it is always index.php?param=value&etc... – Tom J Nowell Aug 5 '20 at 16:06
  • 1
    Also, is there a reason you created a dedicated template that doubles up the number of queries from scratch? You could have told WP when registering the news CPT that it had a permalinks front of page/ then used the archive-news.php, and all the pagination would have just worked out of the box with a standard post loop, with sitemaps and RSS feeds etc too if you wanted them – Tom J Nowell Aug 5 '20 at 16:09
1

You don't need the rewrite rules, and that's not how they'd work anyway.

The fundamental problem is that you decided not to modify the main query, but to replace it.

There's no need for the custom WP_Query or custom pagination, or a page template. Not to mention by running the main query then discarding the result to make a new one, it doubled the amount of work that needs doing, a major performance hit/slowdown

You can just use an archive-news.php template with a standard post loop, then use pre_get_posts to change how many posts are shown on the page:

// only show 4 posts per page on the news archive
add_action(
    'pre_get_posts',
    function( \WP_Query $query ) {
        // we only want the news post archive, exit early if it isn't
        if ( ! $query->is_main_query() || ! $query->is_post_type_archive( 'news' ) ) {
            return;
        }
        $query->set( 'posts_per_page', 4 );
    }
);

Now the news archive will show 4 posts per page. No rewrite rules, no CPT adjustments, no special page with a page template for the pagination, it should all just work out the box with that hook. You can use normal standard main loops like the default themes. It'll even be faster! You're no longer doubling up all the queries by discarding the main query and putting your own in.

With that, your above code can be simplified to this in an archive-news.php template:

while ( have_posts() ) {
    the_post();
    <!-- ECHOING THE NEWS POSTS-->
}

echo '<div class="pagination">';
echo paginate_links();
echo '</div>';
3
  • Thanks for the response, that seems promising! I have a questions: What is the difference between using the page-new.php template that I have already created vs creating a new news-archive.php. I am new to the wordpress world and haven't worked with archive pages. Would appreciate if you point me to a more detailed tutorial. – Em Karimifar Aug 5 '20 at 17:59
  • Take a look at the template hierarchy, WP uses the main query to pick the template, but it looks for a super specific template, then works its way backwards until it gets to the most generic template, which is index.php, that's why all themes have an index.php. So if it can't find archive-news.php it'll try to load archive.php, and if it can't find that it'll load index.php. Page templates aren't necessary, they're just a way to use an alternative template on a post of type page. For archives you should not use them – Tom J Nowell Aug 5 '20 at 18:06
  • And if you ever want to change something on the main query, use pre_get_posts, don't ignore it and create a new query. CPT's get their own post archives, as do custom taxonomies, with their own URLs, etc, like date archives or tag and category archives, and they all have their own template files, even posts, you can go as specific as a template just for a post with a specific ID, or a tag with a particular name – Tom J Nowell Aug 5 '20 at 18:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.