Pagination on custom post type on front-page.php

The root problem here is the call to query_posts:

    $args = array('post_type' => array('posts', 'affiliate-products'));

    query_posts($args);

At no point do you tell it which page to fetch, so it always fetches the first page, aka the default.

You could add this, but fundamentally, the entire approach is incorrect, and has many other problems you might not be aware of. For example, you don’t call wp_reset_query, so any loops or code that run after your post loop will be broken. query_posts usage is a massive red flag, avoid it at all costs.

Sadly, a lot of legacy code still exists, and a lot of very old tutorials that mistakenly teach this to new people.

The Real Way To Change What Posts Get Shown

If you want to change which posts appear on a page, you’re supposed to use the pre_get_posts filter so that it fetches what you want the first time.

Otherwise, you’re throwing away the main query after the fact, then making a second query, making the page as much as 2x slower. Then, because you created your own query, none of the built in functionality is present, so you have to rebuild that too.

Instead, in your functions.php hook into pre_get_posts like this:

add_action( 'pre_get_posts', function( \WP_Query $query) {
    //
} );

Now you can use $query->set and $query->get to change the arguments before it goes to the database. You can use all the conditionals too such as $query->is_home() or $query->is_main_query().

For example, this filter will set the search page to only show 5 posts:

add_action( 'pre_get_posts', function( \WP_Query $query) {
    if ( $query->is_main_query() && $query->is_search() ) {
        $query->set( 'posts_per_page', 5 );
    }
} );

Now the search listings show 5 posts per page, pagination still works, it’s just as fast, no performance penalty, and other features still work too.

Some notes:

  • pre_get_posts is a filter that modifies a standard main post loop. Use it with a normal loop, template modification is not necessary, if you’re modifying templates to implement this then something has gone wrong
  • Never ever under any circumstances use query_posts. There is no valid use case that isn’t covered by better/easier options. Erase this from your mind and if you come across it treat it with deep suspicion, it’s a major red flag
  • If you need to change the posts the main loop fetches, use the pre_get_posts filter
  • If you need to do a second query, use WP_Query, but never use it to change or replace the main query. This is more for things like sidebar post lists, etc
  • Remember to cleanup after yourself, when using WP_Query you should call wp_reset_postdata afterwards or you’ll break things
  • Don’t smush all your loop statements together on one line, it makes things harder to read and confuses editors and tools

For reference, this is what a standard post loop looks like:

if ( have_posts() ) {
    while ( have_posts() ) {
        the_post();
        // display the post
    }
} else {
    // nothing was found
}

If you need to put a query_posts call before it, then something has gone horribly wrong. Use pre_get_posts instead.

I strongly recommend you look through the slides of this talk, they cover how to use queries, best practices, and common mistakes