Empty archives and duplicate filters can create thousands of URLs without giving visitors useful places to land. Before removing them, check which are actually indexed and which only appear in a crawl. Those are different problems.
Compare Search Console URL examples with a site crawl, the sitemap, and server logs. Group the URLs by purpose, then review a few pages from each group. A tag or category URL is not automatically a bad page.
Group URLs before judging them
Start with these common patterns. Each is a review candidate, not a reason to remove the entire group:
- Tag archives: many similar collections with little useful distinction.
- Author archives: useful author profiles mixed with empty or duplicate archives.
- Date archives: monthly pages such as
/2026/07/that may add nothing beyond the main archive. - Empty categories: pages with no products, posts, or useful explanation.
- Pagination beyond the end: URLs that return an empty listing instead of a missing-page response.
- Filtered listings: combinations such as
/products?color=red&size=m&brand=x.
A Rust program reading your sitemap can flag these patterns by URL structure:
use regex::Regex;
fn classify_url(url: &str) -> &str {
let tag_pattern = Regex::new(r"/tag/").unwrap();
let author_pattern = Regex::new(r"/author/").unwrap();
let date_pattern = Regex::new(r"/\d{4}/\d{2}/").unwrap();
let page_pattern = Regex::new(r"/page/\d+").unwrap();
let filter_pattern = Regex::new(r"\?.*=").unwrap();
if tag_pattern.is_match(url) { "tag" }
else if author_pattern.is_match(url) { "author" }
else if date_pattern.is_match(url) { "date-archive" }
else if page_pattern.is_match(url) { "pagination" }
else if filter_pattern.is_match(url) { "filter" }
else { "other" }
}
Run this against URL samples exported from Search Console, crawl data, sitemaps, and server logs. A URL pattern tells you where to look. It does not tell you whether the page deserves to be indexed.
Count the patterns in your export
In Python, classify a URL list exported from your monitoring tools:
import re
def classify_patterns(urls):
patterns = {
'tag': re.compile(r'/tag/'),
'author': re.compile(r'/author/'),
'date': re.compile(r'/\d{4}/\d{2}/'),
'page': re.compile(r'/page/\d+'),
}
counts = {name: 0 for name in patterns}
counts['other'] = 0
for url in urls:
matched = False
for name, pattern in patterns.items():
if pattern.search(url):
counts[name] += 1
matched = True
break
if not matched:
counts['other'] += 1
return counts
Choose a control per pattern
| Pattern state | Appropriate control |
|---|---|
| Useful, distinct landing page | Keep 200, self-canonical, and internally linked |
| Duplicate but still needed for users | Canonicalise only to a genuinely equivalent page |
| Valid page that should not appear in Search | Keep crawlable and return a consistent noindex directive |
| Unbounded filter or internal-search crawl space | Stop generating links and consider a tested robots rule |
| Resource does not exist | Return 404 or 410 |
Do not combine robots blocking with noindex and expect Google to read the directive. Do not redirect unrelated empty pages to a category root.
Reducing bloat on WordPress
WordPress can expose tag, author, date, search, attachment, and pagination routes depending on its theme and plugins. If an archive has no distinct purpose or useful landing content, it can be noindexed while remaining available to users. Choose one place to manage the directive, such as your SEO plugin. The following core WordPress example applies to all tag, author, and date archives, so use it only if that matches your reviewed policy and does not conflict with your plugin:
add_filter('wp_robots', function (array $robots): array {
if (is_tag() || is_author() || is_date()) {
unset($robots['index']);
$robots['noindex'] = true;
}
return $robots;
});
For an application route, set the directive in the route's metadata or middleware. An HTTP header works for HTML and non-HTML responses:
X-Robots-Tag: noindex
For Laravel, apply a middleware group to your route definitions:
Route::middleware(['crawl.noindex'])->group(function () {
Route::get('/tag/{slug}', [TagController::class, 'show']);
Route::get('/author/{username}', [AuthorController::class, 'show']);
Route::get('/archive/{year}/{month}', [ArchiveController::class, 'show']);
});
The middleware adds the appropriate header:
public function handle(Request $request, Closure $next): mixed
{
$response = $next($request);
$response->header('X-Robots-Tag', 'noindex');
return $response;
}
When not to noindex
Not all non-content URLs should be noindexed. Category pages with unique curated content, author pages with original editorial introductions, and date archives that serve as hubs for event-based content may all be worth keeping indexed. Keep pages that answer a useful search need. Being generated by a CMS is not itself a reason to noindex a page.
Repeat the comparison after changes to categories, filters, routing, or the CMS. Investigate new URL patterns when they grow unexpectedly or begin appearing in Search Console. There is no universal ratio of archives to articles that determines whether a site has index bloat.
