Problem
Because route patterns end in /?$ (router.js buildRoutePattern:237), /about and /about/ both serve a 200 with identical content and there is no canonical redirect to one form. This is duplicate-content: search engines index both URLs, link-equity splits, analytics double-counts, and CDN cache keys split on the slash, halving hit rate. There is no config to pick a policy and no redirect to enforce it.
Design / approach
In handle(), before matchPage, normalize the native URL pathname per a webjs.trailingSlash setting and return a literal 308 to the canonical form (never the root). Pure URL string handling and a standard redirect.
Web-standards fit: Operates on the native URL object and emits a standard 308; the policy is a tiny string check, no router rewrite engine.
Prior art: Next.js trailingSlash:false defaults and 308-redirects; SvelteKit trailingSlash route option; Rails strips by convention.
Acceptance criteria
Filed from the production-readiness audit (webjs vs Next.js / Remix / Rails / Turbo / Lit). Theme: routing. Priority: P1. Kept to webjs identity: no-build, progressive enhancement, web-components-first, AI-first, batteries-included, close to web standards.
Problem
Because route patterns end in /?$ (router.js buildRoutePattern:237), /about and /about/ both serve a 200 with identical content and there is no canonical redirect to one form. This is duplicate-content: search engines index both URLs, link-equity splits, analytics double-counts, and CDN cache keys split on the slash, halving hit rate. There is no config to pick a policy and no redirect to enforce it.
Design / approach
In handle(), before matchPage, normalize the native URL pathname per a webjs.trailingSlash setting and return a literal 308 to the canonical form (never the root). Pure URL string handling and a standard redirect.
Web-standards fit: Operates on the native URL object and emits a standard 308; the policy is a tiny string check, no router rewrite engine.
Prior art: Next.js trailingSlash:false defaults and 308-redirects; SvelteKit trailingSlash route option; Rails strips by convention.
Acceptance criteria
Filed from the production-readiness audit (webjs vs Next.js / Remix / Rails / Turbo / Lit). Theme: routing. Priority: P1. Kept to webjs identity: no-build, progressive enhancement, web-components-first, AI-first, batteries-included, close to web standards.