On 23 August 2026 we ran a technical audit on bloodstone.co.uk. Not a client site. Ours. We build and run websites for other people, which is exactly why the audit was overdue: the cobbler's children, and so on.
Five faults. None of them appeared as an error in Search Console, none broke anything a visitor could see, and all five had been live for months. Every one is checkable on your own site with a single command, so the commands are here. What follows is what ours was doing wrong. We are not claiming anything about yours.
Fault one: the homepage told crawlers we had shipped nothing
The homepage has stat counters that animate up when they scroll into view. Nice effect. They were built with useState(0) as the initial value, which means the server-rendered HTML contained the initial value, not the real one.
The HTML that left our server said:
0+ Projects shipped
0k+ Hours automated
The real figures are 47 and 12,000. Googlebot renders JavaScript and would eventually have seen the right numbers. The crawlers behind AI answers do not. Vercel and MERJ analysed over 500 million GPTBot fetches and found no evidence of JavaScript execution at all; ClaudeBot downloads JavaScript files in roughly a quarter of requests and never runs them. Those crawlers took the raw HTML and recorded that we had shipped zero projects and automated zero hours.
The check, on any page with a number you care about:
curl -s https://yoursite.com | grep -i "projects shipped"
If the number in the response is not the number on the screen, the server is lying on your behalf. The fix was to seed the state with the true value and treat the count-up as progressive enhancement, so the animation only exists for people whose browsers run it.
Fault two: eighteen landing pages that nothing linked to
We have a set of commercial landing pages under /websites/. Eighteen of them had zero inbound internal links. They cross-linked to each other happily, but nothing pointed in from outside the cluster, including their own parent page.
This is the one-liner, and it is the most useful thing in this article:
curl -s https://yoursite.com/websites | grep -c 'href="/websites/'
Ours returned 0. The parent page did not link to a single one of its children.
For a full orphan sweep across the site, crawl your sitemap and count inbound links per URL:
curl -s https://yoursite.com/sitemap.xml | grep -o '<loc>[^<]*' | cut -d'>' -f2 > urls.txt
xargs -n1 -P4 curl -s < urls.txt | grep -o 'href="/[^"#?]*"' | sort | uniq -c | sort -n > inbound.txt
head -40 inbound.txt
Anything in urls.txt that never shows up in inbound.txt is an orphan. Ours were reachable from the sitemap and from nowhere a person would walk. A sitemap entry says a page exists. Internal links say it matters. We had told Google the first thing and never the second.
Fault three: every enquiry was attributed to us
The enquiries table had 19 rows. All 19 recorded the referrer as our own domain.
The client-side code was correct. It read document.referrer, filtered out same-host values deliberately (so an internal navigation does not overwrite the real source), and sent null when there was nothing worth recording. The server then did this:
referrer: body.referrer || req.headers.referer
On a form POST, the HTTP Referer header is the page the form was on. That is always your own site. The fallback silently undid the filtering the client had done on purpose, and every honest null became bloodstone.co.uk.
The cost was not traffic, it was knowledge. We could not answer which pages produce enquiries, which is the question the rest of the audit existed to serve. Check yours with one query:
SELECT referrer, COUNT(*) FROM enquiries GROUP BY 1 ORDER BY 2 DESC;
If your own domain is 100% of the rows, that is a bug, not a finding. We removed the fallback and now store the first-touch landing page and UTM parameters in localStorage over a 90-day window instead. A session-scoped store was the fault, not the fix: organic search frequently converts on a later visit, and anything that dies with the tab records that visitor as direct.
Fault four: the pricing page and its structured data disagreed
The visible pricing page sells monthly plans. The JSON-LD embedded in the same page published fixed one-off prices of £1,500 to £5,000, left over from an earlier version of the offer. Both were public simultaneously, so anything parsing the page for "what does this cost" got two incompatible answers depending on whether it read the copy or the markup.
curl -s https://yoursite.com/pricing | grep -o '"price":"[^"]*"'
Or dump the whole block and read it properly:
curl -s https://yoursite.com/pricing | python3 -c "import sys,re,json; [print(json.dumps(json.loads(m),indent=2)) for m in re.findall(r'application/ld\+json[^>]*>(.*?)</script>', sys.stdin.read(), re.S)]"
Google's structured data guidelines require markup to reflect content that is actually on the page, and mismatches can earn a manual action for spammy structured data. Nobody had flagged ours. It was still wrong.
Fault five: "From £3,500" became exactly £3,500
Four service pages said "From £3,500" in the body copy. The schema template derived its price by stripping non-digits out of that string and publishing 3500 as an exact price. The pages were honest. The machine-readable version was not, and the machine-readable version is what gets quoted back in an answer engine.
We stopped deriving structured data from marketing copy. Where a price genuinely is a range, the schema now models it as one; where it is a starting point rather than a price, we omit it entirely and let the pricing page carry the number.
The five checks
| Fault | Command | Bad result |
|---|---|---|
| Placeholder values in server HTML | curl -s https://site.com | grep -i "projects" | The number is 0 |
| Orphan child pages | curl -s https://site.com/parent | grep -c 'href="/parent/' | Returns 0 |
| Referrer attribution | SELECT referrer, COUNT(*) ... GROUP BY 1 | Your own domain is 100% |
| Price mismatch | curl -s https://site.com/pricing | grep -o '"price":"[^"]*"' | Does not match the page |
| "From" prices in schema | Same command, compare to the copy | An exact number for a range |
All five run in under a minute each. All five found something on our site.
What to do with this
Run the orphan check first. It is one command, it needs no access to anything, and a 0 tells you that a section of your site exists only for your sitemap. Then diff your rendered page against curl output on the two or three pages that carry the numbers you would want an AI answer to repeat.
We found these because we finally treated our own site as a client project rather than a side effect of everything else. If you would rather someone else ran that pass, that is part of how we build and manage sites and it sits alongside the rest of what we do. Otherwise, take the commands and keep the findings to yourself. Or tell us what you found; we will happily compare notes.
Need the site itself? We build and manage websites from £125/month — website design london, website design manchester and website design birmingham.
Need help with this?
Bloodstone Projects helps businesses implement the strategies covered in this article. Talk to us about Website Build & Manage.
Get in touch