Most bugs announce themselves. The page errors, the form breaks, someone rings you.
This one does not. The site builds. It deploys. It loads perfectly for every visitor. And over a few weeks it disappears from Google, and nobody can tell you why, because from the inside nothing is wrong.
The cause is usually a single line in the HTML.
What a canonical tag is
Search engines have a problem: the same page is often reachable at several addresses. With and without www. With and without a trailing slash. Over http and https. With tracking parameters stuck on the end.
To a person these are one page. To a search engine they are four or five, competing with each other, splitting whatever authority the page has earned.
The canonical tag is how a page settles the argument. It sits in the HTML and says: whatever address you arrived at, the real one is this.
<link rel="canonical" href="https://example.com/services" />
Search engines take that seriously. That is the point of it, and also the danger.
How it goes wrong
Almost every framework builds the canonical URL from a configured base address — an environment variable, usually. And almost every framework tutorial writes it like this:
const siteUrl = process.env.SITE_URL || 'https://example.com'
That fallback is the bug.
It looks like sensible defensive programming. It reads as "use the real value, and if it is missing use something reasonable so nothing crashes." What it actually does is guarantee that a misconfiguration produces a working site that points at somebody else's domain.
The variable goes missing for ordinary reasons. Someone sets it in the server's .env but not in the build environment — and these values are baked in when the site is built, not read when it is served, so the server's copy has no effect. A CI configuration gets recreated. A repository is forked for a second client and the setting is not carried over.
Whatever the cause, the outcome is the same. Every page ships a canonical tag pointing at a domain you do not own, and every page politely tells Google not to index it.
Why nobody notices
Nothing fails.
The build succeeds — the fallback made sure of that. The deploy succeeds. Monitoring returns 200 on every page. Customers who already have the link browse normally. Staff who check the site see it working.
The only symptom is that search traffic decays, over weeks, in a way that is easy to blame on anything else. By the time someone thinks to view the page source, months of ranking are gone and getting them back is slow.
The fix: fail loudly
We do not use a fallback. On every site we build, the base URL has no default anywhere in the codebase. If it is missing or malformed, the build stops with an explicit message.
┌───────────────────────────────────────────────────────────────┐
│ BUILD STOPPED — environment is not configured │
└───────────────────────────────────────────────────────────────┘
NEXT_PUBLIC_SITE_URL is not set.
Set it to the full origin this build will be served from, with
protocol and no trailing slash. It is read at BUILD time, not
runtime — setting it only in the server's .env is not enough.
A failed build is annoying for ten minutes. A wrong canonical is invisible for three months. Given the choice, take the annoying one.
The same variable then produces every absolute URL on the site — canonical tags, the sitemap, robots.txt, Open Graph tags for link previews, and structured data. One source, so they cannot disagree with each other.
Then check it after it deploys
A build that fails loudly stops the obvious mistake. It does not prove the live site is correct — the value could be present and simply wrong.
So the deploy checks the finished site. After the new version is live, it fetches the homepage, reads the canonical tag out of the real HTML, and confirms it points at the domain we intended. Then it fetches the sitemap and confirms every URL in it uses the same host.
If either check fails, the deploy is marked failed and somebody finds out immediately.
It takes about fifteen seconds and it catches the one class of bug that no amount of local testing will.
What to check on your own site
Right now, on your phone:
- Open your homepage.
- View the page source.
- Search for
canonical.
The address in that tag should be your domain. If it is localhost, example.com, a staging subdomain, or a domain you have never seen before, that is why your site is not appearing in search.
While you are there, open yourdomain.com/sitemap.xml and check the addresses listed there match too. They are generated from the same setting, so if one is wrong the other usually is as well.
We build and host websites for Malawian businesses, with this check running on every deploy. If you would like us to look at yours, send us the URL.