How to implement pagination that search engines can crawl
Pagination splits a long list, such as blog posts, products or search results, across numbered pages so each page loads quickly and stays usable. It matters for SEO because the paginated pages are often the only route a crawler has to older posts and deeper products. Built badly, with links that only work in JavaScript, every page canonicalised to page one, a noindex on page two onwards or an infinite scroll with no addresses behind it, pagination hides most of a site from search. This guide explains what pagination is and how it works, then shows how to implement it with crawlable links, unique URLs and correct canonicals in plain HTML, WordPress, Shopify and Next.js, and how to keep the database side fast.
- What pagination is and how it works
- Pagination problems that hide pages from search
- How to add pagination in HTML
- Implementing it on the server, and does pagination improve performance?
- Pagination in WordPress, Shopify and Next.js
- A worked example: a recipe archive
- The fix, step by step
- Edge cases worth knowing
- Keeping pagination crawlable
- Questions and answers
What pagination is and how it works
Pagination means dividing a set of items into pages of a fixed size and giving the visitor controls to move between them: previous, next and numbered page links. In web development, each page is normally a separate request. The server works out which slice of items to show from a page number in the address, such as /blog/page/3/ or /shoes?page=3, and returns just those items with links to the other pages.
The arithmetic is simple. With 24 items per page, page 3 shows items 49 to 72:
per_page = 24
offset = (page - 1) * per_page # page 3 -> skip 48 items
pages = ceil(total_items / per_page) # 500 items -> 21 pages
For search engines, each page in the series is just another URL. Google crawls page 1, follows the link to page 2, and so on, discovering the posts or products linked from each one. That is why pagination is a crawling question, not only a design one: an item that is only linked from page 14 is found only if the crawler can reach page 14.
rel="next" and rel="prev" as an indexing signal some years ago. They do no harm and other systems may read them, but they do not replace normal links between the pages.Pagination problems that hide pages from search
| Problem | What happens | The fix |
|---|---|---|
| Page links are buttons or JavaScript only | Crawlers cannot follow them, so pages 2 onwards are never found | Use <a href> links to real URLs |
Page number in a fragment, such as #page=2 | Everything after # is ignored, so all pages are one URL | Put the page in the path or a query parameter |
| Every page canonicalised to page 1 | Google may drop pages 2 onwards and the items linked from them | Give each page a self-referencing canonical |
noindex on pages 2 onwards | Over time those pages are crawled less and their links followed less | Leave paginated pages indexable |
| Infinite scroll with no URLs | Only the first batch exists for crawlers | Back the scroll with paginated URLs |
| Pages beyond the last return 200 | Endless empty pages, a crawler trap | Return 404 for out-of-range pages |
| Sort and filter parameters on every page | Thousands of combinations of the same list | Canonical to the unfiltered page, limit crawlable combinations |
The last two rows overlap with wider crawl issues. See crawler traps and faceted navigation for those.
How to add pagination in HTML
Whatever builds the page, the HTML a crawler receives for page 3 should look like this:
<head>
<title>Sourdough Recipes, Page 3 | Example Bakery</title>
<link rel="canonical" href="https://example.com/recipes/?page=3">
</head>
...
<nav aria-label="Pagination">
<a href="/recipes/?page=2" rel="prev">Previous</a>
<a href="/recipes/">1</a>
<a href="/recipes/?page=2">2</a>
<a href="/recipes/?page=3" aria-current="page">3</a>
<a href="/recipes/?page=4">4</a>
<a href="/recipes/?page=21">21</a>
<a href="/recipes/?page=4" rel="next">Next</a>
</nav>
- Every page link is a plain anchor with a real
href. You can still enhance it with JavaScript, but the link must work without it. - The canonical points at the page itself, including its page number. It does not point at page 1.
- Page 1 lives at the clean address,
/recipes/. Redirect/recipes/?page=1to it with a 301 so there are not two versions. aria-current="page"tells screen readers which page is current, and thenavlabel makes the control easy to find.- Linking to the last page and a few around the current one keeps every page within a few clicks of page 1.
Use a self-referencing canonical on each page as described in how to fix canonicals, and make sure pages beyond the last return a real 404 status.
Implementing it on the server, and does pagination improve performance?
Yes, pagination improves performance for both the visitor and the server. A page of 24 products sends a fraction of the HTML and images of a page with 500, and the database returns a small slice. The usual query uses LIMIT and OFFSET:
SELECT id, title, slug, published_at
FROM posts
WHERE status = 'published'
ORDER BY published_at DESC, id DESC
LIMIT 24 OFFSET 48; -- page 3
Offsets are fine for a few hundred pages. On very large tables they slow down as the page number rises, because the database still reads and discards every skipped row. For deep lists and APIs, keyset pagination, also called cursor pagination, starts from the last item seen instead:
SELECT id, title, slug, published_at
FROM posts
WHERE status = 'published'
AND (published_at, id) < ('2026-03-14 09:00:00', 4812)
ORDER BY published_at DESC, id DESC
LIMIT 24;
Keep an index on the sort columns, here published_at and id. Always include a unique column in the sort order so items with the same date do not jump between pages. For public, crawlable listings, keep numbered page URLs even if you use keyset queries behind them; cursor tokens in URLs make poor, unstable addresses.
Return the right status codes
curl -sI "https://example.com/recipes/?page=3" | head -n 1 # expect 200
curl -sI "https://example.com/recipes/?page=999" | head -n 1 # expect 404
curl -sI "https://example.com/recipes/?page=1" | grep -i '^location' # expect /recipes/
Pagination in WordPress, Shopify and Next.js
- WordPress. Archives paginate at
/page/2/by default, with the number of posts per page set under Settings, Reading. Themes output the links withthe_posts_pagination()orpaginate_links(), which produce real anchors. Check that your SEO plugin gives each page a self-referencing canonical, and avoid load more buttons that replace those links. The WooCommerce crawl guide covers shop archives. - Shopify. Collections paginate with the Liquid
paginatetag and the?page=parameter. Usedefault_paginationor build links from thepaginateobject, and check the theme's canonical includes the page number. See the Shopify crawl guide. - Next.js. Read the page number from
searchParamsin a Server Component, fetch that slice on the server, rendernext/linkanchors to the other pages, and callnotFound()when the page is out of range. Set the canonical ingenerateMetadatato include the page parameter. - Category pages generally. Put a short introduction on page 1 only, so the later pages do not repeat it. The category pages guide covers the rest.
Infinite scroll and load more buttons
Infinite scroll is fine for visitors if it sits on top of normal pagination. Each batch should correspond to a real URL such as ?page=4, the page should update the address with history.pushState as the visitor scrolls into a new batch, and the paginated links should remain in the HTML, even if visually tucked below the list. A crawler then sees normal paged URLs while visitors scroll.
A worked example: a recipe archive
Example Bakery's recipe section had 500 recipes, 24 per page, across 21 pages. A crawl found only 62 recipes reachable from internal links, and Search Console listed most of the rest as Discovered, currently not indexed or did not know about them at all.
The page links were <button> elements that loaded the next batch with JavaScript, every page had a canonical pointing to /recipes/, and pages 2 onwards carried noindex, follow. Requesting ?page=999 returned an empty page with status 200.
The fix replaced the buttons with anchors to ?page=N, kept the JavaScript as an enhancement, changed each canonical to the page's own address, removed the noindex, and returned 404 for out-of-range pages. A recrawl reached all 500 recipes within three clicks of the recipe index, and over the following weeks the number of indexed recipes rose steadily.
The fix, step by step
Step 1
Map your paginated listings
List every template that paginates, such as blog archives, categories, collections, tag pages and internal search results. For each, note the URL pattern for page 2, the number of items per page and the total number of pages. Then pick one deep item from the last page of each listing. This map tells you which templates to fix and gives you test items to check later, because reaching those deep items is the whole point of crawlable pagination.
Step 2
Give every page a real URL
Make sure each page in a series has its own address, using a path such as /page/3/ or a query parameter such as ?page=3, never a fragment after a hash. Redirect ?page=1 to the clean listing address with a 301. Confirm by opening page 3 directly in a private browser window: it should load the third slice of items without needing you to visit pages 1 and 2 first.
Step 3
Use crawlable anchor links
Replace any page controls built from buttons, onclick handlers or scripts with plain <a href> links to the real page URLs. Include previous and next links, the first and last page, and a few page numbers around the current one. JavaScript can still improve the behaviour on top. Confirm by fetching page 2 with curl and finding anchors with href values for pages 1 and 3 in the raw HTML.
Step 4
Set self-referencing canonicals
Change the canonical tag on each paginated page so it points at that page's own URL, including the page number, instead of page 1. Keep the canonical free of sort and tracking parameters. In WordPress and Shopify this usually comes from the SEO plugin or theme template. Confirm by fetching pages 2 and 3 with curl and checking that each canonical matches the address you requested.
Step 5
Remove noindex from pages 2 onwards
Check the robots meta tag and the X-Robots-Tag header on pages 2 and later, and remove any noindex added by a plugin or template. Paginated pages should be indexable so Google keeps crawling them and following their links to items. Confirm with URL Inspection in Search Console on page 2, using Test live URL, which should report that the page can be indexed.
Step 6
Return 404 beyond the last page
Add a check that compares the requested page number with the total number of pages, and return a 404 status when it is too high, zero, negative or not a number. Do not show an empty listing with status 200. Confirm by requesting ?page=999 with curl -I and checking the first line shows 404, and that a valid last page still returns 200 with items on it.
Step 7
Back infinite scroll with pages
If the listing uses infinite scroll or a load more button, tie each batch to a page URL, update the address with history.pushState as each batch appears, and keep the numbered pagination links in the HTML. Load the right batch when a page URL is opened directly. Confirm by opening ?page=4 in a fresh tab and checking that it shows the fourth batch, then fetching it with curl.
Step 8
Keep the queries fast
Check the query behind each listing. Add an index on the sort columns, include a unique column such as id in the sort order so items do not move between pages, and consider keyset pagination for very deep lists. Measure the response time of page 1 and the last page. Confirm the last page responds about as quickly as the first, and well within one second on your server.
Step 9
Crawl and confirm deep items
Run a crawl of the site starting from the homepage and check that every page in each series is found and that the deep test items you noted in step one are reached. Check how many clicks they are from the homepage. Then submit the sitemap and use URL Inspection on a few deep items. Rising indexed counts over the following weeks show the pagination is now working.
Edge cases worth knowing
- View all pages. A single page listing every item can work for a few dozen items, and you can point paginated pages' canonicals at it if it loads quickly. For hundreds of items it becomes slow, so stay with pagination.
- Changing items per page. Moving from 24 to 48 per page shifts every item to a new page number. That is harmless for items, which keep their own URLs, but out-of-range pages should then return 404 rather than errors.
- Duplicate titles. Paginated pages sharing one title are normal and rarely a problem, but adding the page number keeps crawl reports and tabs readable.
- Sorted views. If
?sort=pricecreates a second full set of pages, canonicalise sorted views to the default order and keep sort links out of the main crawl path. See crawl budget. - Items reachable only by pagination. Deep pages are crawled less often. Link important items from category hubs, related item blocks and the sitemap too, as covered in orphan pages.
Keeping pagination crawlable
Pagination tends to break during redesigns, when a new theme or front-end component swaps links for a load more button. Protect it with a few checks:
- After any template change, fetch page 2 of a listing with curl and confirm the pagination links are anchors with
hrefvalues and the canonical points at page 2. - Test a page number past the end and confirm it returns 404.
- Crawl the site every few months and compare the number of items found with the number published. A gap usually means a break in pagination or in internal links.
- Keep every item in the XML sitemap, so discovery never depends on pagination alone.
1. What is pagination on a website?
Pagination is the practice of splitting a long list of content, such as blog posts, products or search results, across several numbered pages, with links to move between them. Each page shows a fixed number of items and has its own URL, for example page 2 of a blog archive. It keeps pages fast to load and easy to use, and for search engines it provides the links that lead to older posts and deeper products that are not linked from anywhere else.
2. How does pagination work?
Pagination works by reading a page number from the URL, calculating which slice of items belongs on that page, and returning only those items with links to the other pages. With 24 items per page, page 3 skips the first 48 items and shows the next 24. The server or framework usually does this with a database query using a limit and an offset, or a cursor, and it calculates the total number of pages from the item count.
3. Does pagination improve performance?
Yes, pagination usually improves performance, because each page sends less HTML, fewer images and less data than one long list, and the database returns a small slice instead of every row. Visitors see the first items sooner and phones do less work. The main catch is that offset queries slow down on very deep pages of very large tables, because skipped rows are still read. Indexes on the sort columns and keyset pagination solve that for large catalogues and APIs.
4. Should paginated pages have a canonical to page 1?
No, each paginated page should have a canonical pointing at itself, including its page number. Pointing pages 2 onwards at page 1 tells Google they are copies of page 1, which they are not, because they list different items. Google may then drop them and crawl the items they link to less often. The only exception is when a fast view all page exists, in which case the paginated pages can point their canonicals at that view all page.
5. Should I noindex paginated pages?
No, in most cases you should leave paginated pages indexable. A noindex does not stop Google crawling a page at first, but pages left on noindex for a long time tend to be crawled less often and their links followed less, which is the opposite of what pagination is for. Paginated pages rarely compete with your main pages in search results. If they appear for searches where you want page 1 to rank, improve page 1 rather than hiding the rest.
6. Is infinite scroll bad for SEO?
Infinite scroll is not bad for SEO if it is backed by real paginated URLs, but on its own it usually is. Crawlers do not scroll, so content loaded only when a visitor reaches the bottom of the page is often never seen. The fix is to give each batch its own URL, update the address as the visitor scrolls, load the right batch when that URL is opened directly, and keep normal pagination links in the HTML for crawlers and visitors without JavaScript.
7. How many items should each paginated page show?
There is no single correct number, but between 20 and 50 items per page suits most blogs and shops, and 24 or 25 is a common choice. Fewer items create more pages and push older content deeper, while very long pages load slowly on phones. Choose a number that keeps the page fast and the series reasonably short, then keep it stable. Changing it later moves every item to a different page number, so out-of-range pages must then return 404.
The Site Crawler follows your pagination links the way a search engine does and reports pages it cannot reach, canonicals that point to page one, noindex tags and out-of-range pages that return 200.
Open Site Crawler Fixes โ