SEO-Safe Pagination and Tag Pages
Nobody writes a blog's archive pages, and that is the problem. A static generator happily creates /page/2/ through /page/62/, a page per tag, per author, per year and per month — hundreds of URLs whose content is a list of other pages' summaries. Some are genuinely useful for readers and crawlers. Many are thin, near-duplicate pages that dilute crawl attention and show up in Search Console as "Crawled – currently not indexed" by the hundred.
This guide sets up archives on Eleventy and Hugo that help discovery without generating thin pages: crawlable pagination with self-referencing canonicals, a minimum size for tag pages, no automatic date archives, and a report that watches the count. It is part of Technical SEO for Static Sites. The numbers are from an Eleventy blog with 1,240 posts and 380 tags.
Prerequisites
- A blog or changelog with a paginated index and tag or category archives.
- Search Console access, to count thin and excluded pages before and after.
- A crawl tool, or a script over the built output, to count archive URLs.
Where Thin Pages Come From
On the blog, the built output contained 1,240 posts and 1,183 archive pages:
| Archive type | Pages | Typical content |
|---|---|---|
Main pagination (/page/N/) | 124 | 10 posts each |
| Tag pages | 380 tags → 612 pages with pagination | 211 tags had 1–2 posts |
| Year archives | 12 | useful |
| Month archives | 139 | 1–15 posts |
| Author pages | 316 | 283 authors with one post |
Search Console listed 418 of the archive URLs as indexed-but-low-value or excluded as "Crawled – currently not indexed". The single-post tags and single-post authors were the bulk of them: each was a page containing one post summary that already existed on the post itself and on the main index.
Rule 1: Pagination Uses Plain Links and Self Canonicals
Each paginated page must link to the next with an ordinary <a href>. A "Load more" button that fetches JSON is fine as an enhancement, but only on top of real links. Each page canonicalizes to itself — page 7 is not a duplicate of page 1. Titles should include the page number so they are unique.
{# Eleventy: posts index with pagination #}
---
pagination:
data: collections.posts
size: 20
reverse: true
permalink: "/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
eleventyComputed:
title: "Articles{% if pagination.pageNumber > 0 %} – page {{ pagination.pageNumber + 1 }}{% endif %}"
---
<nav aria-label="Pagination">
{% if pagination.href.previous %}<a href="{{ pagination.href.previous }}" rel="prev">Newer</a>{% endif %}
{% if pagination.href.next %}<a href="{{ pagination.href.next }}" rel="next">Older</a>{% endif %}
</nav>
Raising the page size from 10 to 20 halved the main pagination from 124 pages to 62, and each page became more useful. Also link to a few pages ahead (1 2 3 … 62) rather than only "next", so deep pages are a few hops from the index instead of sixty.
Rule 2: Tag Pages Need a Minimum Size
Generate a tag page only when a tag has enough posts to be a useful collection. Five worked well. Smaller tags stay visible on posts but link somewhere useful rather than to a thin page — the site search filtered by tag, or a combined "all topics" page.
// eleventy.config.js
eleventyConfig.addCollection('tagPages', (api) => {
const counts = new Map();
for (const post of api.getFilteredByTag('posts')) {
for (const tag of post.data.tags.filter((t) => t !== 'posts')) {
counts.set(tag, (counts.get(tag) ?? 0) + 1);
}
}
return [...counts].filter(([, n]) => n >= 5).map(([tag]) => tag).sort();
});
eleventyConfig.addFilter('tagUrl', (tag, tagPages) =>
tagPages.includes(tag) ? `/tags/${slugify(tag)}/` : `/search/?tag=${encodeURIComponent(tag)}`);
In Hugo, taxonomy term pages are always generated, but a term template can set noindex and omit itself from the sitemap when len .Pages is below the threshold. That keeps the URL alive for existing links while stopping it from counting as a thin indexed page:
{{ if lt (len .Pages) 5 }}<meta name="robots" content="noindex, follow">{{ end }}
noindex, follow lets crawlers still follow links from the page to the posts.
Rule 3: Give Tag Pages Something of Their Own
A tag page that clears the threshold is more valuable with a short introduction: two or three sentences written for the tag, plus links to the best posts first rather than strictly by date. On the blog, the 40 largest tags got hand-written introductions stored in a data file; those 40 pages went from "Crawled – currently not indexed" to indexed and began receiving search traffic for topic queries such as "css container queries articles". The rest remained plain lists, which is fine.
Rule 4: Drop Month Archives, Keep Years
Month archives were the least visited pages on the blog — 0.02% of page views — and the most numerous after tags. Year archives were visited, linked from the footer, and short enough to list every post of the year with titles, which made them ideal crawl paths. Removing month archives and redirecting their URLs to the year page (/2023/04/ → /2023/#april) removed 139 pages with no measurable loss. Author pages got the same treatment as tags: only authors with five or more posts have a page.
Documentation Sites Have Archives Too
Pagination and tag pages sound like blog problems, but documentation generators produce the same pattern under different names. Docusaurus and many Hugo docs themes generate tag pages for every frontmatter tag; changelogs paginate; API references generate an index page per namespace, some containing a single type; versioned docs multiply every archive by the number of versions kept online.
The same rules apply. A namespace index with one entry should link to that entry rather than exist as its own page, or be noindex, follow. Changelog pagination should use large pages — fifty entries — and be complemented by per-version or per-year pages that are genuinely useful to readers looking for "what changed in 4.2". Versioned documentation should keep archive and tag pages only for the current and previous major versions; older versions can keep their content pages indexable while their generated archives are dropped. On the 3,900-page documentation site, applying the tag threshold and trimming generated index pages for versions older than two majors removed 540 URLs from the build, and the section sitemaps showed no change in indexed content pages — the removed pages had never been doing any work.
Keeping It That Way
Add the archive count to the build summary and fail the build if it grows faster than the post count — a new plugin or template change that suddenly generates hundreds of pages is caught on the pull request. The sitemap check from Generating Sitemaps for Large Static Sites should exclude noindex tag pages automatically, since it compares the sitemap with indexable pages only.
Measured Impact
| Measure | Before | After (8 weeks) |
|---|---|---|
| Archive pages generated | 1,183 | 214 |
| Thin archive URLs in Search Console | 418 | 37 |
| Maximum click depth to any post | 124 | 3 |
| Posts not indexed | 96 | 22 |
Build time (Eleventy, hyperfine, 5 runs) | 18.4 s | 14.1 s |
The drop in unindexed posts is the result that matters: with archives pointing crawlers at posts more efficiently, 74 posts that had never been indexed were picked up.
Pitfalls & Rollback
- Canonicalizing pagination to page one. Hides posts listed on later pages.
- Removing archive URLs without redirects. They may have external links; redirect to the nearest useful page.
noindex, nofollowon tag pages.nofollowstops link discovery; usenoindex, follow.- JavaScript-only "load more". Keep real paginated URLs underneath.
- Rollback: all rules are template and config changes. Restoring the templates regenerates the archives; the redirects can stay, since they point at pages that still exist.
Conclusion
Archive pages should earn their URLs. Keep pagination crawlable with plain links, numbered navigation and self canonicals; generate tag and author pages only above a minimum size and give the largest an introduction; prefer year archives over month archives. On the blog that cut archive pages by 82%, brought every post within three clicks of the home page, and got 74 previously ignored posts indexed.
FAQ
Should paginated pages canonicalize to page one?
No. Page two of an archive lists different posts from page one, so it is not a duplicate. Each paginated page should carry a self-referencing canonical. Pointing all of them at page one can stop search engines from discovering the posts listed on later pages.
Are rel next and rel prev still needed?
Google stopped using them as an indexing signal in 2019, though they are harmless and some other consumers read them. What matters is that each paginated page links to the next with a plain anchor element that crawlers can follow.
How many posts should a tag have before it gets its own page?
A threshold of around five posts worked well on the blog measured here. Below that, a tag page mostly repeats the summaries of one or two posts. Keep showing small tags on posts, but link them to search results or a combined page instead of generating a thin archive.
Does infinite scroll hurt SEO on static sites?
Only if it replaces links. Infinite scroll that progressively enhances a normal paginated archive, where each page also exists at its own URL with plain next links, is fine. Scroll-only loading with no URLs hides everything beyond the first batch from crawlers.
Related
- Parent: Technical SEO for Static Sites — build-time SEO in full.
- Canonical URLs and Duplicate Content on Static Sites — self canonicals everywhere.
- Generating Sitemaps for Large Static Sites — keeping noindex archives out of the sitemap.
- Speeding Up Slow Jekyll Builds — archive plugins are a common cause.
- Search Index Size Budgets for Large Docs — excluding archive pages from search.