Table of contents
A soft 404 is a page that says "not found" while the status line says 200. On the six platforms below, the default behaviour ranges from a correct 404 with a dead-end body (Webflow, Wix, Docusaurus build) to a 200 for every made-up URL (Lovable on React + Vite, Docusaurus npm start, an Astro or Next.js route that finds no data). AIScan check E1 (Correct 404 handling) grades both the status code and the body, so each platform needs its own fix and the same curl to prove it.
TL;DR
- The test is one command.
curl -s -o /dev/null -w '%{http_code}\n' https://yoursite.com/this-page-does-not-exist-9f3amust print404, and the body must hold at least two links and the site name. - Test a dynamic URL too.
/blog/zzzexercises the route you fixed. A random path often hits the framework's own handler and passes while the soft 404 hides in your template. - Never redirect dead URLs to the homepage. A 301 to
/ends in a 200 page that does not match the request, which Google's status code guidance treats as a soft 404. - Fastest check of all:
npx aiscan-cli yoursite.com, then read E1, Correct 404 handling under Discoverability.
Contents
| Platform | Jump to | Default behaviour on a missing URL |
|---|---|---|
| Astro | #astro | 404 for unmatched URLs, 200 when a dynamic route finds no data |
| Docusaurus | #docusaurus | 200 under npm start, 404 from the production build, no links in the body |
| Lovable | #lovable | 200 on React + Vite, 404 on TanStack Start |
| Next.js | #next-js | 404, but 200 when loading.tsx streams first |
| Webflow | #webflow | 404, stock page has no links; reserved /404 returns 200 |
| Wix | #wix | 404, script-built shell with no links |
What every platform has in common
E1 reads the status line first and the body second. Full credit needs a 404 and a body that names the site and links somewhere useful. RFC 9110 defines 404 as exactly the "this resource does not exist" signal, and an agent that reads only the status line treats a 200 as a real page. The same four steps apply everywhere, so each section below only says what is different:
- Return the status. Only a server (or the host) can send 404. A client-side router cannot.
- Give the body a way out. One heading, one sentence naming the site, and plain text links to the homepage, your main sections and the sitemap.
- Check the layer in front. A CDN or proxy rule that swaps a 404 for a friendly 200 hides a correct origin. Test the public domain, not
localhostor the preview address. - Verify by hand. The commands are in the shared check at the end.
What I measured on each platform
First-party probes, curl with a made-up path on live sites or local builds, 10 to 15 October 2026.
| Platform (version or sample) | Date | Made-up URL returns | Body |
|---|---|---|---|
Astro 7.3.8, @astrojs/node 11.1.7, four route patterns | 11 Oct | 404 for three patterns, 200 for the route that prints "not found" | Your 404.astro only when you rewrite |
Docusaurus 3.10.2, classic site, 5 URLs | 14 Oct | 200 under npm start (same 1,873-byte shell), 404 after build and serve | 11 links, none inside <main> |
| Lovable, 3 published sites | 13 Oct | 200 on both React + Vite sites, 404 on the TanStack Start site | Vite: empty <div id="root"></div>, 0 links. TanStack: 1 link |
| Next.js 16.4.0, App Router, 2 dynamic routes | 10 Oct | 404, 404, and 200 for the segment with loading.tsx | not-found.tsx content |
| Webflow, 2 published sites | 12 Oct | 404 on both; /404 itself 200 on the designed-page site | Stock page: 906 bytes, 0 links |
Wix, 9 sites (5 on wixsite.com, 4 on custom domains) | 15 Oct | 404 on all nine | 2.2 to 2.5 KB script shell, 0 links, noindex |
Astro
Astro sends 200 unless you change it. A missing database row or an empty getEntry() result is not an error to Astro, so a template that prints "Post not found" is a successful response. Astro's pages guide covers the file for unmatched URLs: a 404.astro or 404.md in src/pages builds to 404.html. It says nothing about routes that match a pattern and then find no data, and that gap is where the soft 404 lives. Tested on 11 October 2026.
Step 1: add src/pages/404.astro with a heading, a sentence naming the site and links to home, blog and /sitemap-index.xml.
Step 2: reproduce it. I built a server-rendered project (output: 'server', Node adapter) with one lookup route written four ways:
| Route | Miss handling | Status | Body |
|---|---|---|---|
| a | Prints "Post not found" | 200 | Not-found text, soft 404 |
| b | Astro.response.status = 404 | 404 | Not-found text |
| c | return Astro.rewrite('/404') | 404 | Your 404 page |
| d | return new Response(null, { status: 404 }) | 404 | Empty |
Route a is the failure. Routes b to d fix the status, but only c also serves the recovery links, so it is the one that passes both halves of E1.
Step 3: fix the dynamic route. In src/pages/blog/[slug].astro, put the check before any other work:
---
const { slug } = Astro.params;
const post = posts.find((p) => p.slug === slug);
if (!post) return Astro.rewrite('/404');
---
Astro.rewrite() keeps the visitor on the original URL and renders the other page's content.
The trap in Astro's own routing guide. The routing guide shows a catch-all route that ends a miss with return Astro.redirect("/404"). I tested that exact pattern. The missing URL answers 302 with location: /404, and a client that follows it ends on a 404 at a different address. Crawlers that do not follow redirects record a 302. Use Astro.rewrite('/404').
Static builds and hosts. A fully static site has no dynamic miss, but the host must serve 404.html with a 404 status. Both the dev server and astro preview did that for me, so a 200 on your live site comes from the host. On Cloudflare Workers, Astro's deploy guide says you must set not_found_handling to serve a custom 404 page:
{ "assets": { "directory": "./dist", "not_found_handling": "404-page" } }
Cloudflare's single-page-application value serves /index.html for unknown navigation requests, which is a soft 404 for every bad URL.
Astro edge cases. Prerendered routes with getStaticPaths() only build the paths you return, so other URLs get the plain 404 page. Middleware that rewrites or catches errors can override a status, so test through the live domain. For the rest of the Astro setup, see AI readiness setup on Astro.
Docusaurus
Docusaurus builds a real 404.html, and the place you test it hides it. Run npm start, type a made-up URL and you get a friendly page with a 200 status. The production build and each host behave differently. Tested on 14 October 2026 with Docusaurus 3.10.2, a fresh classic site and Node 22.22.0.
Why npm start returns 200 for everything. The dev server hands every path to the client-side router. I started docusaurus start on port 3200 and requested five URLs that do not exist, including /zzz-nope and /docs/zzz. Every one returned 200 text/html with the same 1,873-byte shell. Nothing about your site is broken; you are testing the wrong server.
Step 1: test the production build.
npm run build
npm run serve -- --port 3100
curl -s -o /dev/null -w '%{http_code} %{content_type}\n' localhost:3100/zzz-nope
curl -s -o /dev/null -w '%{http_code}\n' localhost:3100/docs/zzz
Both returned 404 with the body of build/404.html. A missing /img/zzz.png and a missing /assets/js/zzz.js also got 404. If your own site returns 200 here, the cause is in your config. If it returns 404 locally and 200 live, the cause is your host (Step 3).
Step 2: give the 404 page something to follow. The default page says only that you should "contact the owner of the site that linked you to the original URL". In my build the whole page held 11 links, all in the navbar and footer, and none inside <main>. Docusaurus marks NotFound/Content as unsafe to swizzle, so the eject needs --danger:
npm run swizzle @docusaurus/theme-classic NotFound/Content -- --eject --danger
That writes src/theme/NotFound/Content/index.js. Replace its body with real links:
import React from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import Heading from '@theme/Heading';
export default function NotFoundContent({className}) {
return (
<main className={clsx('container margin-vert--xl', className)}>
<Heading as="h1" className="hero__title">Page not found</Heading>
<p>This page is not on example.com. Try one of these instead:</p>
<ul>
<li><Link to="/docs/intro">Documentation</Link></li>
<li><Link to="/blog">Blog</Link></li>
<li><a href="/sitemap.xml">Sitemap</a></li>
</ul>
</main>
);
}
After npm run build, build/404.html had one <h1> and three links inside <main>, and /zzz still answered 404.
Why not src/pages/404.js? It works and replaces the built-in page, but Docusaurus treats it as an ordinary page. The build produced build/404/index.html and sitemap.xml listed /404. With the swizzle the sitemap held no 404 entry. A sitemap that advertises your error page is the opposite of what E1 confirms.
Step 3: check what your host does with a miss. Docusaurus only emits 404.html; the host decides whether to use it.
- GitHub Pages serves a
404.htmlat the root of the publishing source. A made-up path onfacebook.github.ioreturned404 text/htmlon 14 October. - Cloudflare Pages looks for the closest
404.htmlup the directory tree. Its docs add that without a top-level404.html, Pages assumes a single-page app and serves the root for any unknown path. Docusaurus always emits that file, so keep it in the output directory. - Netlify needs no rule for a
404.html. Its redirect docs describe the automatic custom 404 page. The trap is a rule such as/* /index.html 200, the single-page-app recipe, which turns every bad URL into a 200. I did not deploy to Netlify; this comes from its documentation.
docusaurus.io itself passes: /zzz-nope-9f3a and /docs/zzz-nope both returned 404 text/html.
Docusaurus edge cases. Trailing slashes did not change the result (/docs/zzz/ returned 404). With baseUrl set to a repo name on GitHub Pages, URLs outside that folder never reach your 404.html. See also AI readiness setup on Docusaurus.
Lovable
Lovable can return a real 404 or a soft 200 for the same missing URL, depending on the stack the project was built on. I probed live Lovable sites on 13 October 2026: a project on the newer TanStack Start template answered 404, and two older React + Vite projects answered 200 for a path that does not exist.
| Site stack | /zz-nope-123 | /zz-nope-123.md |
|---|---|---|
| React + Vite (two sites) | 200, ~3.3 to 3.7 KB, <div id="root"></div>, 0 links | 404, text/plain, 9 bytes |
| TanStack Start (one site) | 404, text/html, ~12.9 KB, rendered "Page not found" | 404 |
The Vite sites show why this is a soft 404: the server does not know which routes exist, so it sends the same index.html for every path and lets React Router decide in the browser. Note the .md row. The platform answers that one itself, so a probe with only a .md path would wrongly tell you the site is fine. Lovable's own docs draw the same line: new apps created from 13 May 2026 use server-side rendering, while older apps serve pre-rendered copies only to verified search and AI crawlers.
Step 1: find out which stack you are on. Ask the project chat "What stack is this project on?", which Lovable's upgrade documentation suggests. Or view source on the live site: an empty <div id="root"></div> means React + Vite.
Step 2: migrate a Vite project to TanStack Start. Only a server can set a status code, so this is the real fix. Lovable documents three ways in:
- In the project chat, type
/, choose Migrate to TanStack Start and confirm. - Open project settings, go to General > Project actions and select Migrate.
- Ask Lovable in plain words to upgrade the app to TanStack Start.
Lovable states that most upgrades cost 10 to 35 credits, that the live site keeps serving the old version until you publish again, and that you can revert from version history. Test in the preview, then click Publish. If the migrate option is missing, the project is probably on TanStack Start already.
Step 3: give the 404 page links, and cover missing records. On TanStack Start the router throws a not-found error for any URL that matches no route, and the status follows. What is left is the body. On the site I probed, the 404 page had exactly one link, to /. TanStack Router's documentation lets you set one component for the whole app through defaultNotFoundComponent:
const router = createRouter({
defaultNotFoundComponent: () => (
<div>
<h1>Page not found</h1>
<p><a href="/">Home</a> · <a href="/pricing">Pricing</a> · <a href="/blog">Blog</a> · <a href="/sitemap.xml">Sitemap</a></p>
</div>
),
})
Dynamic routes need one more rule. For a record that does not exist, such as /posts/missing-slug, throw notFound() in the route's loader. That call turns a missing record into a not-found response instead of an empty page.
Step 4: if you must stay on React + Vite. A static host cannot send a 404 for a client-side route. Google's JavaScript SEO guide names two options for single-page apps. The redirect to a /not-found URL that returns 404 does not work here, because every path returns 200. That leaves adding <meta name="robots" content="noindex"> when the route is missing:
useEffect(() => {
const meta = document.createElement("meta");
meta.name = "robots";
meta.content = "noindex";
document.head.appendChild(meta);
return () => meta.remove();
}, []);
This keeps Googlebot from indexing the empty page. It does not change the status code, so E1 still reports a failure. Migrating is the only complete fix.
Lovable edge cases. Several lovable.app sites I tried answer with a 302 to their own domain, so probe the domain visitors use. Lovable warns that some browser-only libraries can break server rendering after the upgrade, so open every page in the preview first. Check one logged-out visit to a private URL after migrating. See also AI readiness setup on Lovable.
Next.js
Next.js has a built-in 404 path, and it works until a loading.tsx file sits in the same route segment. Then a missing record returns HTTP 200 with a "not found" page and a noindex tag. Everything here was measured on Next.js 16.4.0 (App Router) on 10 October 2026.
Why the status goes wrong. HTTP sends the status line before the body. The not-found.js reference says that before streaming starts Next.js returns a 404, and after streaming starts the status stays 200 while the not-found UI renders. A loading.tsx file wraps the page in a Suspense boundary, so the shell streams first and the 200 is already on the wire when notFound() throws. The loading.js reference adds that the noindex tag keeps these pages out of search results, which helps Google but does nothing for an agent that never reads the HTML head.
Step 1: reproduce it. Two dynamic routes call notFound() for unknown IDs; only app/shop/[id]/ has a loading.tsx:
app/not-found.tsx
app/blog/[slug]/page.tsx # notFound() for unknown slug
app/shop/[id]/page.tsx # notFound() for unknown id
app/shop/[id]/loading.tsx
After npx next build and npx next start:
curl -s -o /dev/null -w '%{http_code}\n' localhost:3000/nowhere # 404
curl -s -o /dev/null -w '%{http_code}\n' localhost:3000/blog/nope # 404
curl -s -o /dev/null -w '%{http_code}\n' localhost:3000/shop/nope # 200 <- soft 404
The /shop/ miss returns 200 and still carries <meta name="robots" content="noindex"/>.
Step 2: write a not-found page agents can use. In app/not-found.tsx, render a heading, a sentence naming the site and a list of Link components to home, blog and /sitemap.xml. It is a Server Component by default, so the links appear in the raw HTML, and Next.js renders it for both unmatched URLs and notFound() calls.
Step 3: pick the fix for the 200.
- Branch A: drop
loading.tsx. Deleteapp/shop/[id]/loading.tsx. In my test the sibling/blog/[slug]route, which had none, returned a true 404. - Branch B: keep the loading state. Decide before streaming begins, which the
loading.jsreference points to Proxy for. Createproxy.tsin the project root, next toapp/:
import { NextResponse, type NextRequest } from 'next/server'
const known = new Set(['1', '2']) // your real IDs or a fast lookup
export function proxy(req: NextRequest) {
const id = req.nextUrl.pathname.split('/')[2]
if (!known.has(id)) {
return NextResponse.rewrite(new URL('/not-a-route', req.url))
}
}
export const config = { matcher: '/shop/:id' }
The rewrite sends the request to a path no route matches, so Next.js serves app/not-found.tsx with a real 404. Keep the lookup cheap, because the docs warn against fetching full content in Proxy. In Next.js 16 the file is proxy.ts and the export is proxy; the build output still labels it "Middleware". After this change /shop/nope returned 404 with the recovery links, and /shop/1 still returned 200 with its loading state.
Next.js edge cases. A notFound() call nobody awaits throws where nothing catches it, and no not-found UI renders. Re-run the /shop/nope check after adding any new loading.tsx. For why agents treat a 200 error page as content, read the soft-404 discovery problem, and for the content-negotiation side of a good 404 body, correct 404 handling for AI agents. The rest of the setup is in AI readiness setup on Next.js and the Next.js platform page.
Webflow
Webflow already does the hard part: ask a published site for a URL that does not exist and the server answers with a real 404. What it does not do is make that page useful. Tested on 12 October 2026 against two live Webflow-hosted sites, one with a designed 404 page and one still on the platform's stock error page. A made-up path and a made-up .md path both returned 404 on both, so Webflow does not soft-200 one extension and hard-404 another. The difference is the body:
| Site | Status for /zz-nonexist-123 | Body |
|---|---|---|
Stock page (cloneable.webflow.io) | 404 | 906 bytes, one <h1>, 0 links |
| Designed page | 404 | ~50 KB, navigation and footer links |
Step 1: design a 404 page that links somewhere. Webflow's help article on the 404 error page says it sits under Utility pages in the Pages panel. In the Designer: click Pages, expand Utility pages, click 404, add a heading, one sentence naming your site, and plain links to the homepage, main sections and sitemap, then Publish. Use static elements (a Navbar component, a text link or a Link block). The same article states you cannot add Collection lists to a 404 page, because it has to work even if the CMS is down, so hard-code the links. The slug is reserved at /404 and cannot be changed, and the page cannot be saved as a draft.
Step 2: keep /404 itself out of the index. On the designed-page site, curl -I https://yoursite.com/404 returned 200, while every other unknown URL returned 404. The reserved slug is a real page, so it answers like one, and a crawler that finds a link to /404 sees a 200 page that says "not found". Open the 404 page, go to Page settings > SEO settings, switch Sitemap indexing off and publish. Webflow documents that this adds <meta content="noindex" name="robots"> to the page, and that it works only if the auto-generated sitemap is enabled. On the live site I tested, /404 was not listed in sitemap.xml.
Step 3: redirect URLs that used to exist. Webflow's redirect documentation requires a paid Site plan, or a site in a Workspace with a paid plan. Go to Site settings > Publishing > 301 redirects, enter the old path, enter the new path in Redirect to path, click Add redirect path and publish. To redirect an existing static page, Webflow says you must first delete it, save it as a draft or change its slug. When you change a slug in Page settings, tick Permanently redirect the old URL to this new one. Do not use one wildcard rule to push every unknown URL to the homepage: a redirect answers 301, so a missing page would stop returning 404 at all.
Webflow edge cases. A reverse proxy or CDN in front of Webflow can replace the 404 with its own 200 page, so test the public domain and not only the .webflow.io address. Webflow notes that 301 redirects do not apply to localised slugs, so /old-url and /es/old-url need separate rules. A CMS item must be archived, deleted, unpublished or re-slugged before a redirect rule for its URL applies. Webflow's help center returned 403 to my automated fetch on 27 October 2026, so the click paths above are as I recorded them on 12 October. See also AI readiness setup on Webflow.
Wix
Wix gets the status code right and leaves the page empty. I requested a made-up path on nine Wix sites, five on wixsite.com addresses and four on custom domains, on 15 October 2026. All nine answered 404 text/html, between 2,204 and 2,505 bytes, with <meta name="robots" content="noindex"> and the title "404 Error: Page Not Found". Inside the body there is one <div id="root"></div> and a script that draws the message. There were zero <a> tags in any of them, so a crawler or agent that does not run scripts finds nothing to follow.
AIScan scored one of those sites (a Wix manufacturer's site) as a pass on E1, with the evidence line HTTP 404, 2206 bytes. The status is right; what the default page cannot give a script-less reader is a way out. None of the nine sites had a custom 404 page, so I could not measure one. Step 1 builds it and Step 3 is the test.
Step 1: add a 404 page with links. Per Wix's help article, a custom 404 page needs a premium plan. In the Editor: click Pages & Menu, Add Menu Item, New Page, (404) Error Page, hover a template and click Add Page. Edit the text, add your site menu and links to the sections visitors look for, then Publish. The page URL always contains error404, though you can change the title in Page Settings. Name the site in the sentence and use text links, not only buttons or an image.
Step 2: do not redirect dead URLs to the homepage. The Wix URL Redirect Manager creates 301 redirects, and it is easy to point every retired URL at /. Redirect an old URL to its closest replacement, and when there is none, leave it to 404. Limits from Wix's redirect article:
- 301 redirects only work with a custom domain; a free
wixsite.comaddress cannot have them. - You cannot create a redirect whose source is your homepage.
- A site can hold at most 5,000 redirects.
- Wix creates a redirect automatically when you change an Editor page slug. Blog pages are the exception: change a blog slug and you must add the 301 yourself, or the old URL 404s.
Step 3: check the layers in front of Wix, then verify. A CDN or proxy in front of a custom domain can replace a 404 with a friendly 200. Run the shared check below against the live domain. On the default Wix page I measured a link count of 0. If the status prints 200 after you add your page, Wix support is the next stop. I found no live Wix site with a custom page to measure, so the command is the test, not my word. See also AI readiness setup on Wix.
Verify the fix on any platform
With AIScan (default): run npx aiscan-cli yoursite.com, open Discoverability and find E1, Correct 404 handling. It should read pass, with the 404 body counted. The Discoverability checks page explains the grading.
By hand with curl:
curl -s -o /dev/null -w '%{http_code} %{content_type}\n' https://yoursite.com/this-page-does-not-exist-9f3a
curl -s https://yoursite.com/this-page-does-not-exist-9f3a | grep -a -c '<a '
curl -sI https://yoursite.com/this-page-does-not-exist-9f3a | grep -i '^location:'
Expected: 404 text/html, then a link count of at least 2, then no location: line. Test one URL that matches a dynamic pattern (such as /blog/zzz) as well as one that matches nothing, and one path under a subfolder such as /docs/, because a rewrite rule scoped to a folder only shows up there. Republish first if the output looks old.
Next step
Run the scan on your own domain and confirm E1, then read the rest of the series in AIScan's guides. For why agents treat a 200 error page as content, see the soft-404 discovery problem, and for what a good 404 body looks like to a machine, correct 404 handling for AI agents.
Updated on 27 October 2026. This guide replaces six single-platform posts; their redirects point to the sections above.
Frequently asked questions
Why does my site return 200 for a page that does not exist?
The server is not sending a 404 for that route. Common causes: an Astro or Next.js route that renders a not-found template without setting the status, a loading.tsx that streams before notFound() runs, a Lovable React + Vite app that serves one index.html for every path, or a host rule that rewrites misses to index.html. Find your platform in the contents table above.
What does AIScan check E1 need in the 404 body?
A body that names the site and links somewhere useful, such as the homepage, blog or sitemap. An empty page or a script shell with no links fails the body half of E1 even when the status is 404.
curl returns 404 for a random path but 200 for a real dynamic URL that does not exist. Is that a bug?
No, it is the typical soft 404. A random path matches no route and gets the framework 404. A dynamic URL such as /blog/zzz or /shop/nope matches a route that rendered successfully. Handle the miss inside that route.
Why does npm start return 200 for every URL on Docusaurus?
The dev server hands every path to the client-side router. Test with npm run build and npm run serve instead. If the live site also returns 200, your host is rewriting misses to index.html.
Astro.redirect('/404') gives me a 302. What should I use?
Use Astro.rewrite('/404'). A redirect sends a 302 with a location header and the 404 only appears after the client follows it. A rewrite keeps the URL and returns 404 with your 404 page.
Why does my Next.js not-found page return 200?
The status line was sent before notFound() ran. A loading.tsx in the same segment streams the shell first, so the status stays 200. Remove loading.tsx or decide in proxy.ts and rewrite to a path no route matches.
Does a noindex tag fix a soft 404?
It keeps the page out of Google's results, but an agent that reads only the status line still sees 200. E1 grades the status code, so noindex does not make the check pass.
How do I know whether my Lovable project is on TanStack Start?
Ask the project chat what stack it uses, or view source on the live site. An empty div with id root and no content means React + Vite. If Migrate to TanStack Start is missing in the project actions, the project is probably on TanStack Start already.
Does a noindex meta tag fix the soft 404 on Lovable React + Vite?
It stops Googlebot indexing the empty page, but the status stays 200, so E1 still fails. Migrating to TanStack Start is the only complete fix.
Why does yoursite.com/404 return 200 on Webflow when other missing URLs return 404?
/404 is the reserved slug of a real utility page, so it answers like a page. Switch Sitemap indexing off in the 404 page's SEO settings and publish, which adds a noindex robots tag.
Can I put a CMS Collection list on my Webflow 404 page?
No. Webflow states Collection lists cannot be added to 404 pages because the page must work even if the CMS is down. Use static links.
Does Wix return a real 404 for a page that does not exist?
Yes. On nine Wix sites tested on 15 October 2026 a made-up path returned 404 text/html with a noindex tag. The weak spot is the body: a script-built shell with no links in the HTML.
Why is there no (404) Error Page option in my Wix Pages & Menu panel?
Wix says a custom 404 page needs a premium plan. Check your plan, then retry Pages & Menu, Add Menu Item, New Page and look for (404) Error Page.
Can I redirect every old URL to my homepage to avoid 404s?
You can, but it is a bad idea. Google treats a 200 whose content does not match the request as a soft 404. Redirect to the closest matching page, or let a retired URL return 404.
Why does my host show 200 when the local build shows 404?
The build is fine and the host is not. Look for a rewrite such as /* /index.html 200 in _redirects, netlify.toml or CDN rules, a Cloudflare Workers not_found_handling of single-page-application, or a missing top-level 404.html.

About the author
M Asif Rahman
Entrepreneur, curious about AI
I have been building companies for a long time, and lately most of my curiosity goes to AI. AIScan is a small personal project I built on a flight from Dubai to Narita. I use it to understand how ready the web is for AI agents, and I share what the scans teach me.
Related guides
AI readiness setup guide by platform (2026)
Verified 17, 18, 20 and 22 October 2026. Updated on 22 October 2026. An AIready site needs six things: a robots.txt that names AI crawlers D1, B2, a sitemap D2, a declared feed E5, an llms.txt C2,…
llms.txt on Astro, Docusaurus, Framer, Ghost, Hugo, Next.js, Replit, Shopify, Squarespace, Webflow and Wix
Updated on 26 October 2026. Every platform section was checked on the dates shown inside it. Squarespace, Webflow and Wix joined this guide on 26 October 2026, so all eleven platforms that had their…
One h1, title, meta description and JSON-LD on Ghost, Lovable, Next.js, Replit, Webflow, Wix and WordPress
One page needs one <h1, a <title, a meta description and a JSONLD block, and AIScan check C3 structured HTML passes only when all four are in the HTML the server returns. Who writes each of the four…
