Dark green cover illustration with layered server panels and a single document sheet drifting away, titled A valid llms.txt on Replit.
Dark green cover illustration with layered server panels and a single document sheet drifting away, titled A valid llms.txt on Replit.
AI Readiness

How to publish a valid llms.txt on Replit

On Replit, where llms.txt lives depends on your deployment type. Exact file paths for Agent apps and Static Deployments, and why a 200 response can lie.

AAsif Rahman September 1, 2026 7 min read
#llms.txt#Replit#AI readiness#agent readiness#deployments

This guide covers C2 · Content — for Replit.

Table of contents

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 with your own server answering every path, so the file is a build asset your server hands out. A Static Deployment has no server at all, so the file is a plain file in a public directory and you can set its content type from .replit. Getting this backwards is why the file looks published and then does not answer.

Either route takes about five minutes. Read the verification step twice, because the app scaffold Agent writes answers 200 for a file that is not there.

Quick summary

QuestionAnswer
Where does the file go?client/public/llms.txt on an Agent app (Autoscale); the published public directory on a Static Deployment
Does a config change help?Only on Static, where [[deployment.responseHeaders]] in .replit sets the content type
Does it work on the dev URL?Yes in the workspace preview, but grade the published domain
Time to fixAbout 5 minutes, plus a republish
AIScan checkC2, llms.txt, in the content dimension
The trapThe default Express scaffold returns 200 and HTML for any missing path, so a status-code check passes on an app with no file

Find out which deployment type you have

Open .replit in the app root. According to Replit's configuration reference, these files "are hidden by default. Show them in your Replit App by selecting Show hidden files from the filetree menu." The value to read is deploymentTarget under [deployment].

An app scaffolded by Agent, fetched from the workspace on 1 September 2026, carries this:

[deployment]
deploymentTarget = "autoscale"
build = ["npm", "run", "build"]
run = ["npm", "run", "start"]

That is path A. If the Publishing tool shows a Deployment type of Static, that is path B. The two do not overlap, and Replit says so in its own words on the deployment types page: "Static Deployments are not compatible with Replit Apps created using Agent."

Deployment typeWho serves /llms.txtWhere the file lives
Autoscale (default, and every Agent app)Your Express serverclient/public/llms.txt, built into dist/public
StaticReplit's cached file serverThe public directory you name when publishing
Reserved VMYour server, always onSame as Autoscale
ScheduledNothing is servedNot applicable

Path A: an Agent app on Autoscale

The React and Express scaffold Agent writes puts the front end under client/, and its vite.config.ts sets root to that folder with build.outDir pointing at dist/public. Vite copies the contents of the root's public/ folder into the build output untouched, so a file you drop in client/public/ arrives at the web root.

  1. In the file tree, open client then public.
  2. Create a file named llms.txt there. Not client/llms.txt, and not the repository root.
  3. Write the file (the section below covers what belongs inside).
  4. Click Publish, then Republish. The build step runs npm run build, which is what copies the file.

The server half is already done for you. The scaffold's server/vite.ts mounts express.static() on the build directory in production, so no route code is needed for a static file.

Path B: a Static Deployment

Here the public directory and an optional build command are set in the Publishing tool under Adjust settings. Put llms.txt at the top level of that directory, republish, and it answers at the root.

Static gives you one thing the other types do not: a content type you control. Add this to .replit in the app root:

[[deployment.responseHeaders]]
path = "/llms.txt"
name = "Content-Type"
value = "text/markdown; charset=utf-8"

According to Replit's static deployment guide, fifteen headers are reserved and cannot be set this way: Accept-Ranges, Age, Allow, Alt-Svc, Connection, Content-Encoding, Content-Length, Content-Range, Date, Location, Server, Set-Cookie, Trailer, Transfer-Encoding and Upgrade. Content-Type is not among them, so this rule takes effect after you republish. Most platforms in this series cannot do that at all, and neither of Replit's own files uses it.

The 200 that means nothing

The scaffold's production server, read from its server/vite.ts and verified on 1 September 2026, ends with a catch-all that sends the app shell for anything it did not match:

app.use(express.static(distPath));
app.use("*", (_req, res) => {
  res.sendFile(path.resolve(distPath, "index.html"));
});

A request for /llms.txt on an app with no such file therefore returns HTTP 200 with text/html and your homepage in the body. A checker that reads the status code alone will report success. So will one that follows redirects and stops there, which is the same class of false pass that Ghost produces by a different mechanism, covered in the Ghost guide.

