All notes

Canonical URLs Are a Deployment Contract, Not Just an SEO Tag

Keep origins, paths, language versions, previews, and content identity consistent.

English edition, edited for length. Read the complete Chinese original.

The same article can be accessed through the main domain, www, the “Workers” subdomain, and the preview address. If a page directly includes the current request domain in its canonical tag, each of these access points will claim to be the primary version. Access to the site does not guarantee content identity consistency.

The canonical tag specifies the preferred URL for a search index, but it does not provide access control or enforce redirection. This document assumes that public websites use a stable primary domain, while other entry points can be accessed or redirected. It does not discuss the scenario where multi-tenant websites dynamically assign domains to different customers.

Generate canonical URLs from configuration

---
const productionSite = new URL('https://example.dev');
const canonical = new URL(productionSite.href);
canonical.pathname = Astro.url.pathname;
canonical.search = '';
canonical.hash = '';
---
<link rel="canonical" href={canonical.href} />

Example: Preserving the path, removing query parameters and fragments, suitable for article pages where the content remains the same regardless of the parameters. This strategy does not apply if the parameters represent independent, indexable content. Also, avoid directing all pagination to the first page: different sets of articles on subsequent pages should be handled according to a real indexing strategy.

Clone a trusted base URL and assign its pathname instead of resolving untrusted input as a URL. new URL('//other.example/path', productionSite) changes the origin, whereas assigning pathname preserves the configured protocol and host. Whether // is a valid internal path remains a routing-policy decision; this pattern prevents an origin change but does not replace route or deployment-prefix validation.

In Astro, you can use site to provide a production site baseline. Layout components, RSS feeds, and sitemaps should reuse this configuration instead of creating separate strings for each. Otherwise, a domain migration would only change the layout title, while the RSS feed would still point to the old site.

When the production site’s baseline is missing, some implementations revert to the current request URL or a development domain, and the build still succeeds. For canonical URLs, this fallback hides deployment errors. Production builds should require a valid, absolute HTTPS baseline and reject local addresses and unauthorized preview domains. Development environments can use different rules, but they must have explicit environment markers. Rolling back a configuration failure is easier than fixing a single, incorrect identity.

The list of allowed domains should not be dynamically expanded by arbitrary requests. For products that genuinely support multiple tenants, public domain names should be obtained from trusted tenant records, and their binding status should be verified before generating the address. For a single-person blog, a fixed site configuration is usually sufficient, and complex derivations should not be created for the hypothetical expansion. Temporary addresses returned by the deployment tool can be used for acceptance and should not automatically replace the permanent addresses of the content.

Verify both the protocol and the port. A development port left in a production absolute URL can make shared links unusable. If a proxy misidentifies the protocol, it may generate an HTTP URL that requires an extra redirect. During normalization, preserve meaningful path characters and validate the domain and path separately. Avoid making one function handle every string correction: fixing one kind of URL problem can otherwise introduce another.

Write the site baseline into a non-sensitive build summary, recording the environment, version, and the number of public entries. This allows you to confirm which configuration was actually used after encountering an error. Do not print all environment variables; the fact that a domain name is not secret does not mean that nearby tokens can also be output. Configuration transparency should be focused on necessary fields, not indiscriminate debugging dumps.

Path Normalization Must Also Be Standardized

Choosing to use the trailing slash ensures that internal links, the canonical URL, and redirects all adhere to this rule. Whether to preserve case is not purely an aesthetic issue: servers may treat two different paths as distinct resources. The most convenient approach is typically to make the content slug lowercase from the start, and to have the deployment layer explicitly handle the migration of historical variations.

Binding both the root domain and www does not mean that both should be canonical. You can choose the root domain as canonical and permanently redirect www. Tests must preserve paths and query parameters so that deep links do not collapse to the homepage. Also test the HTTP-to-HTTPS path to ensure that no intermediate hop downgrades the connection.

The #transaction-boundary token in an article points to a specific location within the main content, and typically doesn’t need to be a canonical version of the same content. However, removing a fragment from the canonical version doesn’t necessarily mean that the sharing functionality should also remove that fragment: readers copy chapter links because they want to directly access that specific section. The system should verify both the page identity and the location behavior, ensuring that the main URL doesn’t contain the fragment, while the corresponding ID within the article still exists. The two outputs can be different because they serve different purposes.

