WordPress does a lot to automatically to optimize your blog for the search engines -- linking between pages, putting your post titles in your page title tag and header tags, etc. But one flaw in the design has been bugging me for a while: the order of page numbers. Let me explain.

Let's say you're showing 10 posts per page. Until you've written 11 posts, everything is on the homepage. Once the 11th post is published, your first post moves to an archive page, and posts 2-11 stay on the homepage. When you post again, post 2 joins post 1 on the archive page. And so on until you get to 20 posts.

So far so good.

But when you publish your next post, we see the flaw in the design. Your first post, which had been archived to page 2 gets pushed to archive page 3. From then on, every time you publish a new post, one post shifts from the end of each archive page to the next archive page. Every 10 posts, the contents of your archive pages change completely.

What if one of your archive pages got ranked high by Google? Within 10 posts, Google would be outdated, and any traffic they sent you would probably bounce when the searcher couldn't find what they wanted.

I've been thinking for a while that it would be nice to reverse the order of the archive pages so that the oldest posts were on page 1, the next oldest on page 2, etc. Yesterday, I spent some time hacking WordPress and figured out how to make it happen.

Disclaimers

Before we continue, if you're planning on trying this, let's be clear about a few things:

  • What follows involves editing your WordPress theme and WordPress files.
  • You should make a backup copy of each file before editing it.
  • If you're using a different version of WordPress than me, you might need to make different changes than what I'm going to show.
  • I will NOT provide tech support or any other kind of assistance if you mess up your blog or need to adapt this code to a different WordPress version.
  • Making these changes may cause your blog to explode or fall into a black hole.

Okay, I'm exaggerating a little, but you get the point. Continue at your own risk.

Changes to wp-includes/query.php

After exploring several different approaches, I decided that the easiest way to reverse the page numbers would be to reverse the order in the query that retrieves the posts from the database. So instead of sorting by date descending (ie. last post first, and thus last post on page 1), we sort by date ascending (first post on page 1, etc.).

But it's not quite that simple -- reversing the query order requires solving two problems:

  1. The since the items are retrieved in reverse order, they need to be displayed in the reverse order too (in order to display last post first as usual).
  2. On the homepage (when you don't have a page number), you don't know which page to query for, because you don't know how many total pages there are.

Actually, solving the second problem solves another problem at the same time -- if you have 21 posts, page 3 only has one post on it. But you don't want your homepage to only have one post, so you don't really want your homepage to display the last page -- you want it to continue working just the way it does now.

Here's how I made that work. In the function "get_posts", between the section labeled "MIME-Type stuff for attachment browsing" and the section labeled "Order by", I added this:

if ( empty($q['nopaging']) && !$this->is_singular ) {
	$page = absint($q['paged']);
	if (!empty($page)) {		
		$q['order']=($q['order']=='ASC')?'DESC':'ASC';
		define('AER_REVERSED',1);
	}
}

Key points to note:

  • We don't make any changes on the homepage (that's what "if (!empty($page))" checks).
  • There are other places in our code where we need to know whether we reversed the query order. The most reliable way I could think of, that didn't risk interacting incorrectly with anything else WordPress is doing, was to define a constant that I could check for later, thus "define('AER_REVERSED',1);".

