Three different post types on homepage

Since you’re not displaying a different amount of posts on the front page and on the paged pages for your main query, you don’t really need to mess around with the offset. All you need is to exclude the three posts you’ve already shown from your query, which you can do with the pre_get_posts action:

function get_featured_one() { /* returns your post #1 */ }
function get_featured_two() { /* returns your posts #2 and #3 */ }

add_action( 'pre_get_posts', function( $query ) {
    if ( ! $query->is_main_query() || is_admin() )
        return;

    if ( is_front_page() ) {
        $featured_one = get_featured_one();
        $featured_two = get_featured_two();

        $exclude = array_merge( wp_list_pluck( $featured_one->posts , 'ID' ), wp_list_plugk( $featured_two->posts, 'ID' ) );
        $query->set( 'post__not_in', $exclude );
    }
});

Then in your template files just get rid of your $mainLoop, because WordPress already does that query long before it reaches your template file, so there’s no need to run one again. All you need is to display your latest post in your header:

<?php if ( is_front_page() && ! is_paged() ) : ?>
    <?php $latest = get_featured_one(); ?>
    <?php while ( $latest->have_post() ) : $latest->the_post(); ?>
        <!-- Output stuff here. -->
    <?php endwhile; ?>
<?php endif; ?>

And same thing but get_featured_two() for your archive/front-page/index template for your two 50/50 posts. After that, the (actual) main loop will take care of everything else. So this is your order:

  • $latest->have_posts()
  • $featured->have_posts()
  • have_posts()

For paged pages you’ll probably want to leave out the $featured output, but still keep those posts excluded from the main query, since you’ve already shown them on your front page. Also including them in your main query will change the total number of posts returned by the main query (on paged pages), and will break pagination.

Both get_featured_* functions have to return a WP_Query object:

function get_featured_one() {
    return new WP_Query( array(
        'ignore_sticky_posts' => true,
        'posts_per_page' => 1,
    ) );
}

function get_featured_two() {
    return new WP_Query( array(
        'ignore_sticky_posts' => true,
        'tag' => 'featured',
        'posts_per_page' => 2,
    ) );
}

They can also probably use some static caching to improve performance, but only if you’re absolutely sure that calling them several times in a single request must result in the exact same output (i.e. never used in a switch_to_blog() context).

It will probably also be a good idea to exclude the post returned in _one() from _two() because a post can probably be both featured and latest.

I did something similar although a bit more complex in my Semicolon theme. It’s trickier because the amount of posts has to stay the same regardless of whether featured posts are stuck to the top, hence the offset magic. But the idea is very similar.

Hope that helps.

Leave a Comment