We run a small Astro site on Netlify for Stillware, a UK studio that makes pay-once apps (Zeroed, an offline budgeting app, and RankUp Chess). Over six weeks this year we made the most expensive SEO mistake a young site can make, and nothing in our build noticed.

What happened

  • 28 August: a "content cleanup" deleted 13 blog posts that looked off-topic for a budgeting-app company. They had no redirects. Two of them were the biggest pages on the site: one had 94 Google clicks and 4,517 impressions, the other 18,137 impressions at an average position of 4.6.
  • 29 September: an audit spotted the 404s and turned them into 410 Gone. A 410 tells Google the removal is deliberate, so it drops the URL faster.
  • Result: the 17 deleted URLs that had ever earned an impression accounted for 63% of every Google click the site had ever had. Clicks per 28 days fell from about 65 to about 40.

None of it was malicious or careless in the moment. Each step had a reason ("off-topic", "clean up 404s"). The problem was that "is this page earning search traffic?" was nobody's job at the moment of deletion, and certainly not the build's.

The fix: make "never remove a working page" a failing build

We restored the pages at their original URLs, with the facts re-checked. Then we added scripts/verify-protected.mjs to the end of the build chain:

verify-content → astro build → verify-sitemap → verify-links → verify-protected 

It reads two plain-text files in the repo:

  • seo/protected-urls.txt lists every path that has had at least one impression: 16 months of Google Search Console data pulled from the API, plus pages with Bing impressions or Copilot citations from Bing Webmaster Tools.
  • seo/approved-removals.txt holds dated exceptions in the form path | date | who | reason, written by the founder. Nothing else exempts a path.

For every protected path that isn't an approved removal, the build fails if:

  1. there's no built file for it (/foo/ → dist/foo/index.html);
  2. _redirects or netlify.toml has a 301/302/307/308/404/410 rule that matches it, even one a real file currently shadows (a non-forced Netlify rule can start winning the day someone deletes the file);
  3. netlify.toml sends X-Robots-Tag: noindex for it;
  4. the built HTML has noindex or a canonical pointing somewhere else.

There's a loud SKIP_PROTECTED_CHECK=1 escape hatch for genuine emergencies, which must be logged.

// core of the check (simplified) for (const path of protectedPaths) { if (approved.has(path)) continue; const file = path.endsWith('/') ? join(dist, path, 'index.html') : join(dist, path); if (!existsSync(file)) fail(path, 'no built page'); if (redirectRules.some(r => matches(r.from, path) && r.status >= 300)) fail(path, `rule ${r.status}`); const html = readFileSync(file, 'utf8'); if (/<meta[^>]+robots[^>]+noindex/i.test(html)) fail(path, 'noindex'); const canon = html.match(/<link rel="canonical" href="([^"]+)"/)?.[1]; if (canon && new URL(canon).pathname !== path) fail(path, `canonical → ${canon}`); } 

The second rule: rewrites keep what ranks

Deletions weren't the only leak. On 29 September we also rewrote a comparison page. Its title had been "7 YNAB Price Increase Alternatives in 2026", and "ynab price increase" was the site's #1 query. The rewrite changed the title to "7 YNAB Alternatives After the Price Increase" and cut 20% of the words. Impressions fell from 22 a day to 7 within a week.

So every edit to a protected page now:

  • keeps the URL, the H1 and the exact words of its top queries in the <title>;
  • cuts no more than 15% of the words;
  • gets a row in seo/CHANGES.md.

A weekly job compares each changed page's impressions over the 14 and 28 days after the edit with the 28 days before. If a page drops more than 30% while the site as a whole didn't, the edit is rolled back, title first.

What we'd tell anyone running a small content site

  • "Off-topic" is a brand judgement; "earning traffic" is a fact. Check Search Console before deleting anything.
  • Prefer a 301 to the closest page over a 410, and do neither to a page that ranks.
  • Put the rule in the build, not in a doc. Docs get skipped at 9 pm; failing builds don't.
  • Check Bing too. Bing Webmaster Tools' AI Performance report showed Copilot citing one of our "off-topic" posts 10 times. It had no Google impressions, so a Google-only list would have let it go.

Stillware makes pay-once, offline apps: Zeroed (envelope budgeting) and RankUp Chess. More on how we build: stillwareltd.com.