Why does WP pagination break when used on the homepage?

If you take the code below and use it in a page template it works perfectly (make sure you have 3 or more posts). However, as soon as you use the same code on home.php, front-page.php, index.php or even as a page template but set as a static homepage, it breaks.

The URL shows /page/2/ but you get a 404 page. If you change the URL to /?page=2 it works.

I have seen so many questions related to this all over the place, yet none have a solution that works.

For the sake of simplicity I have simplified the loop below and I'm using the default WP next and previous posts links. I don't wish to use a plugin such as WP-PageNavi or similar.

<?php get_header(); ?>


        // taken from https://codex.wordpress.org/Pagination
        if ( get_query_var('paged') ) { 
            $paged = get_query_var('paged'); 
        else if ( get_query_var('page') ) {
            $paged = get_query_var('page'); 
        else {
            $paged = 1;

        $wp_query = new WP_Query( array( 
            'posts_per_page' => 2,
            'paged' => $paged


    <?php if ( $wp_query->have_posts() ) : while ( $wp_query->have_posts() ) : $wp_query->the_post(); ?>

         <?php the_title(); ?>

    <?php endwhile; endif; ?>

    <?php previous_posts_link(); ?>
    <?php next_posts_link(); ?>

    <?php wp_reset_query(); ?>

<?php get_footer(); ?>
  • And you down voted because? please be constructive
    – Andrew
    Dec 27 '12 at 0:59
  • 1
    +1 seems like a well-researched question to me. Have you checked what value is returned by get_query_var('page') by itself? Also is there anything unusual about your permalink structure?
    – cantera25
    Dec 29 '12 at 6:20
  • @cantera25 Thanks - My permalink structure is the "post name" setting. If I set it to the default (?p=123), clicking "next page" still doesn't work and shows ?paged=2 in the URL. Now if I change it to ?page=2 it works fine, why is this? If I echo get_query_var('page') on the index.php I get nothing at all. If I go through and change each page URL to ?page=X for example, echoing get_query_var('page') now gives me 2 for ?page=2, 3 for ?page=3 etc.
    – Andrew
    Dec 29 '12 at 6:45

The solution is to not alter the main query in the template. The default query happens before the template is loaded, so querying in the template overwrites that original query, which is a waste of resources. See the example in codex under pre_get_posts for the correct way to alter the default query without pagination issues.

  • Thank you, after reading the codex page it made things a lot clearer. From what I understand now, when you want to modify the main loop on pages like index.php, front-page.php, home.php etc you need to use pre_get_posts action hook and use query->set to modify the posts per page, post type etc. As you said this all happens before the template is loaded and by using pre_get_posts, there is no performance hit. I was always under the impression that wp_query was the answer when dealing with any form of loop. What an eye opener.
    – Andrew
    Dec 29 '12 at 22:03

Also from codex.wordpress.com/Pagination (Under "Advanced Troubleshooting Steps" > "Removing query_posts from the main loop") regarding modifying the main query via the pre_get_posts action mentioned by Milo:

[...] add the query for your home and category pages back in your theme's functions.php file:

function my_post_queries( $query ) {
     // do not alter the query on wp-admin pages and only alter it if it's the main query
    if (!is_admin() && $query->is_main_query()){
        // alter the query for the home and category pages 

           $query->set('posts_per_page', 3);

            $query->set('posts_per_page', 3);
add_action( 'pre_get_posts', 'my_post_queries' );

So your implementation might look something along the lines of

function modify_query( $query ) {
    if( !$query->is_main_query() )
        return;  //Only wish to modify the main query

    //Modify the query vars for the home and front pages
    if( is_home() || is_front_page() ) {
        $paged = get_query_var('page');

        //If the 'page' query var isn't set, or didn't return an integer, default to 1
        $paged = !isset( $paged ) || !is_int( $paged ) ? $paged : 1;

        $query->set( 'paged', $paged );
        $query->set( 'posts_per_page', 2 );
add_action( 'pre_get_posts', 'modify_query' );        
  • Thank you also. I had actually read that page you linked to but took another go just now to make sense of it, now that I'm aware of pre_get_posts and $query->set. I've used an example from the page Milo linked to to get my homepage loop all working. It's very similar to the example you have posted above but I didn't need to set $paged. Everything just seems to work without it.
    – Andrew
    Dec 29 '12 at 22:11
  • From the codex: is_front_page() will not work inside pre_get_posts()
    – Vasu Adiga
    Sep 10 '13 at 5:40

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.