What a soft 404 is, and why your site may have one without you knowing

A soft 404 is a page that does not exist but answers 200. We had one on tresojotas.com until October 2026, alongside a near-empty HTML for crawlers. What it is, how to spot it and how we fixed it.

A soft 404 is a page that does not exist but answers as though it did: the server returns a 200 status ('all fine') and then shows an error message, an empty page or, worse, the homepage. To a person it is a detail. To Google it is a contradiction: you are saying the page exists and, in the same breath, that there is nothing to show. We had one on tresojotas.com until 27 October 2026, and it came with two cousins almost nobody checks for: a near-empty HTML for crawlers, and several sections telling Google they were the homepage. Here is how we found them, what caused them and how we fixed them, so you can check yours in ten minutes.

We found it by going over our live site the way a robot would, not the way a visitor does. In the browser everything looked fine. But asking curl for a made-up address (tresojotas.com/this-does-not-exist) got a 200 back, with the entire homepage attached. The cause is the rule almost every single-page application (React, Vue and the like) ships with: 'if the file does not exist, serve index.html and let the app decide what to show'. The app did show its 'not found' page, but the status code had already gone out: 200. According to Google's documentation, when a 2xx response carries an empty page or an error message, Search Console flags it as a soft 404, and URLs that return a 4xx are dropped from the index. In other words, we were asking Google to consider indexing every broken address, every old link from our WordPress days and every route anyone mistyped.

The lesser-known cousin lived in the folders. Our /blog, /en, /studio, /en/blog and /en/work routes were returning the Spanish homepage with a canonical tag pointing at the root. It came down to an Apache rule: because a blog/ folder exists with the articles inside it, the server treated /blog as a directory, found no index there and fell through to the same catch-all. In English it was worse: the /en homepage was telling Google 'my official address is tresojotas.com', in Spanish. None of this shows up in a browser, because the app boots, reads the URL and paints the right section. It only shows in the HTML that arrives before any JavaScript runs, which is all that many crawlers ever read.

  1. A real 404 for what does not exist The last rule in the .htaccess changed from 'everything else goes to index.html' to 'anything that is not a real file answers 404'. The error page is generated at build time (dist/404.html) with the site menu and a noindex tag, so someone arriving via a broken link sees a useful page, and Google sees what it should: nothing here. We check it every week with a made-up address: it has to come back 404.
  2. One HTML file per route, with the actual content We were already prerendering (one HTML file per route, with its own title and description), but the body arrived almost empty: around 200 words per page, the shell of the app. The prerender script now bundles the same data the site uses (the case studies and the articles) with esbuild and writes the full text into each HTML file. Measured today on the live site, without running JavaScript: the Polish case study arrives with close to 800 words, the article on migrating from WordPress without losing SEO with around 1,200, and the work and blog listings with every one of their links.
  3. Every section serves its own HTML A new rule checks whether blog.html exists before treating /blog as a folder. So /blog, /en and /studio now deliver their own page, with their own title and the right canonical. As a side effect, the English version stopped declaring itself a copy of the Spanish one.
  4. Check like a robot, not like a visitor The browser lies by omission: it runs the JavaScript and patches everything up. To see what a crawler sees, you have to request the page without running it (curl, or 'view page source' in the browser) and look for your own copy. It is one of the checks in Radiografía, our free audit: 'content visible to robots'.

Why it matters more now than two years ago: AI assistants. Googlebot has executed JavaScript for years, so a single-page application does get indexed in the end, even if it takes longer. The crawlers behind ChatGPT, Claude and Perplexity do not. In December 2024 Vercel and MERJ analysed AI crawler traffic across their network: GPTBot made 569 million requests in a month and Claude 370 million; they download JavaScript files (ChatGPT in 11.5% of its requests, Claude in nearly 24%) but do not execute them. Googlebot and Gemini's crawler do render. To an AI assistant, your website is exactly the HTML that arrives on the first request. If that HTML is 200 words of shell, that is all it will ever know about you when someone asks.

And the results? Only a few weeks have passed, too soon to talk about rankings, and we are not going to invent them. What we can say is what changed on the day: a made-up URL answers 404, every route delivers its own HTML with its full content, and Radiografía's audit of our homepage scores 100 out of 100 today. What we expect to see in Search Console over the coming months is fewer pages under 'soft 404' and 'crawled, currently not indexed', and more crawling spent on what matters.

Three checks for your own site, in the order they hurt most. First, request a made-up address on your domain and look at the status code (in a terminal, curl -sI yourdomain.com/does-not-exist, or any online header checker): it has to be 404 or 410, not 200. Second, open the page source of your homepage and of a product or service page and search for a sentence of your own copy: if it is not there, your pages depend on JavaScript and AI assistants are not reading them. Third, in Search Console's Page indexing report, see whether 'soft 404' shows up in numbers you were not expecting. If your site is a single-page application built a couple of years ago, there is a fair chance it fails at least one of the three. We were failing two, and we build websites for a living.

In short: a soft 404 is telling Google 'it exists' when it does not, and it is fixed on the server, with a real 404 and a useful error page. Empty HTML is its quiet cousin, and today it costs you more with AI assistants than with Google. Both take ten minutes to check and nothing has to be touched. If you would rather we looked, run your domain through Radiografía or tell us what site you have.