Deciding Which Facet Combinations to Index
The allowlist is the single most consequential SEO decision in a catalogue, and it is usually made implicitly by whoever wrote the filter component.
When to Use This Approach
Work through these tests when:
- You are building the indexable allowlist for the first time and need a defensible basis for it.
- Facet pages exist but are reported as “Crawled - currently not indexed”, which usually means they failed the distinctness test.
- Merchandising wants a new filter combination promoted and you need criteria rather than an opinion.
Three Tests, All Required
A combination qualifies only if it passes all three. Passing two is the profile of a page that gets crawled, indexed briefly, and then dropped.
Implementation Steps
Step 1: Apply the demand test
Demand means people search for the combination as a phrase, not that the filter is popular in your UI. Those are different things and only the first predicts search traffic.
# Existing impressions are the strongest evidence — the page already competes
curl -s -X POST \
"https://searchconsole.googleapis.com/webmasters/v3/sites/$SITE/searchAnalytics/query" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"startDate":"2026-01-01","endDate":"2026-06-30",
"dimensions":["query"],"rowLimit":25000}' \
| jq -r '.rows[] | select(.impressions > 50) | [.keys[0], .impressions] | @tsv' \
> queries.tsv
# Match queries against facet value vocabulary
python3 scripts/match-facet-phrases.py queries.tsv > candidate-combinations.tsv
Validation: A combination with impressions already accruing has passed empirically. One with only keyword-tool volume is a hypothesis — worth testing on a handful of combinations before promoting hundreds.
Step 2: Apply the inventory test
// scripts/inventory-check.mjs
const MIN_RESULTS = 24; // one full page
const MIN_TROUGH = 16; // must hold through a seasonal dip
for (const combo of candidates) {
const counts = await catalog.countByMonth(combo, { months: 12 });
const min = Math.min(...counts);
const now = counts.at(-1);
const verdict = now >= MIN_RESULTS && min >= MIN_TROUGH ? 'pass' : 'fail';
console.log([combo.key, now, min, verdict].join('\t'));
}
SEO impact: Testing the twelve-month trough rather than today’s count prevents promoting a combination that will be a three-product page for half the year.
Validation: Anything failing on min but passing on now is a seasonal candidate — worth revisiting, not worth promoting.
Step 3: Apply the distinctness test
A combination whose results are almost the same as its parent’s produces a near-duplicate page, which is what “Crawled - currently not indexed” usually means on a catalogue.
// scripts/distinctness.mjs — Jaccard overlap between result sets
export async function distinctness(combo) {
const child = new Set(await catalog.ids(combo));
const parent = new Set(await catalog.ids(combo.parent));
const shared = [...child].filter((id) => parent.has(id)).length;
const union = new Set([...child, ...parent]).size;
return 1 - shared / union; // 0 = identical, 1 = disjoint
}
Validation: Require a distinctness above roughly 0.3 in practice. A brand filter on a large category usually clears it easily; a price-range filter usually does not, which is why price bands are rarely worth indexing.
Step 4: Promote survivors properly
Promotion is more than adding a key to a set — the page needs the attributes of a real page.
// lib/indexable-facets.js — the allowlist, loaded from the CMS
export async function loadAllowlist() {
const entries = await cms.facetPages.list({ where: { status: 'published' } });
return entries.map((e) => ({
key: e.combination, // e.g. 'category+brand'
path: e.path, // e.g. '/shoes/running/nike/'
title: e.title,
intro: e.introCopy, // distinct copy — not the category's
minResults: e.minResults ?? 24,
}));
}
// The promoted page self-canonicalises and carries its own copy
export async function generateMetadata({ params }) {
const entry = await allowlistEntry(params);
return {
title: entry.title,
alternates: { canonical: `https://example.com${entry.path}` },
robots: { index: true, follow: true },
};
}
SEO impact: Distinct copy is what converts a filtered view into a page worth indexing. Without it, the combination passes the distinctness test on inventory but fails it on content.
Validation: Confirm the page appears in the sitemap, self-canonicalises, and has introductory copy that does not appear on the parent category.
Step 5: Re-run the tests on a schedule
Inventory moves. A combination that qualified in March can be a thin page by November.
# Weekly: demote anything that has fallen below the floor
node scripts/inventory-check.mjs --allowlist-only \
| awk -F'\t' '$4 == "fail" { print $1 }' \
> demotion-candidates.txt
Validation: Demotion means removing the sitemap entry and canonicalising to the parent — not deleting the URL, which would strand any links pointing at it.
The Shape of a Healthy Allowlist
Across catalogues the distribution is remarkably consistent, and a list that does not look like this is usually wrong in a predictable direction.
SEO Impact Summary
The allowlist is not a fixed artefact: entries enter it when they pass all three tests and leave it when inventory falls, so it needs a review cycle rather than a launch decision.
| Signal | What improves | What breaks if misconfigured |
|---|---|---|
| Coverage quality | Indexed facet pages earn impressions instead of being crawled and discarded | Promoting thin combinations dilutes site-level quality signals |
| Canonical clarity | Each promoted page is distinct enough to hold its own canonical | Near-duplicate facet pages create disputes with their parent category |
| Merchandising velocity | A reviewable list lets merchandisers propose pages without a deploy | An unvalidated CMS field lets anyone publish a two-product page |
| Crawl efficiency | A small deliberate set of facet URLs consumes proportionate budget | A large allowlist recreates the explosion the allowlist exists to prevent |
Measurable signals to watch:
- Impressions per allowlisted facet page, with zero-impression pages reviewed for demotion.
- Coverage state distribution across the allowlist, where “Crawled - currently not indexed” points at a distinctness failure.
- Allowlist size over time, which should grow slowly and deliberately.
Edge Cases and Gotchas
Existing rankings override the demand test A combination already earning impressions has proved demand more convincingly than any keyword tool. Add those to the allowlist explicitly rather than re-deriving them, and never demote one because it failed a theoretical test.
Price and rating filters almost never qualify They rarely produce a distinct result set and are rarely searched as phrases. They are also unstable — the set changes whenever prices do. Treat them as views by default.
Multi-select facets are a different shape “Nike or Adidas” is not a combination in the sense used here; it is an ad-hoc set with no search demand behind it. Restrict the allowlist to single-value selections per facet.
Promoted pages need internal links A facet page in the sitemap with no internal link is discoverable but weakly signalled. Link the allowlist from the category page as a real anchor — those are exactly the links you kept when controlling facet URL explosion.
Frequently Asked Questions
How much inventory does a facet page need? Enough to fill a page of results and survive normal fluctuation — at minimum a full first page, with a buffer so a seasonal dip does not leave three products behind. A combination that only clears the bar at peak is a thin page for most of the year.
Can a facet page outrank the category it filters? Yes, and that is often correct: a specific query deserves a specific page. It becomes a problem only when the facet page starts ranking for the generic query the category should own, which is a sign the two are too similar.
Should the allowlist live in code or in the CMS? In the CMS, with validation. Merchandisers know which combinations have commercial value, and requiring a deploy for each change means the list stops being maintained. Validate every entry against the inventory and distinctness thresholds before it takes effect.
What happens to a demoted combination’s rankings? They transfer to the canonical target if the demotion is done with a canonical rather than a removal. Expect a transitional period where both are in flux; this is why demotion should be a scheduled review rather than a reaction to one bad week.
Part of: Faceted Navigation & URL Parameters
Related
- Controlling Facet URL Explosion in Headless Catalogs — what happens to everything that fails these tests
- Canonicalising Sorted and Filtered Listings — the canonical rules that back up a demotion
- Resolving Duplicate Content via Slug Standardization — the same near-duplicate problem seen from the URL side