---
title: "How to publish an RSS/Atom/JSON feed and declare it in the head on Replit"
slug: rss-feed-replit
published: 2026-09-28T13:22:19.630081+00:00
updated: 2026-09-28T13:22:19.630081+00:00
author: "Asif Rahman"
author_url: https://masifrahman.com
category: "AI Readiness"
tags: check:E5, platform:replit, Replit, RSS, Atom, JSON Feed, content signals, Autoscale, Static Deployment
description: "Replit's Autoscale and Static deployments store an RSS feed differently. Add a live route or a static file, declare it in head, and verify with AIScan check E5."
url: https://aiscan.site/blog/rss-feed-replit
---

A Replit app can serve `/feed.xml` two completely different ways, and which one applies to you depends on a setting you may never have opened. Get it backwards and you'll ship a feed that 404s in production while it worked fine in the workspace preview.

## Quick summary

| | |
|---|---|
| **Check** | E5 (Content feed: RSS, Atom or JSON Feed) |
| **Platform** | Replit |
| **The fork** | Autoscale and Reserved VM run your Express server; Static Deployments serve files only, no backend |
| **Fix** | A live route on Autoscale/Reserved VM, or a rebuilt static file on Static |
| **Time** | About 10 minutes once you know which deployment type is live |
| **Verify** | `curl` the feed, grep the `<head>` declaration, then run the AIScan scan |

## Find out which deployment is actually live

Replit publishes an app one of four ways: Autoscale, Static, Reserved VM, or Scheduled, chosen from the **Deployment type** dropdown under **Adjust settings** in the Publishing tool. Autoscale is the default, and it's what you get automatically from an Agent-built app: according to Replit's own deployment-types documentation, verified on 28 September 2026, "Static Deployments are not compatible with Replit Apps created using Agent." The reason is the one that matters for a feed too: an Agent build ships a real backend, and that backend is exactly what Path A below needs. Scheduled deployments run a command and stop, with no public URL, so they're out of scope for a feed entirely.

The response headers, fetched from your own domain, tell you which one is live without opening the dashboard:

```bash
curl -sI https://yourapp.replit.app/ | grep -i '^server\|^x-powered-by'
```

| Response | Deployment | Backend? |
|---|---|---|
| `x-powered-by: Express` | Autoscale or Reserved VM | Yes, your server answers every route |
| `server: nginx`, no `x-powered-by` | Static | No, only files in the public directory are served |

This is the same header signature [the complete Replit setup guide](https://aiscan.site/blog/ai-readiness-setup-replit) uses to tell the two apart before touching anything else on the site, verified again for this post. It also decides how the fixes in [the title, meta and schema guide](https://aiscan.site/blog/title-meta-schema-replit) get applied, so it's worth confirming once and remembering.

## Path A: Autoscale or Reserved VM, a route that builds itself

If your server is answering, add a route next to your other routes that assembles the feed from whatever already holds your content (a database table, a CMS call, a folder of Markdown files) and serve it with the right content type:

```js
app.get('/feed.xml', async (req, res) => {
  const items = await getRecentPosts(20); // newest first, real dates
  res.set('Content-Type', 'application/rss+xml; charset=utf-8');
  res.send(buildRssXml(items));
});
```

Register it before any catch-all or SPA fallback route. A wildcard `app.get('*', ...)` placed above `/feed.xml` will swallow the request and hand back your app shell instead of XML, and the response will still be a `200`, which is exactly the kind of failure a status-code check misses. Because this runs on every request, the feed is never stale: publish a post, and the next fetch reflects it with no rebuild step.

## Path B: Static Deployment, a file you rebuild

A Static Deployment has no server to answer a request live, so `feed.xml` has to exist as an actual file in the folder you're publishing. Two things follow: it has to be produced by your build (or committed by hand), and it goes stale the moment you publish new content until you rebuild and republish.

**Adjust settings > Deployment type > Static**, then set the public directory to your build's output folder (`dist` for a typical Vite project). If your static site generator can emit a feed as part of its normal build, most can, from a template or a plugin, point that output at the same public directory so `feed.xml` ships automatically every time you republish. If it can't, write the file into the public directory before each publish and remember to redo it. A Static Deployment that shipped a feed once and never rebuilt it again is worse for E5 than shipping no feed at all, because the dates inside it start lying about what's fresh.

One more static-only detail: if `curl` later shows the wrong `Content-Type` for the file, you can force it. According to Replit's Static Deployment configuration page, fetched from docs.replit.com on 28 September 2026, `Content-Type` isn't on the list of reserved response headers, so a `[[deployment.responseHeaders]]` entry in your `.replit` file scoped to `path = "/feed.xml"` overrides it on your next republish.

## Path A vs Path B, side by side

| | Autoscale / Reserved VM | Static Deployment |
|---|---|---|
| Where the feed lives | Generated in a route handler | A real file in the public directory |
| Freshness | Live on every request | As fresh as your last rebuild |
| What breaks it | A catch-all route registered above it | A build that doesn't emit or copy the file |

## Point `<head>` at whichever one you shipped

Every page that should surface the feed needs the same declaration, adjusted for your format:

```html
<link rel="alternate" type="application/rss+xml" title="Your Site" href="/feed.xml" />
```

Use `application/atom+xml` for Atom or `application/feed+json` for JSON Feed. Match the `type` to what you actually built, not to whichever example you copied. On Autoscale, this goes in whatever template renders your page `<head>` on every route, so it doesn't depend on which page the crawler lands on first. On Static, there usually isn't a template: you're editing the built `index.html` (or your generator's layout partial) directly, and it's worth checking that every page, not just the homepage, inherited the change.

