Table of contents
- Quick summary
- Why a missing file on Replit returns 200 instead of 404
- Find out which deployment target is serving your app
- Path 1: Static Deployment, when your app can use one
- Path 2: Autoscale, which is what Agent gives you
- The AI bot rules that actually decide anything
- What twelve published Replit apps look like right now
- Confirm the files reached a crawler
- When the Replit app is only the front door
- Where AIScan fits, and where it doesn't
- Your next move on Replit
Verified 2 September 2026. Every command, config key and file path below was run against live Replit apps or fetched from Replit's own documentation on that date.
On most platforms a missing robots.txt returns 404 and you find out immediately. On a Replit app built by Agent it returns HTTP 200 with your homepage HTML inside it. Twelve published Replit apps were probed on 2 September 2026 and ten answered 200 for a path that does not exist. Six returned the same HTML shell for /robots.txt, /sitemap.xml, /llms.txt, /agents.md and a nonsense URL, byte for byte identical each time.
That is the whole problem here, and it takes about fifteen minutes to fix once you know which deployment target is serving your app.
Quick summary
| What you want | Check | What decides it on Replit | Where the fix goes |
|---|---|---|---|
| robots.txt exists | D1 | Whether the file is in the served public directory | client/public/robots.txt |
| AI crawler rules | B2 | The contents of that same file | Named groups in robots.txt |
| Sitemap reachable | D2 | A real file, or a route registered before the catch-all | server/routes.ts |
| llms.txt reachable | C2 | Same as robots.txt | client/public/llms.txt |
| Missing paths return 404 | E1 | The Express catch-all in server/vite.ts | A 404 handler, or Static Deployment |
| Crawlers see body copy | C3, E3 | React renders in the browser, so the shell is empty | Prerender, or move content to Static |
| Feed declared | E5 | Nothing native. You write the route | server/routes.ts plus <link rel="alternate"> |
Fastest confirmation: run npx aiscan-cli yoursite.com, or paste the URL at aiscan.site. It reports D1, D2, B2, C2, C3, E1, E3 and E5 in one pass, free and with no account.
Why a missing file on Replit returns 200 instead of 404
Ask Agent for an app and you get a full-stack project: a client/ folder holding a React and Vite front end, a server/ folder holding an Express server, and a shared schema. In production that server does two jobs in order. It serves the built static files, then it hands everything it did not recognise to React so the browser-side router can decide what to show.
That second job is one line, and it is the line that matters. Replit's generated server/vite.ts ends with a wildcard route sending index.html for any unmatched path. Think of a receptionist told to hand every unrecognised visitor the same brochure rather than saying "nobody by that name works here". A browser is fine with it, because the router reads the URL and draws the right page. A crawler is not: it asked for /llms.txt, got 200 text/html, and cannot tell the file is absent.
That hits the whole discovery layer at once. E1 fails because there is no real 404. C2 and D2 report a file present when nothing is there. Any checker deciding on a status code alone records a pass. The same shape appears elsewhere: Ghost 302s four llms.txt paths to its homepage, while Squarespace hard-404s an unknown root path, which is correct and the useful control.
Find out which deployment target is serving your app
Two targets serve public web traffic differently, and the response headers name which one in a single request.
curl -sI https://yourapp.replit.app/ | grep -i '^server\|^x-powered-by'
server: Google Frontendplusx-powered-by: Expressmeans Autoscale. Your Express server is answering, catch-all included. Verified onvisa4.travelandaskquran.chat.server: nginxwith nox-powered-bymeans Static Deployment. Files come off disk and missing paths return a real 404. Verified onspeed.press.
The .replit file in your project root says the same thing in its [deployment] block. Two values map to Autoscale in the wild: newer apps read deploymentTarget = "autoscale" (read from Visa4.Travel) and older ones deploymentTarget = "cloudrun" (read from AskQuran, last updated May 2026). Static reads deploymentTarget = "static" and adds a publicDir key.
Path 1: Static Deployment, when your app can use one
Best for landing pages, portfolios and documentation, and the only target where discovery files behave the way the rest of the web does.
Replit's deployment-types page, fetched from docs.replit.com on 2 September 2026, says verbatim: "Static Deployments are not compatible with Replit Apps created using Agent. Agent builds full-stack apps that need a backend server, so use Autoscale or Reserved VM for those." This path is open to you if your project has no backend, or if you split the marketing pages into a second project.
- Open the Publishing tool, then Adjust settings, then the Deployment type dropdown, and choose Static.
- Set the public directory to your build output. A Vite project building the client alone uses
dist/public, which appears in.replitaspublicDir = "dist/public". - Put
robots.txt,sitemap.xmlandllms.txtin the source folder copied into that directory. For a Vite client that isclient/public/. - Create a
404.htmlin the root directory of your Replit App. According to Replit's static configuration reference, that file renders for URL paths matching no file or rule. - For a specific content type on a file, add a header block to
.replit:
[[deployment.responseHeaders]]
path = "/llms.txt"
name = "Content-Type"
value = "text/plain; charset=utf-8"
- Republish. Changes to
.replitdo not apply until you do.
One trap before you add a catch-all [[deployment.rewrites]] rule: Replit calls it shadowing, and a real file always wins over a matching rewrite. That is what keeps robots.txt serving as itself rather than as index.html.
Path 2: Autoscale, which is what Agent gives you
Most readers are here. The app has an Express server, Static is unavailable, and the fix is three small edits.
- Put the flat files where the build copies them. In an Agent-built Vite project that is
client/public/. Onvisa4.travel, verified on 2 September 2026, that folder holds exactlyrobots.txt,llms.txt,favicon.svg,favicon.pngandog-image.png, and all of them serve at the domain root. Files placed anywhere else do not. - Register real routes for anything generated, before the catch-all. A sitemap built from a database is a route, not a file.
visa4.travel/sitemap.xmlreturns200 application/xmlat 32,731 bytes with nositemap.xmlon disk anywhere in the project, which is what a registered route looks like:
// server/routes.ts: must be registered BEFORE the wildcard handler
app.get("/sitemap.xml", async (_req, res) => {
const urls = await buildUrlList();
res.type("application/xml").send(renderSitemap(urls));
});
- Give unmatched paths a real 404. Add a handler that answers a 404 status for requests a crawler would make, while leaving your React routes alone:
// after your API and file routes, before the SPA catch-all
app.use((req, res, next) => {
if (/\.(txt|xml|json|md)$/.test(req.path)) {
return res.status(404).type("text/plain").send("Not found");
}
next();
});
That last edit is the one nobody makes, and it is the difference between a scanner telling you the truth and a scanner telling you everything is fine.
The AI bot rules that actually decide anything
Replit ships no robots.txt of its own, so whatever you write is the whole file. Named groups replace the wildcard group rather than adding to it, so repeat the rules you still want.
User-agent: *
Allow: /
Content-Signal: search=yes, ai-train=no, use=reference
User-agent: GPTBot
Disallow: /
User-agent: OAI-SearchBot
Allow: /
Sitemap: https://yourapp.com/sitemap.xml
GPTBot collects training data and OAI-SearchBot fetches for search answers, so blocking the first and allowing the second is usually what people mean by "no training". Verified on 2 September 2026, askquran.chat runs a 7,548-byte file in this shape with Content-Signals and eight named crawler groups, and its only gap is a missing Sitemap: line. Full reference on /docs/checks/bot-access.
What twelve published Replit apps look like right now
Twelve live apps, five paths each plus the homepage, fetched with a desktop browser user agent on 2 September 2026, scripts and styles stripped before counting. One operator's portfolio, so it says nothing about adoption and everything about platform behaviour.
| Behaviour | Count |
|---|---|
| HTTP 200 for a path that does not exist | 10 of 12 |
| Same HTML shell for all five probed paths | 6 of 12 |
| A real 404 for an unknown path | 2 of 12, both Static Deployments |
| Homepage under 15 visible words | 10 of 12 |
Homepage with one <h1> and real body copy | 2 of 12 (852 and 906 words) |
The two exceptions are the same app in two forms, both Static Deployments built with Astro, and they are the only ones returning a real 404 and the only ones a crawler can read without running JavaScript. That correlation is the deployment target showing up in two checks at once.
visa4.travel is the instructive middle case. It has a real robots.txt, a real llms.txt and a working sitemap route, and still returns eight visible words on the homepage and 200 for a URL that does not exist. Fixing the files does not fix the render, a separate decision covered in why React apps go invisible to AI crawlers.
Confirm the files reached a crawler
Start with the scan. npx aiscan-cli yourapp.com, or paste the URL at aiscan.site. It checks D1 and D2 for robots and sitemap, B2 for the AI crawler groups, C2 for llms.txt, E1 for the soft-404 above, and C3 and E3 for whether a crawler receives body copy. One command covers everything this guide changed, free and with no account.
If you would rather check by hand, three commands settle it:
# 1. A real file, or the shell?
curl -so /dev/null -w '%{http_code} %{content_type} %{size_download}\n' https://yourapp.com/llms.txt
# 2. Control: a path that definitely does not exist
curl -so /dev/null -w '%{http_code} %{content_type} %{size_download}\n' https://yourapp.com/zzz-not-real
# 3. What a crawler reads without JavaScript
curl -s https://yourapp.com/ | sed 's/<script[^>]*>.*<\/script>//g;s/<[^>]*>/ /g' | wc -w
Pass looks like this: command 1 returns 200 text/plain at a size matching your file, command 2 returns 404, command 3 returns more than 200 words. Fail is commands 1 and 2 returning identical numbers, which means both are the shell. Under about 50 words on command 3 means an unauthenticated crawler reads an empty page.
When the Replit app is only the front door
Plenty of Replit apps are a marketing surface for content living on another domain, and none of the work above touches that domain.
If the blog runs on WordPress, ThinkRank is the one to reach for first: it handles robots.txt, robots meta, schema, sitemaps and llms.txt from a single plugin rather than three plugins each rewriting the same file, and it migrates settings from Rank Math, Yoast, All in One SEO and SEOPress so nothing is re-entered. Rank Math has the deeper schema builder for unusual types and Yoast the better multi-author editorial workflow; both are honest choices, and both leave llms.txt to be managed elsewhere.
If the store runs on Shopify, StoreSEO generates llms.txt from live products, collections, pages and articles and includes an agents.md editor, which is the part hand-maintained files fall behind on. Full setup in the Shopify guide.
Where AIScan fits, and where it doesn't
| What it answers | What it cannot answer |
|---|---|
Whether /llms.txt returns a real file or your app shell | Whether the file's contents describe your site accurately |
| Whether an unknown path returns 404 (E1) | Which Express route produced the response |
| Whether a crawler receives body copy (C3, E3) | What Google's renderer sees after running your JavaScript |
| Whether robots.txt names AI crawlers (B2) | Whether those crawlers obey it |
The middle row is the honest limit here. A failing C3 on an Autoscale Replit app means an unverified client reads an empty page, which is true and worth fixing. It does not mean Google reads an empty page, because Google renders JavaScript and we do not.
Your next move on Replit
Ten minutes, in order:
- Run
curl -sIand record whether you are onGoogle Frontendornginx - Add
robots.txtandllms.txttoclient/public/, with aSitemap:line - Register the sitemap route above the catch-all, or ship a static file
- Add the 404 handler for
.txt,.xml,.jsonand.mdpaths - Republish, because
.replitchanges need it - Scan with
npx aiscan-cli yourapp.comand confirm D1, D2, B2, C2 and E1
The check definitions live on /docs/checks/discoverability and /docs/checks/content, the platform notes on /docs/platforms/replit, and the rest of the setup guides on /guides. For a deeper walkthrough of the file itself, see publishing llms.txt on Replit; for a spec-shaped starting draft, the llms.txt generator. Builders arriving from a different AI builder will find the same argument shaped differently in the Lovable guide.
Frequently asked questions
My robots.txt returns 200 but the file is not in my project. Where is it coming from?
It is not coming from anywhere. Replit's Agent-built apps end their Express server with a wildcard route that sends index.html for any unmatched path, so /robots.txt returns your homepage HTML with a 200 status. Compare the byte size against a nonsense URL like /zzz-not-real: if both return the same number of bytes, both are the app shell and neither file exists.
AIScan says my llms.txt passes but I never created one. Is the scan wrong?
The scan is reporting what it received, which was a 200 response. The soft-200 described in this guide is a known limitation of any status-code check, and we say so publicly. Confirm by hand with curl and look at the content-type: a real llms.txt returns text/plain, while the app shell returns text/html. Add the 404 handler shown above and the check starts telling you the truth.
I chose Static Deployment and the publish failed. What went wrong?
If Agent built your app, Static is not available to it. Replit's deployment-types documentation states that Static Deployments are not compatible with Replit Apps created using Agent, because Agent builds full-stack apps that need a backend server. Use Autoscale or Reserved VM, or split the static marketing pages into a separate project with no server folder.
Where exactly do I put robots.txt and llms.txt in a Replit app?
In an Agent-built Vite project the folder is client/public/. Files there are copied into the build output and served at the domain root. On visa4.travel, verified 2 September 2026, that folder holds robots.txt, llms.txt and the favicons, and all of them resolve at the root. A file placed in the project root or in server/ will not be served.
Does the deployment type change what a crawler sees on the page itself?
Indirectly, yes. Across twelve published Replit apps probed on 2 September 2026, ten homepages returned under fifteen visible words after scripts were stripped, and the only two that returned real body copy were Static Deployments built with Astro. Autoscale does not prerender by default, so a React front end ships an empty shell to any client that does not run JavaScript.
How do I tell whether I am on Autoscale or Static without opening the editor?
Run curl -sI against your published URL and read two headers. Autoscale answers with server: Google Frontend and x-powered-by: Express. Static answers with server: nginx and no x-powered-by. The .replit file says the same thing in its [deployment] block, where deploymentTarget reads autoscale (or cloudrun on older projects) versus static.
Can I set a Content-Type header on a file in a Replit app?
On Static Deployments, yes, using a [[deployment.responseHeaders]] block in .replit with path, name and value keys. Content-Type is not on Replit's reserved-header list, so it can be set. Republish for the change to apply. On Autoscale you set the header in your Express route instead, with res.type(). Multiple responseHeaders entries are allowed.
Does Replit's own SEO rating catch any of this?
Partly. After a successful publish Replit runs a Lighthouse audit and groups findings into categories including AI Readiness, Crawlability and Discovery, Landing Page Rendering and Metadata, and Performance Proxies, and its documented fixes include adding a robots.txt and a sitemap.xml. It runs only for public web-facing deployment types, and it will not tell you that a file you already have is being answered by the catch-all rather than by disk.
Related guides
The State of AI Agent Readiness in 2026: 473 Sites Measured
Half of the web's agentreadiness problem is already solved, and almost nobody has noticed which half. Across 473 real websites scanned by AIScan between 24 August and 3 September 2026, the median…
How to publish a valid llms.txt on Framer
Verified on 2 September 2026. Every path, plan limit and status code below was either read from Framer's own help centre or measured live against www.framer.com on that date. On Framer, llms.txt is…
How to publish a valid llms.txt on Replit
Verified 1 September 2026. On Replit, publishing /llms.txt is not one job. It is two, and which one you have depends on how the app is published. An app built by Agent runs on an Autoscale Deployment…
The complete AI readiness setup for Lovable in 2026
Two Lovable apps can look identical in a browser and differ by a factor of fifty in what an AI crawler actually receives. On 1 September 2026 we fetched fifteen published Lovable apps twice each,…