Check the content type and the first line, never the status code by itself.

What Replit's own two files show

The format is small: an H1 with the site name, an optional blockquote summary, then ## sections holding markdown links. Replit publishes two of these and they are worth reading, though not copying wholesale. Both fetched and verified on 1 September 2026:

FileBytesH1H2H3Links ending .mdContent type
docs.replit.com/llms.txt39,5221940204text/plain; charset=utf-8
replit.com/llms.txt20,1911160228text/plain

Two things to take from that. The docs file carries forty ### headings, which is deeper nesting than the format asks for and a common way a generated file drifts, alongside the other mistakes covered in the llms.txt validator guide. And both point overwhelmingly at .md targets rather than HTML pages, which is the part most hand-written files miss. If you would rather not draft one, the llms.txt generator writes a spec-shaped file you can paste in.

Confirm it with a scan, then by hand

Run the scan against the published domain:

npx aiscan-cli yourapp.replit.app

Read row C2, llms.txt, in the content dimension. It reports whether the file exists, returns 200, and contains an H1, at least one ## section and markdown links. AIScan grades it free and without an account, and the same pass covers C1, C3 and E3 on the same page.

If you would rather check by hand, ask for headers rather than a status code:

curl -sI https://yourapp.replit.app/llms.txt | head -3
curl -s https://yourapp.replit.app/llms.txt | head -1

The first should not say text/html. The second should be your # heading, not <!DOCTYPE html>. On the workspace preview URL the same commands work, but grade the published domain, since that is what agents fetch.

Where AIScan fits, and where it doesn't

C2 can tell youC2 cannot tell you
The file answers at the root of the domain you scannedWhether the URLs inside it resolve
It has an H1, a ## section and markdown linksWhether the summaries describe the pages honestly
It came back as 200 rather than a redirectWhether your Static content-type rule survived the republish
The file is reachable without JavaScriptWhether the file matches the app you shipped today

A generated file that lists routes you deleted still passes C2. Re-read it when the app changes.

Publish, then read row C2

Add the file, republish, and scan the published domain. If C2 still fails, the file is in the repository root instead of client/public, or the build did not run. The same scan gives you C1, C3 and E3 on the same page, and the Astro guide covers the build-time version of this same job if you also ship a static site. Every fix guide in this series lives at aiscan.site/guides, the check itself is documented under content checks, and the Replit platform page collects what else we grade on this stack.

Frequently asked questions

My llms.txt returns 200 but the body is my homepage. What went wrong?

The file is not there. The Express scaffold Replit's Agent writes ends with a catch-all that sends index.html for any path it did not match, so a missing file answers 200 with text/html. Check the content type and the first line of the body rather than the status code. If the first line is <!DOCTYPE html>, the file never made it into the build.

I put llms.txt in the repository root and it still 404s or serves the app shell. Why?

On an Agent-built app the web root is not the repository root. The Vite config sets its root to client/ and builds into dist/public, so only files inside client/public are copied to the served output. Move the file to client/public/llms.txt and republish.

It works in the workspace preview but not on the published app.

The preview runs the dev server, which serves client/public directly without a build. The published app serves dist/public, which is only refreshed when the build command runs. Click Publish, then Republish, so npm run build copies the file across.

My scan still fails C2 after republishing. What should I check next?

Three things, in order. Confirm the file is at client/public/llms.txt and not client/llms.txt. Confirm the deployment build command actually ran. Then fetch the published domain rather than the preview URL, because that is the host the check grades.

Where exactly does the file go on a Replit app built by Agent?

client/public/llms.txt. Agent apps publish as Autoscale Deployments, the front end lives under client/, and Vite copies that folder's public directory verbatim into the build output that Express serves.

Can I use a Static Deployment for an app Agent built?

No. Replit's deployment types page 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 for those, and Static only for a site with no server.

Does llms.txt have to be served as text/markdown?

No. AIScan's C2 check does not require a content type, and neither of Replit's own two llms.txt files uses text/markdown. On a Static Deployment you can set one anyway with a deployment.responseHeaders entry in .replit, since Content-Type is not on Replit's reserved header list.

Do I need to write the file by hand?

No. The format is an H1, an optional summary blockquote, then ## sections of markdown links. The llms.txt generator at aiscan.site produces a spec-shaped file you can paste into client/public/llms.txt, which is faster than drafting one and avoids the deep heading nesting that generated files often pick up.

Related guides