---
title: "How to publish a valid llms.txt on Replit"
slug: llms-txt-replit
published: 2026-09-01T13:27:29.234374+00:00
updated: 2026-09-01T13:27:29.234374+00:00
author: "Asif Rahman"
author_url: https://masifrahman.com
category: "AI Readiness"
tags: check:C2, platform:replit, llms.txt, Replit, AI readiness, agent readiness, deployments
description: "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."
url: https://aiscan.site/blog/llms-txt-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 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

| Question | Answer |
|---|---|
| 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 fix | About 5 minutes, plus a republish |
| AIScan check | **C2**, llms.txt, in the `content` dimension |
| The trap | The 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:

```toml
[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 type | Who serves `/llms.txt` | Where the file lives |
|---|---|---|
| Autoscale (default, and every Agent app) | Your Express server | `client/public/llms.txt`, built into `dist/public` |
| Static | Replit's cached file server | The public directory you name when publishing |
| Reserved VM | Your server, always on | Same as Autoscale |
| Scheduled | Nothing is served | Not 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:

```toml
[[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:

```js
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](https://aiscan.site/blog/llms-txt-ghost).

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:

| File | Bytes | H1 | H2 | H3 | Links ending `.md` | Content type |
|---|---|---|---|---|---|---|
| `docs.replit.com/llms.txt` | 39,522 | 1 | 9 | 40 | 204 | `text/plain; charset=utf-8` |
| `replit.com/llms.txt` | 20,191 | 1 | 16 | 0 | 228 | `text/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](https://aiscan.site/blog/llms-txt-validator-common-mistakes). 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](https://aiscan.site/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:

```bash
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](https://aiscan.site/) 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:

```bash
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 you | C2 cannot tell you |
|---|---|
| The file answers at the root of the domain you scanned | Whether the URLs inside it resolve |
| It has an H1, a `##` section and markdown links | Whether the summaries describe the pages honestly |
| It came back as 200 rather than a redirect | Whether your Static content-type rule survived the republish |
| The file is reachable without JavaScript | Whether 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](https://aiscan.site/blog/llms-txt-astro) 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](https://aiscan.site/guides), the check itself is documented under [content checks](https://aiscan.site/docs/checks/content), and the [Replit platform page](https://aiscan.site/docs/platforms/replit) collects what else we grade on this stack.

