All 30 checks Canonical points to staging
Canonical points to staging
Updated
A canonical link that names a staging or preview address tells search engines that the real version of your live page lives on a server you meant to hide, switch off, or delete after launch.
<!-- Served at https://example.com/services -->
<link rel="canonical" href="https://staging.example.com/services"><link rel="canonical" href="https://example.com/services">What a staging canonical tells search engines
The canonical link says which URL should represent a page in search results when the same content is reachable at several addresses. When it names the page's own live URL, it consolidates duplicates there. When it names a staging host, it asks search engines to treat your live page as the copy and the staging page as the original.
Google treats the canonical as a strong hint, not an order, and may pick a different URL when other signals disagree. That is not a safety net. The staging copy is often behind a password, carrying noindex, or deleted once the site launches, and you have handed the decision about which URL represents your page to a guess. Links to the live page, its title and its snippet all depend on a choice you stated incorrectly.
How it happens
Canonicals are almost always generated, not typed, and generators build them from a base URL set once. In Next.js that base is metadataBase, and building it from VERCEL_URL is a common trap, because that variable holds the generated address of each deployment, not your domain. In WordPress, canonicals come from the Site Address (URL) under Settings, General, so a migration that leaves it, or URLs saved in the database, on the old host carries staging into every page.
Webflow's SEO settings include a global canonical URL field, and a webflow.io address entered there during the build ends up on every page. Static site generators read the base from config, such as site in astro.config or baseURL in Hugo. Hand-written canonicals in a shared head snippet or an SEO plugin's per-page field go stale the same way. On a client handover, the result is a site that looks finished and points every page at an address the client never sees.
How Skewcheck detects it
Skewcheck reads the first <link> in the HTML whose rel includes canonical and whose href is not empty, and resolves the href against the audited page's final URL, so relative values work. With no canonical, the check warns with medium severity for 4 points, as covered in the no-canonical guide. A value that is not a valid URL, or is not http or https, gets the same medium warning and 4 points.
If the canonical's host matches the page's host, ignoring a leading www., the check passes. If the audited page is itself on a staging host, any valid http or https canonical passes, because pointing a staging copy at the live site is correct. If the canonical's host is a staging host and the page is not, the check fails with high severity, removes 8 points from the audit score, which starts at 100, and caps the score at 49.
Staging hosts include localhost and addresses ending in .local or .test, .webflow.io, WP Engine's .wpengine.com and .wpenginepowered.com, Vercel preview URLs containing -git-, Netlify deploy previews and branch deploys with a double hyphen, Cloudflare Pages previews in front of project.pages.dev, and subdomains containing the word staging, stage, stg, dev, preview, uat or sandbox.
Fixing it
Point each canonical at the page's own URL on the live domain, using an absolute https:// address. Fix the base URL setting rather than individual pages, then re-audit a few templates. In Next.js, set metadataBase to the production domain in the root layout and write canonicals as paths. In WordPress, correct the Site Address and run a search-and-replace on the database for the old host. In Webflow, update or clear the global canonical field and republish to the custom domain.
import type { Metadata } from "next";
export const metadata: Metadata = {
// Not process.env.VERCEL_URL: that is the generated address of each deployment.
metadataBase: new URL("https://example.com"),
alternates: {
canonical: "/",
},
};Canonicals on another site, and other edge cases
A canonical on any other host that is not staging gets a softer result: a low-severity warning worth 2 points. That covers syndicated articles that credit the original publisher, which is legitimate, but it also catches subdomain mismatches such as blog.example.com pointing at example.com, a canonical left on a plain .framer.app or .vercel.app address after a custom domain was connected, and hash-style Vercel deployment URLs, which Skewcheck does not count as staging. If the audited page is the original, the canonical should name the page itself.
Skewcheck compares hosts, not paths, so a canonical that sends every page to the home page of your own domain still passes, though it causes the damage described in the no-canonical guide. It also reads only the HTML tag, not a canonical sent in an HTTP Link header, and only the first canonical if a theme and a plugin both add one. Check the og:url and sitemap URLs while you are there, since they are usually built from the same base URL.
Before and after
<!-- Served at https://example.com/services -->
<head>
<title>Services | Example Studio</title>
<link rel="canonical" href="https://example-studio.webflow.io/services">
</head><!-- Served at https://example.com/services -->
<head>
<title>Services | Example Studio</title>
<link rel="canonical" href="https://example.com/services">
</head>Check your page
Run all 30 checks on any URL. Free, no account.