# 0039. Every page states its canonical, without query parameters

- **Status:** Accepted
- **Date:** 2026-09-24

## Context

`View::head()` emitted a canonical only when the controller set one **and** it differed from the current URL —
so practically no page had one. The storage search is driven by `s[...]` query parameters (filters, sort,
page), and every campaign parameter produces another address for the same page. The error page answered every
unknown URL with a self-referencing `og:url` and the full hreflang set.

## Decision

`View::getCanonical()`: the controller's explicit canonical, otherwise the current URL **without its query
string**. hreflang alternates drop the query string too. A `noindex` page (the error page, the admin) emits no
canonical, no hreflang and no `og:url`. The error page is `SiteController::actionError()`: `noindex`, and the
title of its own error type.

## Alternatives

- **An allow-list of query parameters** (Molino keeps `p` and `c`). Nothing here needs one: pagination outside
  the search is in the path, and the search's filtered views are not pages to index.
- **Keep the difference check.** It is why there were no canonicals.

## Consequences

- A filtered, sorted or paged `/tarolok` canonicalises to `/tarolok`; units beyond the first page are found
  through the sitemap, which lists every one.
- A query parameter that should become an indexable page needs an explicit canonical from its controller.
- `noindex` and a canonical cannot coexist; nothing needs both today.
