A 60-Second Soft 404 Test (We Missed Ours for Weeks)

On September 19 we found that every URL that doesn’t exist on this site was returning HTTP 200 and a copy of the homepage, instead of a 404. We had already run Screaming Frog’s free crawler and Semrush’s free site audit on the site, weeks earlier, and neither said a word about it. This page is the test that finds the problem, what happened on our site, and what we changed. The tests below are ones you can run yourself.

The test (about a minute)

You are asking your own site for a page that shouldn’t exist. Pick a made-up path nobody would ever link to:

curl -s -o /dev/null -w "%{http_code}\n" https://your-site.example/this-page-does-not-exist-xyz-123

You want 404. Anything else is worth a closer look, and 200 is the classic soft 404: the server says “success” for a page that isn’t there, whether it shows visitors an error message or, as ours did, just some other page. Google reports these as “soft 404” errors in Search Console’s page indexing report (Google’s help page for that report).

A status code alone can hide the worst version of this, where the server hands back your homepage for everything. So compare what came back:

curl -s https://your-site.example/this-page-does-not-exist-xyz-123 | grep -o "<title>[^<]*</title>"
curl -s https://your-site.example/ | grep -o "<title>[^<]*</title>"

If the two titles are identical, that’s the homepage answering for every address. Two variants are worth adding, because they’re what a mistyped link looks like in the wild: the same made-up path with a trailing slash, and a real page with the wrong extension (for example /about.htm when the page is /about/).

What happened on this site

Why the crawlers missed it

A crawler asks for URLs it has discovered, through links and the sitemap. Every one of those URLs exists, so nobody ever asks the question “what does this site do with an address that doesn’t exist?” That’s our explanation of the mechanism, not something either vendor has told us.

What we ran When Flagged the soft 404? Why we think not
Screaming Frog (free crawl) Around Aug 21 No Follows links it finds
Semrush (free site audit) Aug 22 No Same reason
Our own content check Every publish Not applicable Lints our content files, never requests a live URL

We can’t say the same about Ahrefs’ Site Audit. Its first crawl of the site happened after the fix, so we have no evidence either way about what it would have done.

The fix

One small file, an ordinary Astro page at src/pages/404.astro:

---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="Page not found — Solo Stack Lab" description="This page doesn't exist. ...">
  <h1>Page not found</h1>
  <p>There's no page at this address. ...</p>
  <ul>
    <li><a href="/reviews/">All firsthand reviews</a></li>
    <li><a href="/">Home</a></li>
  </ul>
</BaseLayout>

The build turns it into 404.html, and Cloudflare Pages serves that file for unknown paths. Our sitemap plugin leaves it out of the sitemap (we checked: zero mentions of it). Re-running the September 19 probes on September 25:

Request Before (our Sep 19 note) After (Sep 25)
/wp-login.php 200, homepage 404, 4,350 bytes
/llms-full.txt 200, homepage 404, 4,350 bytes
/a/b/c/d.html 200, homepage 404, 4,350 bytes

The made-up path from the test above (/this-page-does-not-exist-xyz-123) now returns 404 as well, and its title is “Page not found — Solo Stack Lab” instead of the homepage’s title, which is the second half of the test. We don’t have a recorded “before” for that exact path; our September 19 note covers the three paths above and says any unknown path behaved the same way.

Make it a standing check

We didn’t want to depend on remembering to look. A reader on Indie Hackers suggested asserting on status codes for the requests a broken link or a wrong config would make, instead of running a full crawl. We built that into a script we can run after any deploy (we haven’t wired it into an automatic step yet). It checks that:

To make sure it could fail, we pointed it at a throwaway server built to reproduce both of our September bugs. It failed 12 of the 14 checks that apply to a non-production address (the http and www checks only run against the real domain), and it passes 17 of 17 against the real site.

What we don’t know

For a related trap in the same family, see how to sanity-check your first backlink report, which covers the www mistake we also found that week.

Tools mentioned in this guide

ToolBest forStarting priceFree trial
Screaming Frog SEO Spiderfree, no-expiry technical SEO audits for small sites, catching title/meta issues static pages miss from content-only checks£199/yr for the licensed version (removes the 500 URL cap, adds crawl saving and advanced features)Free forever for up to 500 URLs per crawl, no signup, no email, no credit card required (confirmed firsthand Aug 2026). Desktop app (Windows/Mac/Linux), not a web dashboard.
Semrushfree technical site audits without a card, cross-checking AI-search-visibility findings against a second tool$139.95/mo (Pro, monthly - not tested firsthand)Free, no-signup, no-card tier covers Site Audit (up to 100 pages), AI Visibility, and limited Position Tracking (confirmed firsthand Aug 2026). The 7-day trial of paid plans requires a credit card, and its money-back guarantee explicitly excludes month-to-month billing - the same refund-guarantee trap shape found in the Surfer SEO review. Stayed in the free tier deliberately for this review.