Next, to get the posts to display in the right order, on pages other than the homepage, we need to have WordPress run through the database query results in reverse order. To do that:

  1. Replace the first line of the function "next_post":
    $this->current_post++;

    with:

    if (defined('AER_REVERSED')) $this->current_post--;
    else $this->current_post++;
  2. If we're going to count down, we need to start from the top, so in the function "the_post", we replace this:
    if ( $this->current_post == -1 ) { // loop has just started
    do_action_ref_array('loop_start', array(&$this));

    with this:

    if ( $this->current_post == -1 ) { // loop has just started
    	do_action_ref_array('loop_start', array(&$this));
    	if (defined('AER_REVERSED')) $this->current_post=$this->post_count;
    }
  3. We also need to fix the way WordPress checks to see whether it's done displaying posts, so in the function "have_posts", we change this:
    if ($this->current_post + 1 < $this->post_count) {

    to this:

    if (defined('AER_REVERSED')) {
    	if ($this->current_post && $this->post_count) {
    		return true;
    	} elseif ($this->post_count&&!$this->current_post) {
    		do_action_ref_array('loop_end', array(&$this));
    		// Do some cleaning up after the loop
    		$this->rewind_posts();
    	}
    } else if ($this->current_post + 1 < $this->post_count) {

Okay, that takes care of the easy stuff :-). Next, let's fix the page numbers.

Changes to wp-includes/link-template.php

First, when page 1 was the homepage, it was better not to include a page number in the URL for page 1 -- otherwise it'd be a duplicate of your homepage. That's no longer the case, so we need to tell WordPress to always include a page number (you might think you should leave the number off the last page, but we'll address that issue differently later).

To do that, make 2 changes to the function "get_pagenum_link":

  • Change:
    if ( $pagenum > 1 ) {
    	$result = add_query_arg( 'paged', $pagenum, $base . $request );
    } else {
    	$result = $base . $request;
    }

    to:

    $result = add_query_arg( 'paged', $pagenum, $base . $request );
  • And a little further down, change:
    if ( $pagenum > 1 ) {
    	$request = ( ( !empty( $request ) ) ? trailingslashit( $request ) : $request ) . user_trailingslashit( 'page/' . $pagenum, 'paged' );
    }

    to:

    $request = ( ( !empty( $request ) ) ? trailingslashit( $request ) : $request ) . user_trailingslashit( 'page/' . $pagenum, 'paged' );

Next, in the function "get_next_posts_page_link", replace the contents of the "if ( !is_single() )" block with the following, which is an adapted version of what used to be in "get_previous_posts_page_link" (several of the changes below are like that -- swapping the "previous" and "next" functions, with adaptations as needed). In all the following code, where it says "if (!defined('AER_REVERSED'))", that's the code that's used on the homepage.

if (!defined('AER_REVERSED')) {
	$paged=$max_page;
	global $wp_query;
	if ($extras=($wp_query->found_posts % $wp_query->query_vars['posts_per_page']))
		$anchor='#post'.(1+$wp_query->query_vars['posts_per_page']-$extras);
	else $anchor='';
} else $anchor='';
$nextpage = intval($paged) - 1;
if ( $nextpage < 1 )
	$nextpage = 1;
return get_pagenum_link($nextpage).$anchor;

I'll tell you what I added "$anchor" for in a minute.

Next, we replace the contents of the function "get_next_posts_link" with:

global $paged, $wp_query;

if (!defined('AER_REVERSED')) $local_paged=$wp_query->max_num_pages;
else $local_paged=$paged?$paged:1;
if ( !is_single() && $local_paged > 1 ) {
	$attr = apply_filters( 'next_posts_link_attributes', '' );
	return '<a href="' . next_posts( $wp_query->max_num_pages, false ) . "\" $attr>". preg_replace( '/&([^#])(?![a-z]{1,8};)/', '&$1', $label ) .'</a>';
}

Next, replace the contents of the "if ( !is_single() )" block in "get_previous_posts_page_link" with:

if (!defined('AER_REVERSED')) return '';
if ( !$paged )
	$paged = 1;
$nextpage = intval($paged) + 1;
if ( floor($wp_query->found_posts / $wp_query->query_vars['posts_per_page']) >= $nextpage )
	return get_pagenum_link($nextpage);

Note that by using the function "floor" instead of "ceil" to determine whether to output a link to the previous page, we avoiding linking to the last archive page until it's completely full of posts. The reason for this is that we don't want to link to it while its contents are entirely duplicated on the homepage. I suppose I ought to have it link to the homepage in that case...and I see now how I'd do that. But for now, I'm happy with what I've got.

Finally, the function "get_previous_posts_link" is changed to:

global $paged, $wp_query;

if (!defined('AER_REVERSED')) return '';
if ( !$paged )
	$paged = 1;

$nextpage = intval($paged) + 1;

if ( !is_single() && ( empty($paged) || $nextpage <= floor($wp_query->found_posts / $wp_query->query_vars['posts_per_page'])) ) {
	$attr = apply_filters( 'previous_posts_link_attributes', '' );
	return '<a href="' . previous_posts( false ) . "\" $attr>". preg_replace('/&([^#])(?![a-z]{1,8};)/', '&$1', $label) .'</a>';
}

We're nearly done, but there's one more issue to address.

Changes to your blog template

Here's the problem. Let's say you've got 25 posts. Your homepage displays posts 16 through 25. Archive page 2 displays posts 11 through 20. When a visitor clicks the "older posts" link, you don't want them to see the duplicated items. So we need to add an anchor in the archive page so that we can link to it.

When "$anchor" was added to "link-template.php", it added an anchor to the link in the form "#post[number]" where "[number]" is the number of the post (with the posts on the page numbered 1 to 10) -- for example "#post3".

To make that link automatically scroll to the first older post, we need to add "name" attributes to the headline links. So in index.php, we add this, just before "while ( have_posts()) {" (depending on how your blog template is set up, details of where to put the rest of the code may vary):

$GLOBALS['aer_postnum']=1;

Then, either in index.php, or in another file, if you template uses a separate file to output posts (eg. in mine, it's called "post.php"), find the headline link, and add this to it:

<?php if (isset($GLOBALS['aer_postnum'])) echo 'name="post'.($GLOBALS['aer_postnum']++).'"'; ?>

If the posts are displayed by index.php itself, you can simplify that to:

name="post<?php echo $GLOBALS['aer_postnum']++; ?>"

So the opening tag for the headline link will end up looking something like this:

<a <?php if (isset($GLOBALS['aer_postnum'])) echo 'name="post'.($GLOBALS['aer_postnum']++).'"'; ?> href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link: <?php the_title(); ?>">

So that's it.

Will it help my search engine rankings? I have no idea. It might.

But at least the backwards page numbers won't bug me anymore.