If a front-end application interprets a fragment as a different piece of content, such as an older #/article/42 route, then the standard article strategy cannot be directly applied. If the server receives a request that does not include a URL fragment, and instead generates an identity based solely on the request path, all front-end pages will be treated as root pages. First, choose a content address that can be independently requested, or verify the rendering and indexing scheme of the current application. Do not simply modify a single tag, leaving an inaccessible entry point for the main content.

The test setup includes three types of input:

  • Same body, different parameters: verifies which variants are genuinely equivalent.
  • Unknown parameters: verifies that unsupported input is handled deliberately.
  • Different body and different parameters: verifies that the strategy does not merge distinct content.

Specifically, it’s crucial to verify that after a framework update, the Astro.url.pathname, trailing slashes, and deployment base path remain consistent with the expected values.

When deploying sub-paths, using only the root domain may result in the loss of the site prefix. For multi-tenant deployments, it’s essential to have a trusted tenant configuration and cannot treat any Host header as a trusted site baseline.

Do not canonicalize every language page to English

Although articles in Chinese and English discuss the same topic, they are still different language content. Typically, each language version should maintain its own canonical version, and then use the correct language to establish relationships between them. Pointing the Chinese version’s canonical to the English version could send a misleading signal that “Chinese is just a duplicate,” and also create inconsistencies in sharing and indexing. Language replacement tags must correspond to each other; they should not only point from the English page to the Chinese page.

If the root path is responsible for browser language negotiation, it should be clearly defined as either the entry point or a standalone content page. After redirecting to a stable language path, the canonical URL should be generated for the final content page, without creating separate identities for all browser preferences. Search crawlers may not carry the same language headers as real users, so all language pages should also be accessible via clear links. Browser language policies cannot replace site navigation and language mapping.

Not all articles have complete translations. If only a simplified Chinese version exists, do not generate a non-existent English alternative address. The English index page can link to Chinese articles, but it must clearly indicate the language. When the translation lags behind the original, record the update time and content status, rather than simply claiming complete equivalence based on the same slug. The language association in the content management system should come from a stable identifier, not from guessing based on similar titles.

The preview environment requires two layers of protection.

The first step is to ensure that the preview page does not generate incorrect production content identifiers. The second step is to prevent preview content that should not be indexed from being discovered. You can set X-Robots-Tag: noindex for previews, but if the content itself is confidential, you should use access control and not rely on crawlers to self-regulate.

This is especially true for previewing drafts: marking a page as “noindex” still allows anyone who has a link to access it. Private drafts should be excluded from the public build, and preview access should require authentication. The “canonical” link to the official page also cannot hide the text that is being previewed.

When building, it’s crucial to clearly define the source of environment variables. Instead of guessing the production environment based on whether a CI variable exists, use a fixed deployment context and output non-sensitive site base addresses in the logs. Running previews and production builds separately prevents a single, faulty build from introducing preview settings into the production environment.

Switching Domains Requires an Identity Mapping Table

Assume that a technical blog migrates from an old domain to a new domain, while the content paths remain unchanged. The migration should not only involve modifying a single string in the layout. Instead, you should first list the HTML canonical URL, Open Graph URL, structured data, RSS links, sitemap, absolute image URLs, and the sources of sharing buttons. Then, clearly specify which site each piece of data is associated with. When configuring a single source to affect multiple outputs, a unified configuration is more reliable than searching and replacing, as some URLs are generated by third-party plugins.

The migration table simultaneously records both the old and new addresses. If some articles have been moved to different paths, simply replacing the old domain name with the new one is not sufficient; a one-to-one mapping is required. For content that has not been migrated, returning a 404 (Not Found) response is more honest than redirecting to an irrelevant homepage. Before migration, save a list of the old paths. After migration, verify that each old address can be reached by the corresponding content. Testing the homepage alone cannot cover deep paths, pagination, and language versions, especially because edge cases may only correctly match the outermost paths.

The redirection of an old domain and the canonicalization of a new domain are complementary processes. The former directs traffic to the new location, while the latter provides a preferred identity for the still-accessible variant. Do not stop maintaining the old domain simply because the new site’s tag is correct, nor should you continue to point the new site’s tag to the old site simply because the old site’s redirection is correct. Therefore, domain renewal and certificate status fall under migration maintenance, rather than purely search optimization.

If the share button reads the current browser address, visitors accessing through a preview domain or tracking parameters will continue to propagate these variations. Sharing an article can directly use the verified main address, while the “Copy Current Page Location” function can retain fragments, allowing readers to navigate to a specific section. These two actions have different intentions and should not be determined by a single, unspecified function.

