| name | url-canonicalization |
| description | Enforce URL rules to prevent SEO issues and cost incidents. CRITICAL - never add searchParams to listing pages ($300 incident). |
URL Canonicalization Skill
Purpose
Enforce URL canonicalization rules to prevent SEO issues and CRITICAL cost incidents from dynamic page rendering.
⚠️ CRITICAL: Cost Prevention Rule
NEVER add searchParams to page components in app/[place]/ routes.
Reading searchParams in a page component makes the page dynamic, creating a separate cache entry for every unique URL+query combination.
Real Incident: This caused a $300+ cost spike on December 28, 2025.
What NOT To Do
export default async function PlacePage({
params,
searchParams, // ← NEVER DO THIS in listing pages
}: {
params: { place: string };
searchParams: { search?: string; distance?: string };
}) {
const events = await fetchEvents({ ...searchParams });
}
What To Do Instead
export default async function PlacePage({
params,
}: {
params: { place: string };
}) {
const initialEvents = await fetchEvents({ place: params.place });
return <HybridEventsList initialEvents={initialEvents} />;
}
Canonical URL Rules
Segment Omission Rules
| Condition | Canonical URL |
|---|
date = tots AND category = tots | /place |
date = tots AND category ≠ tots | /place/category |
date ≠ tots AND category = tots | /place/date |
date ≠ tots AND category ≠ tots | /place/date/category |
Query Parameter Rules
search, distance, lat, lon → Stay as query params
distance omitted when default (50km)
- Legacy
?category=X&date=Y → Redirect to path segments
Middleware Redirects
The proxy (proxy.ts) handles canonical redirects via handleCanonicalRedirects:
URL Building Functions
Always Use These Helpers
import { buildCanonicalUrl, buildFilterUrl } from "@utils/url-filters";
const url = buildCanonicalUrl(filters);
const url = `/${place}/${date}/${category}`;
URL Parsing Helpers
import { parseFiltersFromUrl, urlToFilterState } from "@utils/url-parsing";
import { getRedirectUrl } from "@utils/middleware-redirects";
SEO for Filtered URLs
Filtered URLs (with search, distance) should have noindex:
- Handled via
X-Robots-Tag header in proxy.ts
- NOT by making page dynamic with
searchParams
Checklist Before Modifying URL Logic
Files to Reference
Common Mistakes
- Adding searchParams to page props → Use client-side SWR
- Manual URL string building → Use helper functions
- Including
/tots/ in URLs → Gets redirected, use canonical form
- Forgetting query param preservation → Middleware handles this automatically