Close-up of hands typing on a keyboard.
Photo by Jakub Zerdzicki via Pexels

How we found it

About a month after ciaospeak.app went live, we opened Search Console's Pages report to see which content was getting picked up. Near the top of the list, the same blog post appeared twice: /blog/how-to-learn-chinese-tones/ showing 48 impressions, and /blog/how-to-learn-chinese-tones — no trailing slash — showing another 21. Same content, same page, split into two separate rows. It wasn't a one-off: our TOCFL vs. HSK comparison showed 45 impressions on one URL and 11 on the other, and our Taiwan-vs-Mainland comparison page showed 32 and 10. Every one of them was really one page's worth of visibility, just fragmented into two weaker-looking listings instead of one stronger one.

The root cause: a canonical tag that contradicts its own redirect

The explanation turned out to be a straightforward, self-contradiction — once we went looking for it. Every page's <link rel="canonical"> tag, its Open Graph og:url, its JSON-LD mainEntityOfPage, and our sitemap.xml all declared the slashless URL as the authoritative one (e.g. https://ciaospeak.app/blog/tocfl-vs-hsk). But Cloudflare Pages, our host, actually serves the real page at the trailing-slash version, and issues a 308 redirect from the slashless URL to it. We confirmed this directly:

  • Requesting https://ciaospeak.app/blog/tocfl-vs-hsk returns HTTP 308, redirecting to https://ciaospeak.app/blog/tocfl-vs-hsk/.
  • Requesting https://ciaospeak.app/blog/tocfl-vs-hsk/ directly returns HTTP 200 — the real page.

So Google crawls the URL our own metadata calls canonical, gets redirected away from it immediately, lands on a different URL that actually renders the content — and that page's own canonical tag points right back to the URL it was just redirected away from. An unresolvable loop, from a crawler's perspective. The likeliest explanation for indexing both: with no single URL it can fully trust, Google kept both around rather than confidently consolidating on one.

How to check if your site has this bug

This takes about two minutes per page, and it's worth running on any static site, not just one on Cloudflare Pages:

  • Open any indexable page and view its source. Find the <link rel="canonical" href="..."> tag and copy that exact URL.
  • Run curl -I against that exact URL (or open it directly in a browser and watch what it resolves to).
  • If it 30x-redirects anywhere before landing on a 200, your canonical tag doesn't match what your server actually serves — the same contradiction we found.
  • Repeat for your sitemap.xml entries and any JSON-LD url/mainEntityOfPage fields — a fix to the visible canonical tag alone can still leave these pointing at the old form.

The fix

We updated every self-referencing URL to match what the server actually serves as a real 200 — the trailing-slash form — across four places per page: the <link rel="canonical"> tag, og:url, JSON-LD's mainEntityOfPage, and any breadcrumb links pointing at other pages on the same site (a BreadcrumbList entry for another page is just as self-contradictory if it points at a URL that redirects). We updated sitemap.xml the same way, since Google treats sitemap URLs as a strong canonical signal in their own right. In total: 23 pages, 98 individual corrections, verified by re-parsing every JSON-LD block afterward to confirm nothing broke in the process, then deployed and checked live with a cache-busting request.

Why this is easy to miss

Nothing about this bug is visible from using the site normally. Every page loaded correctly, every link worked, every redirect silently did its job in a fraction of a second — there's no broken experience to notice by clicking around. The only place it ever showed up was in aggregate Search Console data, days to weeks after the fact, as two quietly under-performing listings instead of one healthy one. If you've never specifically compared your canonical tags against your own server's actual redirect behavior, it's worth the two minutes above to check — especially on a young site, where every bit of nascent ranking signal matters more, not less.

This is one of a few real, mechanical things we're getting right before we lean on the bigger lever — backlinks — once CiaoSpeak is actually live on the Play Store. See what six real documented site launches (including this one) show about what actually moves a new domain's traffic for the fuller picture.

Frequently asked questions

What is a canonical URL and why does it matter for SEO?

A canonical URL is the one version of a page you're telling search engines to treat as authoritative when duplicate or near-duplicate URLs exist for the same content. If it points somewhere other than what your server actually serves as the real page, search engines can index both versions separately instead of consolidating signal onto one.

Does Cloudflare Pages redirect URLs with and without a trailing slash?

Yes. Cloudflare Pages serves a static page's real content at its trailing-slash URL and issues a 308 redirect from the slashless version to it. If your canonical tags, sitemap, or structured data declare the slashless form, they contradict what your server actually serves.

Can a duplicate-URL bug like this hurt my Google rankings?

It can dilute rather than directly penalize: instead of one page accumulating impressions and ranking signal, Google can index two URLs for the same content and split that signal across both, making each one look weaker than the page actually is.