## Confirm it, in order

```bash
curl -sI https://yourapp.replit.app/feed.xml   # expect 200 + a feed content-type
curl -s  https://yourapp.replit.app/feed.xml | head -20   # real items, real dates
curl -s  https://yourapp.replit.app/ | grep -o '<link[^>]*alternate[^>]*>'
```

If the first command 404s on Autoscale, the route is either missing or sits below a catch-all: move it. If it 404s on Static, the file isn't in the public directory the deployment is actually serving; confirm the directory under **Adjust settings**, not the one you assumed. If the `<link>` grep comes back empty, the feed exists but nothing points a crawler at it, which is worth catching because it's invisible in a browser. The feed works if you paste its URL directly, and still fails the check.

Then dogfood it the way this check is actually graded: lead with the scan, not the curl. Run `npx aiscan-cli yourapp.replit.app` naming check E5, or paste the URL at [aiscan.site](https://aiscan.site/). The manual commands above are the by-hand equivalent if you'd rather not run anything.

## Where AIScan fits, and where it doesn't

E5 confirms the feed exists, resolves, parses as valid RSS, Atom, or JSON Feed, and is declared in `<head>` where a crawler expects to find it, the same four things you just verified. AIScan's own guidance for this check, fetched from its check-guidance API this run, cites RFC 4287 for Atom and JSON Feed 1.1 as the two specs a valid feed has to satisfy. What none of that confirms is whether the dates inside stay honest over time. A feed that was accurate on launch day and never rebuilt again on a Static Deployment will keep passing the check while quietly telling every agent that reads it that nothing has changed in months. That's a workflow problem, not a markup one, and no scanner catches it from a single request.

## Ship it, then check the row that changed

A feed is the cheapest changed-since-last-week signal your Replit app can publish, and which of the two paths above applies is decided the moment you pick a deployment type, not something you can retrofit blind. Confirm the fix landed with a fresh AIScan scan, then look at what else check E5's neighbors in the [content dimension](https://aiscan.site/docs/checks/content) are asking for; a valid feed rarely ships alone. If your app doesn't have a valid `llms.txt` yet, [the Replit llms.txt guide](https://aiscan.site/blog/llms-txt-replit) walks the same deployment-type fork verified here. A Vite/React app on Lovable hits the identical live-route-vs-static-file choice, covered in [the Lovable version of this fix](https://aiscan.site/blog/rss-feed-lovable). More Replit-specific fixes are collected under [/docs/platforms/replit](https://aiscan.site/docs/platforms/replit), and the full check list is at [/guides](https://aiscan.site/guides).