Email, social media cards, and internal search results should also use standard article addresses. Test values can be generated from public records and then compared across different outputs, rather than maintaining separate configurations for each output. It is important to avoid using the same error-generating functions in both testing and production environments, as this would still result in errors in both cases. Prepare a separate sample with a fixed domain and path to verify concatenation, encoding, and prefix handling, rather than simply verifying that two functions return the same string.

Determining Parameter Identity Based on a Specific Pagination Example

Consider /writing/?page=2&tag=rust&utm_source=mail. The tracking parameter does not change the content, but the page number and tag change the article collection. First define the resource for each page type: an article page represents one article, a tag page represents a topic collection, and a paginated page represents a segment of that collection. Only then decide whether two URLs should share a canonical URL. Defining resources to satisfy a goal of “fewer URLs” can merge content that is not equivalent.

If a site doesn’t want to include specific combinations in the index, it can clearly define indexing rules while still allowing users to share and return their filtered states. If a particular combination is deemed worthy of indexing, it should be given a stable URL, a reliable title, and a clear entry point, and its canonical URL should point to its standard form. Sorting only changes the presentation of a single collection. Whether it should be sorted by default also needs to be determined based on whether the visible content on the page is consistent enough, rather than simply inferring from the parameter names.

Create a page strategy table for these decisions: path category, content type, retention parameters, canonical form, and indexing intent. Then, use a sample to test this table.

The following should be followed jointly by request handling and identity generation:

  • Is page=1 equivalent to a page without a page number, or does it return an error or redirect for an invalid page number?
  • How are duplicate parameters handled?
  • When the response indicates a missing page, it should not continue to use the canonical form of the normal article. Otherwise, invalid input may claim to have a valid content identity.

The same test matrix also needs to validate internal links. The page statement declares /writing/?tag=rust&page=2 as the main version, but pagination navigation continuously generates another parameter order or domain, creating continuous variations in traffic. While differences in order may not prevent the search system from understanding, unifying the generation reduces diagnostic costs. Do not silently execute business logic for unknown parameters; normalization functions can only merge already proven equivalent inputs.

When Migrating a Domain, Check for Old Identities in Caches

The site configuration has been updated to the new domain name, and the new HTML on the source site is also correct. However, the edge cache is still serving pages with the old canonical URL. At this point, the repository check passes, but the actual signals are still split. The migration process should include HTML caching, RSS feeds, sitemaps, and social media card caching in the check scope. Each platform has different clearing capabilities, so it’s important to record the actions that have already been performed and any client-side caching that cannot be controlled. Completing a deployment does not mean that all copies have been updated.

For caches that can be cleared, first determine if the clearing range covers language, path, and parameter variations. Then, request a representative page from the actual entry point. Non-sensitive deployment version information in the response can help identify older versions, but it cannot be used to infer which version the content belongs to solely based on the cache status header. Directly parsing canonical and Open Graph URLs and their corresponding text is necessary to verify identity. When multiple entry points are accessed with different rules, request the deep paths of the root domain, www, and the old domain separately.

Preview cards retrieved from social platforms may have their own cached versions. While new, accurate metadata can be provided and the platform’s refresh mechanisms used, this does not guarantee that all historical shares will be immediately updated. Similarly, a reader retaining old domain links does not necessarily mean that the RSS feed generator is still malfunctioning. Reports should distinguish between the server’s current output, known cached copies, and the status of updates from third-party sources, to avoid repeatedly migrating article identifiers due to uncontrolled, outdated copies.

If you must revert domain configurations, it’s also important to evaluate the redirection direction. If the old domain has been permanently pointed to the new domain, and the new domain points back to the old domain, this could create a loop. Even without a loop, permanent redirects cached by browsers can negatively impact the reversion process. It’s more reliable to maintain a diagram of the directions and a reversion plan in advance, rather than exchanging two strings at the last minute. Domain switching involves revealing public information, so it’s not suitable for frequent flipping like a temporary switch.

The proxy header and request domain cannot be used as unvalidated input

Some runtime layouts generate absolute URLs using the Host or forwarded headers, which is convenient during development. However, the trustworthiness of these fields depends on the proxy chain. When the origin server directly exposes information, attackers may set the forwarded headers themselves. Multi-level proxies may also append different values. A single-domain site can avoid this by using a fixed, publicly known base URL. If dynamic domain names are truly required, a trusted proxy range and domain approval records are essential, not optional optimizations.

