Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 1x 1x 17x 15x 5x 4x | // Bot facet-crawl guard for the faceted listing pages (/projects, /blog).
//
// AI crawlers (CF bot score 99, rotating ASNs that ignore robots.txt) walk
// *unbounded unique* filter permutations. Each unique URL is a cache miss that
// runs a D1-heavy list query (correlated EXISTS subqueries, 1-4s); in bursts
// they saturate D1 and 5xx the API. Real users rarely combine more than a few
// filters, and these pages are `noindex` whenever any filter param is present
// (computeCanonical Rule 2), so short-circuiting degenerate combos costs no SEO
// and (almost) no legitimate UX.
//
// Heuristic — short-circuit (skip the API call, render the empty "refine your
// filters" state) when EITHER:
// - any single facet has more than MAX_VALUES_PER_FACET values — the clearest
// bot tell (no human selects 4+ rooms, or 7 blog categories); or
// - the combined value count exceeds MAX_VALUES_COMBINED.
// These are tuned to spare realistic filtering (≤3 per facet, ≤5 total) while
// catching the degenerate permutations; the edge WAF rate-limit bounds the rest.
export const MAX_VALUES_PER_FACET = 3;
export const MAX_VALUES_COMBINED = 5;
/**
* Decide whether a faceted request looks like a crawler permutation and should
* skip the (expensive) API list call.
* @param facetGroups one array of selected values per facet (e.g. rooms,
* propertyTypes, budgetRanges, or [category…]). Pass `[search]` as its own
* group if a search term should count toward the combined total.
*/
export function shouldShortCircuitFacets(facetGroups: string[][]): boolean {
if (facetGroups.some((g) => g.length > MAX_VALUES_PER_FACET)) return true;
const total = facetGroups.reduce((sum, g) => sum + g.length, 0);
return total > MAX_VALUES_COMBINED;
}
/**
* Sort + dedupe a facet's values so permutations that differ only by order
* (`a,b` vs `b,a`) collapse onto a single API/cache key, improving L2 hit rate.
*/
export function canonicalizeFacet(values: string[]): string[] {
return [...new Set(values)].sort();
}
|