/** * @fileoverview THE route-exclusion denylist. One list, one place. * * Before this file the repo carried three partial, disagreeing copies of the * same idea: * * - `crawlRoutes.ts` dropped `:param`, `*`, `/logout` and `/auth/*` * - `route-collector.ts` dropped `*` and the single literal `/auth/callback` * - and **nothing anywhere encoded `/admin`** or the commerce/account * segments, despite several drafts claiming it did * * A route that reaches this predicate as `true` is one we must never * prerender, never advertise in `sitemap.xml`, and never crawl in a verifier * pass. Three different reasons converge on the same list: * * 1. **Privacy.** `/cart`, `/checkout`, `/account`, `/orders` and their * Spanish equivalents render owner-scoped data. Baking one into a * world-readable GCS object leaks it with no retraction path. * 2. **Side effects.** `/logout` tears down the visiting session and * `/auth/*` is an OIDC round-trip; both are redirect endpoints that never * settle, so crawling them always false-flags a timeout. * 3. **Noise.** `/admin` is the CMS console — a 151-file tree that renders * nothing a search engine should see. It still gets a `noindex,nofollow` * stub document rather than a `Disallow`, deliberately: a blocked URL is * one whose `noindex` a crawler can never read, so it stays indexed * forever. `/spa-shell` joins it: a build artifact that happens to be a * real route, rendering a spinner and nothing else. * * ⚠️ **Segment matching, never prefix matching.** `/administracion` is a * perfectly ordinary Spanish page on a fleet that is Mexico-first, and a * `startsWith('/admin')` test would silently deindex it. Every rule below * compares whole path segments. */ /** * Excluded when they are the FIRST segment of a route. Scoped to the first * segment because these name whole subtrees (`/admin/**`, `/auth/**`) rather * than a word that might legitimately appear deeper in a content path. */ export declare const ROUTE_EXCLUSION_FIRST_SEGMENTS: readonly string[]; /** * Excluded wherever they appear as a whole segment. These are the * owner-scoped commerce/account surfaces, in both languages the fleet ships. * Whole-segment matching means `/blog/mi-cuenta-de-ahorros` (a slug that * merely CONTAINS `mi-cuenta`) is not excluded, while `/tienda/carrito` is. */ export declare const ROUTE_EXCLUSION_SEGMENTS: readonly string[]; /** * Excluded as exact whole paths. * * `/spa-shell` is the TanStack Start `spa.maskPath` target — a route that * renders a loading spinner and nothing else, whose only purpose is to be * prerendered into `_shell.html` and served as the SPA fallback. It IS a real * route (maskPath must point at one), so the enumerator finds it; publishing * it as a page would advertise a permanently blank URL. * * `/pos` is the POINT OF SALE — the counter till (`src/routes/pos.tsx`, * template-owned, root-level, `ssr: false`, `robots: noindex,nofollow`, * `` inside). Its own docblock says *"nothing to * prerender, nothing a crawler should see"*, and on an app with no counter its * `beforeLoad` throws `notFound()` BY DESIGN. It is on this list for the same * three reasons `/admin` is: it is private (a staff surface, never a page), it * has nothing a search engine should index, and every verifier crawl of it * measures either a designed 404 or a lazy chunk racing a 10 s render ceiling. * * MEASURED (prod build review 2026-09-09, the night after the till shipped in * `338167da0`): 4 of the 8 builds in the window carried `/pos` as a routeCrawl * fault — a brochure site's DESIGNED `notFound()` reported as "Página no * encontrada" (`1547073069668601857`), a pizzeria's till caught painting the * router's pending component at the crawl's ceiling (`1547096200383012865`, * `stuck_loading: "Cargando…"`), a store's till mid-error-boundary * (`1547007521496870913`), and one whole crawl ruled UNMEASURED because `/pos` * had not mounted (`1547041698565545985`). Visited live, anonymous, on the * pizzeria's running pod: the route renders the staff gate's sign-in panel * cleanly, with zero console output — the product was right and the * instrument was measuring a route it was never meant to open. The same * predicate feeds `sitemap.xml` and the prerender's publishable set, so this * also stops a private till URL being advertised in every selling app's sitemap. */ export declare const ROUTE_EXCLUSION_PATHS: readonly string[]; /** * Normalise a route path or link target: strip any query string and fragment, * collapse repeated slashes, and drop a trailing slash (except the root). * * Deliberately does **not** change case — a published URL path is * case-sensitive and `/Blog` is not `/blog`. Case folding happens only inside * {@link isExcludedRoute}, where it is a matching concern rather than an * identity one. * * Exported because every consumer of {@link isExcludedRoute} needs to compare * paths the same way; a second normaliser is how the three lists this file * replaces drifted apart in the first place. */ export declare function normalizeRoutePath(routePath: string): string; /** Whole path segments, with empty segments removed. Case preserved. */ export declare function routeSegments(routePath: string): string[]; /** * True when this route must never be prerendered, listed in `sitemap.xml`, or * visited by a verifier crawl. * * Splat routes are excluded too: a splat is a catch-all, so there is no single * URL it names and nothing to emit for it. Both routers' spellings count — * react-router's `*` (`/docs/*`) and TanStack's bare `$` segment (`/docs/$`, * from `docs.$.tsx`). A `$slug` segment is a PARAMETER, not a splat, and is * deliberately not excluded: the publish path expands it against the CMS slug * index into concrete pages. */ export declare function isExcludedRoute(routePath: string): boolean; //# sourceMappingURL=routeExclusions.d.ts.map