The tool can generate test inputs for the runtime generator, including:

  • A normal main domain
  • www
  • A preview domain
  • A “foreign” domain
  • A domain with an illegal port

It should assert that only approved configurations are used. It should not directly echo foreign hostnames to canonical URLs, password reset emails, or publicly shared links. The primary issue with canonical errors is often related to identity, but if the same set of unverified absolute address functions is used in the account flow, the impact will be wider. Therefore, the tool functions should have a clear purpose and avoid a vague interface like “generate URLs everywhere.”

When generating absolute links, use a trusted baseline and a clear path to call the URL API, and check if the path input could be interpreted as an absolute address or a relative address with a protocol. If new URL can correctly parse it, it does not necessarily mean that the results will always remain within the expected domain. Content systems with restricted slugs can usually provide this guarantee; however, if the path comes from free text or third-party imports, the resulting domain still needs to be verified. Successful parsing is a syntactic property, while same-origin is a business and security property.

Preserve enough evidence to disprove a successful result

A report can record the request entry point, the status of the first hop, the final URL, the number of canonical URLs, and content identifiers. Only saving the final URL will not reveal intermediate HTTP redirects or unnecessary jumps; only saving the HTML will not reveal edge redirects. For previewing, it is important to record the intent indicated by the response headers (e.g., indexing) and whether access control is effective, and to avoid assuming that a preview is private simply because the canonical URL points to the production environment.

Failed samples should retain reproducible requests, but remove session and private query values. This allows subsequent changes to be validated against the original request. For example, if a deep path like “www” is redirected to the homepage, the request should be retained for verification purposes after the fix. Testing should use a fixed and known expected content identifier to avoid the final URL being correct but pointing to the wrong article. The identity contract ultimately serves to ensure that readers reach the correct content, rather than simply collecting a few SEO tags.

Do not rebuild subscription entries along with the domain name.

When migrating a domain, the article links in RSS feeds need to point to the new address. However, the persistent identifier for subscriptions is a separate decision. Assuming that the reader previously used old article identifiers to deduplicate, the generator now changes all identifiers to the new domain name. The reader might then treat the old articles as new entries. Since the behavior of different readers cannot be guaranteed, it’s important to first check the current format against existing identifiers and not mechanically derive all subscription identities from the canonical new values.

Keep a synthetic sample of the subscription before migration for comparison, and compare the expected changes in title, body link, publication time, and article ID after migration. Published articles should only change the access location; new publication times should not be fabricated solely for the purpose of migration. Substantial revisions that require republishing should be determined separately regarding notification strategies. The consistency of domain identity and subscription deduplication are not the same assertions.

Complete End-to-End Identity Verification Process

List all URLs accessible through all entry points. For each entry point, record the status code, final URL, canonical URL, Open Graph URL, and RSS feed URL. These URLs should all point to the same main version, unless the multilingual or pagination agreement explicitly allows for different versions.

When migrating a domain, continue to keep the old domain redirects active, update the sitemap, and check for absolute URLs in images and structured data. Do not claim that canonical tags can immediately consolidate all search signals; search engines will still combine redirects, content similarity, and link analysis. The engineering goal is to provide consistent signals, not to guarantee search results for the search engines.

The template only contains a single <canonical> tag, which does not mean that the actual page only has one. Plugins, layout nesting, or older components may re-insert the tag, leading to conflicting values on the same page. After building, parse the representative HTML and assert that there is only one absolute HTTPS address, and verify that the target returns the expected content. Do not only use string searching to find the first tag; the second conflicting tag is equally important.

At a minimum, check the homepage, a single article, pagination, language page, and missing page. The missing page should not claim the homepage as its main content, and the preview page should not lose its “noindex” status due to reusing the production layout. Saving the results separately for production and preview allows you to identify cases where the same generated output is incorrectly reused. The response headers and HTML indexing strategy should be consistent, avoiding situations where a page indicates that it is indexable while the deployment layer uniformly prohibits indexing.

After deployment, you can observe the actual preferred addresses within the indexing tool, but do not consider temporary, unupdated results as inevitable code errors. Also, do not assume that temporary inclusion indicates complete configuration correctness. The engineering acceptance focuses on verifiable signal consistency. The final search system’s selection still depends on its crawling and judgment processes. Complete the controllable aspects thoroughly, and then correct based on real feedback, rather than making promises that a specific tag will immediately change rankings.

References: Google canonical guidelines, Astro configuration reference.