ChatGPT Stopped Recommending This App Overnight. The Culprit Was a Domain Move (and One Regex)
For weeks, ChatGPT was quietly acting as an iOS app's best salesperson, sending roughly a third of all its downloads. Then, almost overnight, it went silent. No bad review. No App Store penalty. No new version. Nothing
For weeks, ChatGPT was quietly acting as an iOS app's best salesperson, sending roughly a third of all its downloads. Then, almost overnight, it went silent.
No bad review. No App Store penalty. No new version. Nothing in the app had changed.
The website had moved to a new address. It was a routine, by-the-book migration, the kind that's usually forgotten by lunchtime. Within days, the app's biggest traffic source had dropped to close to zero, and total downloads were cut in half.
Here's how a "harmless" domain move wiped out a ChatGPT traffic source, the one-line regex bug that quietly made it worse, and the checklist that would have prevented all of it.
The "textbook" migration
The app is a small iPhone utility with a marketing site: a homepage, feature pages, comparison pages and about a dozen how-to guides. The site was originally served from a Cloudflare Workers subdomain (something.workers.dev).
In early October it was rebuilt with Astro and moved to a Cloudflare Pages subdomain (something.pages.dev). As part of the rebuild:
- canonical tags,
og:url, JSON-LD, the sitemap androbots.txtall switched to the new host - URLs changed shape:
/guides/foo.htmlbecame/guides/foo, and/guides/became/guides - the old Workers host became a small redirect Worker that sends visitors to the new host
On paper, it was flawless. In practice, it was about to go badly wrong.
The day the referrals stopped
App Store Connect reports first-time downloads by source. One source is App Referrer, meaning another app sent the user to the App Store, and ChatGPT appears there by name.
| Period | Share of downloads from App Referrer (mostly ChatGPT) | Daily downloads |
|---|---|---|
| Several weeks before the move | roughly a third | baseline |
| A couple of weeks after the move | close to zero | roughly half of baseline |
App Store search dipped only modestly, so the main loss was ChatGPT. The referrals stopped within days of the canonical change. (All figures here are rounded on purpose.)
This is a correlation, not a controlled experiment. Still, no other change lined up: the listing, keywords and app version stayed the same through that window.
Why AI assistants punish a domain move harder than Google does
ChatGPT's web answers rely heavily on Bing's index. When a page that recommended the app was indexed under the old host, ChatGPT could find it, cite it and link to the App Store.
After the move:
- The old URLs told Bing "the real page is now elsewhere" (new canonicals and redirects).
- The new host was brand new to Bing: no history and no inbound links.
- Bing didn't hand the old pages' standing to the new host quickly. The old pages lost value, and the new ones had none yet.
Google handled it a bit better and indexed the new homepage within a few weeks. Bing's URL Inspection showed something more worrying:
Blocked β URL cannot appear on Bing.
"The inspected URL is known to Bing but has some issues which are preventing us from serving it."
The Live URL test for the same page passed: "URL can be indexed by Bing", "No SEO/GEO issues found". So it wasn't a technical block. It was a quality or trust decision about a new, unlinked site, and the Bing dashboard said as much: "Your site lacks inbound links from high-quality domains."
The one-line bug hiding in plain sight
The redirect Worker on the old host looked roughly like this:
const TARGET = 'https://new-host.example';
// Some pages must still be served in place (older app builds load them in a web view).
const PROXIED_PAGES = new Set(['/privacy', '/terms']);
// "Assets" are proxied so the proxied pages can load CSS and images.
const isAsset = (pathname) =>
pathname.startsWith('/_astro/') || /\.[a-z0-9]+$/i.test(pathname);
export default {
async fetch(request) {
const { pathname, search } = new URL(request.url);
if (PROXIED_PAGES.has(pathname) || isAsset(pathname)) {
return fetch(TARGET + pathname + search); // served as 200
}
return Response.redirect(TARGET + pathname + search, 301);
},
};
Spot it? It's easy to miss. The "is it an asset?" regex matches any file extension, including .html. Every old /guides/foo.html URL, which were exactly the URLs search engines had indexed, was proxied with a 200 instead of redirected with a 301.
So the old host kept serving full copies of every page, while those copies' canonical tags pointed at the new host. To a crawler, that looks like two sites with the same content, one of them disowning itself. That's a good way to make a search engine distrust both.
The fix is small: exclude HTML from the asset rule, and map old URL shapes straight to the final URL in one hop.
const isAsset = (pathname) =>
pathname.startsWith('/_astro/') ||
(/\.[a-z0-9]+$/i.test(pathname) && !/\.html?$/i.test(pathname));
// /index.html β /, /guides/ β /guides, /guides/foo.html β /guides/foo
const canonicalPath = (pathname) => {
let path = pathname.replace(/\/index\.html?$/i, '/').replace(/\.html?$/i, '');
if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1);
return path || '/';
};
// ...
return Response.redirect(TARGET + canonicalPath(pathname) + search, 301);
A quick way to test every legacy URL, using the old sitemap from git history:
while read p; do
curl -s -o /dev/null -w "$p -> %{http_code} %{redirect_url}\n" "https://old-host.example$p"
done < old-paths.txt
Every line should show 301 pointing at the matching new URL. Following the redirect (curl -L) should end on a 200.
Two more traps waiting in Search Console
-
Google Search Console showed "Couldn't fetch" for a sitemap that worked. The sitemap returned
200with valid XML, even when fetched with a Googlebot user agent. The status stayed stuck with no "Last read" date for over a week. It's a known display quirk on new properties: the file is fine, Google just hasn't fetched it yet. Resubmitting with a query string (sitemap-0.xml?v=2) creates a new entry, androbots.txtshould list the sitemap anyway. - "Request indexing" hit "Quota exceeded" on the very first click. On new properties this happens often. Don't fight it: the sitemap and internal links from an already-indexed page do the same job without a quota.
The checklist that would have saved the traffic
- Don't move unless you have to. If the old address is indexed and cited, its history is an asset.
-
If you move, move once, ideally to a domain you own. Shared hosts like
*.pages.devor*.workers.devstart with little trust, and every later move resets it. - Redirect every old URL with a single 301 to its exact new equivalent. Test the real legacy URLs (from the old sitemap in git), not just the homepage.
- Never serve full copies of pages from the old host, even by accident through an asset rule.
- Set up Bing Webmaster Tools and Google Search Console before the move, not after. Bing has a site-move tool, and you'll want the before-and-after data.
- Use IndexNow to tell Bing about changed URLs immediately.
- Plan for inbound links. A new host with no backlinks may be crawled but not shown. Directory listings, launch sites and a few relevant articles help.
Where it stands now
The redirect fix is live, every legacy URL now returns a clean 301, a Bing review has been requested, and the backlink work has started. Recovery isn't guaranteed or quick. Search engines rebuild trust over weeks, not days. A follow-up will share what actually brought the traffic back.
The app in this story is Clean Kit, an iPhone photo and storage cleaner that runs entirely on-device. It's on the App Store.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.