Your Blog Pagination Is Probably Hiding Posts From Google

pagination

A blog with sixty posts, twelve to a page, has five pages. Page one lists twelve posts; the other forty-eight are reached through the pagination. If a search engine's crawler cannot follow it, your older posts are reachable only through your sitemap and whatever links to them from elsewhere, and a post that nothing links to is one a search engine has little reason to rank.

Most blog pagination is built in a way crawlers cannot follow, or tells them not to bother. We found one of these mistakes on our own blog while rebuilding it this month. Here are the four common ones, what to do instead, and a checklist at the end.

Mistake 1: "Load more" with no URLs

A "Load more" button or infinite scroll that fetches the next page with JavaScript is pleasant to use and invisible to crawlers. Google's documentation says it generally crawls only the URLs in the href of <a> elements, that its crawlers do not click buttons, and that Search does not scroll or otherwise interact with the page.

If the only way to reach post 13 is to click "Load more", post 13 is not linked from your blog at all, as far as a crawler is concerned.

Instead: give every page a URL, /blog?page=2 or /blog/page/2, and link to it with a real <a href>. A "Load more" button can still exist on top: render the pagination links in the HTML, and let JavaScript intercept the click and append the next page in place. Crawlers follow the link; people get the button.

Mistake 2: pagination as buttons

The same problem in a different shape:

tsx
<button onClick={() => setPage(page + 1)}>Next</button>

It changes what the page shows without changing the URL. Crawlers cannot follow it, a visitor cannot share page three or come back to it, and the back button skips right past it.

Instead: pagination is navigation, so make it links. In a server-rendered framework that also means the page for ?page=2 is rendered on the server, with the posts in the HTML, which is what a crawler reads.

Mistake 3: every page canonical to page one

This one looks like good hygiene, which is why it is everywhere:

HTML
<!-- on /blog?page=2, /blog?page=3, ... -->
<link rel="canonical" href="https://example.com/blog" />

A canonical tag says "this page is a duplicate; the real one is over there". Page two is not a duplicate of page one: it lists different posts. Telling a search engine otherwise invites it to drop page two from its index, and with it the only links to those posts.

Google's pagination guidance says exactly this: do not use the first page of a sequence as the canonical page, and give each page its own canonical URL instead.

We had this bug. Until this release, every page of our own blog at kookee.dev, ?page=2 included, declared /blog as its canonical. Now each unfiltered page is canonical to itself: page one to /blog, page two to /blog?page=2.

Instead: a self-referencing canonical on every page of the sequence.

Mistake 4: noindex on pages after the first

The older version of the same instinct: keep "thin" listing pages out of the index with noindex, follow, so the posts get indexed and the lists do not.

The catch is the follow. Google's John Mueller has said that a page left on noindex long enough is dropped entirely, and its links are no longer followed, so noindex, follow ends up working like noindex, nofollow. Those links are the path to your older posts.

Instead: let the pages be indexed. A listing page with twelve post titles and excerpts is not thin content, and a search for a post's topic is far more likely to land on the post than on the list that links to it. Google's documentation also says the pages of a sequence can share the same title and description, so there is no duplicate-title problem to solve.

What about filters?

Tags, categories, search and sort orders multiply your URLs: /blog?tag=react&page=3&q=hooks is a page too. These do not all need to be in an index, and a crawler working through every combination is spending its time on your site in the least useful way.

Two sensible options:

  • Canonical filtered views to the unfiltered list. This is what we do: /blog?tag=react and /blog?tag=react&page=2 declare /blog as canonical, while the unfiltered pages are canonical to themselves. Every post stays reachable through the unfiltered sequence, and the combinations stay out of the way.

  • Give the filters you want to rank their own paths. If a tag page is a landing page in its own right, make it one, at /blog/tag/react with a self-referencing canonical and its own pagination, and keep query-string filters for everything else.

Google's own guidance also allows noindex on filtered and re-sorted views. Whichever you pick, apply it to the filters, not to the pagination of the main list.

rel="next" and rel="prev"

Google announced in 2019 that it no longer uses them. Its documentation notes that other search engines may still use them, but they are no substitute for links on the page.

A pagination bar with every page number does not scale, and one with only "Previous" and "Next" makes page forty a forty-click walk. The common middle ground: the first page, the last, and one either side of the current one, with an ellipsis for each gap.

Plain Text
‹ Previous   1  …  6  [7]  8  …  15   Next ›

A link to the last page lets both people and crawlers jump to your oldest posts, and the sequence stays walkable from either end. Pagination still is not a guarantee that a crawler reaches everything, which is what the sitemap is for: list every post in it, with a lastmod taken from when the post last changed significantly.

Two accessibility details belong in the same component:

  • Wrap it in a <nav> with a label such as "Pagination", and mark the current page with aria-current="page".

  • At the ends, render "Previous" and "Next" as disabled text, not as links to nowhere or to the page you are already on.

A checklist

  • Every page of the list has its own URL, and its posts are in the server-rendered HTML.

  • Pagination is made of <a href> links. A "Load more" button, if you have one, enhances those links rather than replacing them.

  • Each page of the unfiltered list has a self-referencing canonical. Page two is not canonical to page one.

  • No noindex on the pages of the main list.

  • Filtered and sorted views are canonical to the unfiltered list, or noindexed, or promoted to proper paths if you want them to rank.

  • The sitemap lists every post.


Kookee's Pagination component for React renders this pattern as links: the first and last page, one either side of the current page, ellipses, labelled navigation, and disabled ends. It works from a Server Component and before JavaScript loads, and getPaginationItems gives you the same page window for markup of your own. See Build a blog with the React components for a full list page with tag filters and search.