My chart on differences between WP query functions is one of the most popular materials I did. But neither it or chart on core load don’t cover why exactly query_posts
breaks pagination.
This post will cover that problem and why you should never overwrite main query in a template file.
Main query
Let us think through basic example. You have a blog with six posts.
- What happens on home page?
- What happens on next page from there?
Omitting some details, the process is the following:
- WordPress processes the URL.
- Then it executes main query (global
$wp_query
instance). - Template loader uses conditional tags (which rely on main query) to find and load theme template.
On the second page main query shifts to a second set of posts and the same process happens.
Main query with query_posts()
You notice that you don’t like lone sixth post on the second page. You decide it would be more neat to have three pages of two posts each. To do that you throw a query_posts()
call into your template. It overrides main query to only two posts.
How does this change the process?
At first everything looks the same. Main query runs, template loader picks template, which runs with your changes.
But only to a point:
- on first page your template discards posts 1–5 and queries 1–2 instead;
- on second page your template discards post 6 and queries 3–4;
- on third page… It all breaks! You get 404 error instead of posts 5–6!
What just happened? Note how your query_posts()
call is in your template and template loader loads it.
Main query doesn’t know what is in your template and neither does template loader because of it. When it goes to look for third page of posts — there aren’t enough of them with default amount per page.
Since you only made your changes inside template, there is no way for it to make main query know about them in time!
As result when you request an URL, which main query thinks is invalid, that is precisely how it treats it.
Stop using query_posts()
Never use query_posts()
or otherwise override main query in template.
Your template relies on template loader, which relies on original main query.
Because template runs at the end of the load process, you cannot make changes to main query inside of it! If so query_posts
breaks pagination and will continue to do so!
Any change to main query must be done way before template. You can do that with pre_get_posts
hook.