Pagination
Life after rel=prev/next deprecation. Self-referencing canonicals on paginated pages, view-all decisions, infinite scroll, and load-more buttons that crawl.
Google deprecated rel="prev" and rel="next" as indexing signals on March 21, 2019, and despite seven years of confusion, most sites still treat pagination like the old days. The 2026 reality is simpler than it seems: paginate transparently, give every page its own self-referencing canonical, and let Google figure out the relationship from internal links and content. The hard part is making sure your infinite scroll or “Load more” button is actually crawlable.
TL;DR
- Self-referencing canonicals on every page.
?page=2canonicalizes to?page=2, not to page 1. Canonicalizing all pages to page 1 hides the rest from the index. rel="prev"andrel="next"are indexing-signal dead in Google. Bing still uses them, so keep them if they’re already there. Don’t introduce them just for SEO.- Infinite scroll without progressive enhancement is invisible. If
<noscript>shows nothing, Googlebot rendering quotas leave most of your content uncrawled.
The mental model
Pagination is like the table of contents in a long book. The table of contents (page 1 of a category) lists chapters; each chapter (subsequent pages) contains its own content. Each chapter deserves its own page number because you might cite chapter 7 in another book — but you don’t pretend the whole book is “chapter 1” by canonicalizing every page to it.
The 2018-era myth was that paginated pages were a “duplicate content problem” solved by canonicalizing everything to page 1. That myth was wrong then; it is unambiguously wrong now. Page 2 of a category is not a duplicate of page 1 — it has different products, different listings, different threads. Canonicalizing it to page 1 tells Google “ignore this page entirely,” and the products on page 2 lose their crawl path.
The actual job of pagination markup is to:
- Tell Google that page 2 exists and is its own page.
- Make sure every linked item on every page is reachable by a normal HTTP fetch (no JavaScript-only pagination).
- Avoid creating infinite parameter combinations that flood the crawl budget.
Deep dive: the 2026 reality
The four pagination patterns and what they actually need:
| Pattern | Use case | Self-canonical | Crawlable links | Notes |
|---|---|---|---|---|
Numbered pages (?page=2) | Forums, categories, blog archives | Required | Required | Default choice; works with everything |
| View-all (one long page) | <500 items, latency tolerant | Yes, with <link rel="canonical"> from numbered to view-all | Required | Only if view-all loads in <3s |
| Infinite scroll | Social feeds, image grids | Each “page” needs a unique URL | Requires History API + visible numbered fallback | Needs progressive enhancement |
| Load more button | Modern listings | URL state on click via pushState | Required: <a href="?page=2"> underneath | The “best of both” pattern |
Why canonicalize each page to itself? Because Google needs to know each paginated URL exists and contains unique items. The 2017–2019 SEO advice was to canonicalize /blog/?page=2 to /blog/, which had two consequences:
- The articles on page 2 lost a direct crawl path (Googlebot still found them via individual links, but slower).
- Internal link equity from page 2 to its articles was suppressed because Google treated page 2 as a duplicate.
The current consensus from John Mueller (multiple Office Hours, 2020–2024) and confirmed in Google’s 2023 documentation update: paginated pages should self-canonicalize. No exceptions for normal pagination.
View-all is the historical recommendation when latency permits. If your category has 100 items and you can serve all 100 in under 3 seconds with reasonable LCP, a view-all page with paginated variants canonicalizing to it is technically the cleanest setup. In practice, most modern e-commerce categories are too large or too dynamic for view-all to be viable — pagination is the pragmatic answer.
Infinite scroll is the riskiest pattern because client-rendered content is invisible to Googlebot’s first-pass crawler and only inconsistently rendered in the second pass. The safe implementation:
- Render the first “page” of items in HTML at server time.
- As the user scrolls, fetch additional items via JavaScript.
- Each fetch updates the URL via
history.pushState()so the user can bookmark page 3. - A visible, crawlable
<a href="?page=2">link exists below the scroll for users without JavaScript and for Googlebot’s first pass.
Without step 4, the only thing in the index is page 1.
Load-more buttons are infinite scroll with explicit user intent. Same rule: the button must be a real <a href="?page=2"> styled as a button, with JavaScript hijacking the click to do the in-place fetch. If the user has JS disabled or Googlebot ignores the JS, the link still navigates to page 2.
<!-- Progressive-enhancement load-more -->
<a href="?page=2" class="btn-load-more" data-page="2">Load more</a>
<script>
document.querySelector(".btn-load-more").addEventListener("click", (e) => {
e.preventDefault();
const next = e.target.dataset.page;
fetch(`/api/items?page=${next}`)
.then((r) => r.json())
.then((items) => {
renderItems(items);
history.pushState({}, "", `?page=${next}`);
e.target.dataset.page = parseInt(next) + 1;
e.target.href = `?page=${parseInt(next) + 1}`;
});
});
</script>
Bing still honors rel="prev" and rel="next". ChatGPT Search runs on Bing’s index, so for AI search visibility there is a small ongoing benefit to keeping prev/next on existing implementations. Don’t add them solely for Google in 2026 — Google’s documentation explicitly states they are no longer used for indexing.
The crawl budget angle: a category with 50 pages of pagination, 5 sort orders, and 3 filter parameters generates 750 unique URLs per category. If you have 1,000 categories, that’s 750,000 paginated URLs Google may or may not crawl. Block sort and filter combinations from indexation; let pagination through. Sort/filter handling belongs in the faceted navigation playbook (Module 43).
Visualizing it
flowchart TD
A[Category: 200 items, 20 per page] --> B{Pagination strategy?}
B -->|Numbered pages| C["?page=1, 2, 3 ... 10"]
B -->|View-all| D[Single page with all 200]
B -->|Infinite scroll| E[Progressive HTML + JS fetch]
B -->|Load more| F[Visible link + JS hijack]
C --> G[Each page self-canonicalizes]
D --> H[Numbered variants canonical to view-all]
E --> I[Numbered fallback links visible to bot]
F --> J["a href=?page=2 underneath button"]
G --> K[All items reachable via HTTP only]
H --> K
I --> K
J --> K
K --> L[Full category indexed]
Bad vs. expert
The bad approach
<!-- /blog/?page=3 -->
<head>
<title>Blog</title>
<link rel="canonical" href="https://example.com/blog/" />
<link rel="prev" href="https://example.com/blog/?page=2" />
<link rel="next" href="https://example.com/blog/?page=4" />
<meta name="robots" content="noindex,follow" />
</head>
<body>
<h1>Blog</h1>
<div id="post-list"><!-- Loaded by React on mount --></div>
</body>
// Posts loaded via fetch() in useEffect
useEffect(() => {
fetch(`/api/posts?page=${page}`).then(setPostList);
}, [page]);
Three independent failures. The canonical points all paginated pages back to /blog/, so Google treats every page beyond 1 as duplicate and deindexes them. The noindex,follow directive is the well-known myth that “noindex pages still pass link equity” — Google has confirmed since 2019 that long-term noindex causes the page to be treated as noindex,nofollow regardless of what the directive says, so your post pages stop receiving link equity from these category pages. The post list is rendered entirely client-side with no server HTML, so Googlebot’s first crawl sees an empty <div> and the second-pass renderer is on a budget that frequently times out.
The expert approach
<!-- /blog/?page=3 -->
<head>
<title>Blog — Page 3</title>
<meta name="description" content="Articles 41–60 from the Acme blog. Latest writing on widgets, machining, and warehouse automation." />
<link rel="canonical" href="https://example.com/blog/?page=3" />
<!-- Optional: keep rel=prev/next for Bing/ChatGPT Search -->
<link rel="prev" href="https://example.com/blog/?page=2" />
<link rel="next" href="https://example.com/blog/?page=4" />
</head>
<body>
<h1>Blog — Page 3</h1>
<ol class="post-list">
<!-- 20 posts rendered server-side as crawlable HTML -->
<li><a href="/blog/post-41/">Post 41</a></li>
<li><a href="/blog/post-42/">Post 42</a></li>
<!-- ... -->
</ol>
<nav aria-label="Pagination">
<a href="/blog/?page=2" rel="prev">Previous</a>
<a href="/blog/?page=1">1</a>
<a href="/blog/?page=2">2</a>
<span aria-current="page">3</span>
<a href="/blog/?page=4">4</a>
<a href="/blog/?page=4" rel="next">Next</a>
</nav>
</body>
// For an infinite-scroll variant, render server-side first then enhance
// /pages/blog.astro
export async function getStaticPaths() {
const allPosts = await fetchAllPosts();
const PER_PAGE = 20;
return Array.from({ length: Math.ceil(allPosts.length / PER_PAGE) }, (_, i) => ({
params: { page: String(i + 1) },
props: { posts: allPosts.slice(i * PER_PAGE, (i + 1) * PER_PAGE) },
}));
}
Each paginated URL has its own unique title, description, and canonical pointing to itself. Posts render server-side as HTML, so Googlebot indexes them on the first pass. The pagination nav uses real <a> tags, so the bot can crawl page 4, 5, 6 directly. rel="prev"/rel="next" are present as a Bing/ChatGPT Search hedge but the strategy doesn’t depend on them. No noindex — these pages are valuable category snapshots that earn long-tail traffic for “page 3 of [topic]” queries.
Do this today
- Crawl your site with Screaming Frog, sort by URL, and filter for
?page=or/page/. The Indexability column shows whether each paginated URL is canonical, noindexed, or canonicalized away. Anything pointing back to page 1 is the first thing you fix. - In Google Search Console → Indexing → Pages, search the “Excluded → Alternate page with proper canonical tag” report for paginated URLs. Each one is a page that’s been collapsed into page 1 — usually unintentionally.
- Pick a strategy per template: numbered pages, view-all, infinite scroll with crawlable fallback, or load-more with
<a>underneath. Document it in your engineering wiki. - Update every paginated template’s
<link rel="canonical">to self-reference with the page parameter included. - Remove
<meta name="robots" content="noindex,follow">from paginated pages unless you specifically don’t want them indexed. The follow part is honored only short-term anyway. - Add unique titles and descriptions per paginated page: append ”— Page N” or similar so the SERP doesn’t show duplicate snippets.
- If you use infinite scroll or load-more, view your page in Chrome DevTools → Network → Disable JavaScript and reload. If you cannot reach page 2, neither can Googlebot’s first pass.
- Build a Pagination XML sitemap that lists
?page=2,?page=3, etc., for high-value categories. This guarantees Googlebot discovery even if your internal linking misses them. - Audit your
robots.txtfor accidental blocks likeDisallow: /*?page=. If pagination isDisallow-ed, none of the above matters. - Re-crawl in Sitebulb or Screaming Frog two weeks after the change and check the Internal → Pagination report. The “Discovered URLs” count for paginated pages should match the math:
total items / per-page count.
Mark complete
Toggle to remember this module as mastered. Saved to your browser only.
More in this part
Part 5: Technical SEO
- 026 Technical SEO Fundamentals 12m
- 027 Site Architecture 20m
- 028 Crawling & Indexing 17m
- 029 robots.txt Deep Dive 15m
- 030 XML Sitemaps 12m
- 031 Canonical Tags 20m
- 032 Meta Robots & X-Robots-Tag 13m
- 033 HTTP Status Codes 15m
- 034 Crawl Budget Management 16m
- 035 JavaScript SEO 26m
- 036 Core Web Vitals 17m
- 037 Site Speed & Performance 19m
- 038 HTTPS & Site Security 12m
- 039 Mobile SEO & Mobile-First Indexing 14m
- 040 Structured Data & Schema Markup 17m
- 041 International SEO (hreflang) 19m
- 042 Pagination You're here 12m
- 043 Faceted Navigation 26m
- 044 Duplicate Content 13m
- 045 Site Migrations 24m