A day after cutting over DNS for the three archive sites, the emails started. Google Search Console had opinions.
Review snippets structured data issues. Breadcrumbs using a deprecated schema. Pages returning 404 that used to return 200. The kind of feedback that makes you realise the migration was only half the job.
Setting up programmatic access
The first thing I did was set up API access to Search Console. Google's web interface is fine for checking one site, but with four properties to monitor, I wanted a script I could run from the terminal. A Python script using the Search Console API, authenticated via OAuth2, that pulls coverage data, sitemap status, and URL inspection results across all properties at once.
This turned out to be straightforward. Enable the API in Google Cloud Console, create Desktop app credentials, run the script once to authenticate in the browser, and the token gets cached. From then on, a single command shows me what Google knows about all four sites.
The structured data mess
The review pages were the worst offenders. Over 2,200 pages across the three sites had schema.org/Review markup that had never been quite right. Five distinct issues:
The itemReviewed field was a bare text span, not a typed object. The author field was missing a name property. The author wasn't typed as a Person. The rating used schema.org/Rating instead of the expected type. And there was an unclosed <span> tag that had been silently wrong since the markup was first generated.
For an active site, you'd fix the markup properly. For an archive of Symbian app reviews from 2011, the pragmatic answer was to strip the review schema entirely and reclassify as Article. The ratings still display visually. Google just no longer tries (and fails) to parse them as structured data.
Converting 23,000 breadcrumbs
Every page used data-vocabulary.org/Breadcrumb markup, deprecated by Google in 2020. The old markup was consistent: a <p> tag with nested <span> elements containing the breadcrumb chain. Home > Section > Page.
Rather than stripping it, we converted to schema.org/BreadcrumbList JSON-LD. A Python script parsed each page's breadcrumb HTML, extracted the link text and relative URLs, resolved them to absolute URLs based on the file path and domain, and injected a clean JSON-LD <script> block into the <head>. Then it stripped the deprecated attributes from the visible HTML, leaving the breadcrumb links intact for users.
One script, 23,124 pages, all three sites. The visible breadcrumbs look identical. The structured data is now what Google expects.
The case sensitivity problem
This was the subtlest issue. The old server ran Apache on cPanel, which serves files case-insensitively on most configurations. Google had indexed some URLs with different casing than the actual filenames. When those URLs hit the new Linux server (case-sensitive by default), they 404'd.
The URL Inspection API confirmed it: the lowercase version was indexed and had been successfully crawled as recently as four days before the cutover. The correct-case version was unknown to Google.
The fix was symlinks. For every HTML file with mixed-case characters in the filename, create a lowercase symlink next to it pointing to the real file. 12,232 symlinks across the three sites. Zero performance cost. Caddy follows symlinks by default. The 404s became 200s.
The http sweep
Checking the HTML source more carefully revealed that internal references were riddled with http:// URLs. The og:image meta tags on 23,000 pages. Cross-site links between AAS, AAWP, and AAM. References to the mediafiles subdomains. Even the twitter:image tags on AAWP.
A single Python script with a list of our domains handled the lot. 28,874 pages updated. It also caught a double-www bug that had been in 1,400 pages since the original CMS generated the URLs.
We also found that the mediafiles subdomains had no cache headers, no security headers, no HSTS. A quick Caddyfile update added Cache-Control: immutable (one year), HSTS, and content-type protection. One reload, all mediafiles now cached properly.
The meta description gap
PageSpeed Insights flagged that pages had no <meta name="description">. They had og:description (for social sharing) but not the standard meta description (for search results). A script that copied the og:description value into a meta description tag, only where the og:description was non-empty, added descriptions to 12,494 pages.
Monitoring what you've fixed
Fixing things is satisfying. Knowing they stay fixed is more important. We set up three layers:
A daily health check script runs at 06:00, verifying disk usage, memory, Docker containers, HTTP status of every site, SSL certificate expiry, Tailscale connectivity, and whether the overnight backup completed. It emails only if something is wrong.
A weekly Lighthouse audit runs on Sunday mornings, testing six URLs (two per site) and logging the scores. If any score drops below threshold, it alerts.
Uptime Kuma monitors all eleven public endpoints every five minutes, providing a status dashboard and historical uptime data.
The total resource cost of all three monitoring systems: negligible. The total cost of not having them: discovering a problem when a user emails you, or worse, when Google deindexes a broken page.
The boring truth about SEO
None of this was glamorous work. Converting breadcrumb schemas, fixing canonical URLs, adding meta descriptions, creating symlinks for case-sensitive filesystems. It's the kind of thing that gets skipped because it doesn't feel productive.
But it's the difference between "the site works" and "the site works the way search engines expect it to." For an archive that depends on search traffic to remain discoverable, that distinction matters. Twenty years of mobile history is only preserved if people can find it.
Post-script - 1 month on
...and it had a real impact - traffic is up about 50% - a lot of that attributable to search engine or AI assistant traffic. It's hard to be certain, but I'd attribute it to performance (a ranking signal), technical improvements (e.g. consistent https), and making it easier for machines to understand the content... rather than a sudden upsurge in Symbian nostalgia!
New writing by email
Occasional pieces on product, technology and AI — and how they actually play out in practice.