URL Normalization¶
Canonical URLs are built in the Rust core, not in your framework. This keeps one source of truth: no adapter re-implements trailing slash logic, and Python and JavaScript agree byte for byte.
The pipeline¶
normalize_public_url runs these steps:
- Parse the input URL or path.
- Extract the base path from
public_base_url. - Prepend the base path when it is not already present.
- Normalize the path (see below).
- Filter query parameters: strip tracking, keep the allowlist.
- Build
scheme://canonical_host/normalized_path?filtered_query.
Path rules¶
Controlled by URLPolicy:
| Rule | Default | Effect |
|---|---|---|
| Ensure leading slash | always | /blog and blog both become /blog |
| Collapse duplicate slashes | True |
/a//b becomes /a/b |
| Lowercase | True |
/Blog/Post becomes /blog/post |
| Trailing slash | "never" |
/blog/ becomes /blog; use "always" or "preserve" to change |
| Enforce HTTPS | True |
http:// becomes https:// |
Query filtering¶
Tracking parameters are stripped using the absorbed detrack engine. When
allowed_query_params is non-empty, only those parameters survive.
from easeo import SEOConfig, URLPolicy, SEOEntity, build_seo_payload
config = SEOConfig(
canonical_host="example.com",
public_base_url="https://example.com",
url_policy=URLPolicy(allowed_query_params=["page", "q"]),
)
payload = build_seo_payload(
SEOEntity(entity_type="search", title="Search"),
"/search?q=headphones&utm_source=ad&ref=x",
config,
)
print(payload.canonical)
# "https://example.com/search?q=headphones"
utm_source and ref are stripped; q is kept.
Host validation¶
canonical_host must be host-only. These all fail with ConfigurationError:
https://example.com (scheme)
example.com/path (path)
example.com:8080 (port)
example.com/#frag (fragment)
example.com. (trailing dot)
Sub-path deployments¶
If public_base_url includes a path, that path is used as the base:
config = SEOConfig(
canonical_host="example.com",
public_base_url="https://example.com/blog/",
)
payload = build_seo_payload(entity, "/post", config)
print(payload.canonical)
# "https://example.com/blog/post"
Raw helpers¶
| Python | JavaScript | Purpose |
|---|---|---|
normalize_path(path, policy) |
normalizePath(path, options) |
Normalize a path only |
normalize_public_url(url, config) |
normalizePublicUrl(url, config) |
Build a canonical URL |
clean_url(url) |
cleanUrl(url) |
Remove tracking params, return a report |
clean_query(query) |
cleanQuery(query) |
Clean a query string |
Recap¶
- Normalization lives in the core and is shared by every language.
URLPolicycontrols path rules; the allowlist controls query params.canonical_hostis validated to be host-only.