Table of contents
Two static-site generators build you a feed with zero configuration. One declares it in <head> on every site we tested. The other only manages it on five of every eight. Same default-on feature, same job, and the entire gap between them is one missing <link> tag. If a declared feed answers 403, see why feeds return 403 behind Cloudflare.
Hugo and Docusaurus both write RSS or Atom to disk the moment you build the site, with no plugin and no setting to turn on. Live-tested across 15 sites on each platform in early September 2026: 8 of 15 Hugo sites serve a real feed at /index.xml, but 2 of those 8 never mention it anywhere in <head>, and a third points its rel="alternate" tag at the homepage instead of the feed. Docusaurus, measured the same way, hit 9 of 15 sites publishing /blog/rss.xml and /blog/atom.xml, and all nine declared both, automatically, with no configuration. The feed existing was never the finish line. The tag that tells a reader, human or machine, where to find it is.
Updated on 4 November 2026: added the Docusaurus, Lovable and Replit sections.
Quick summary
| Question | Short answer |
|---|---|
| What does a feed need besides existing? | A <link rel="alternate" type="application/rss+xml"> (or atom+xml) tag in <head>, pointing at the feed's real URL |
| Fastest check | curl -s https://yoursite.com | grep -o 'rel="alternate"[^>]*' |
| Which check this is | AIScan's E5, in the content dimension |
| Auto-declared, no edits needed | Docusaurus (9 of 9 tested), Squarespace, Ghost, WordPress |
| Builds the file but not the tag | Hugo (2 of 8 tested sites silent), Next.js, Astro |
| No native feed at all | Framer, Replit's default Express scaffold, Lovable React + Vite projects |
| Format | RSS 2.0 or Atom 1.0; either is fine, readers and agents parse both |
| Documented gap | Wix: no confirmed root feed path found first-party, verify on your own site before assuming one |
| What AIScan's E5 can't see yet | Whether the <link> target actually resolves to the feed, or quietly points at your homepage instead |
Why a feed that exists can still be invisible
RSS and Atom autodiscovery works on one convention: a <link> element inside <head>, carrying rel="alternate" and a type of application/rss+xml or application/atom+xml, with an href pointing at the feed. A reader, an aggregator, or an agent doing periodic re-checks looks for that tag first. It does not walk a list of common paths hoping one resolves.
Think of it as an unlisted phone number: the line works, you can reach it if you already have the digits, but nobody finds it by looking you up. A blog with a perfectly valid feed at /index.xml and no <link> tag is in exactly that position, reachable by anyone who already knows the URL, invisible to anything that discovers sites the normal way. And the whole reason a feed is worth having is that it gives an outside system a fast, structured, dated list of what changed, instead of forcing it to re-crawl every page to find out. Skip the declaration and you built the shortcut without posting the sign.
This is check E5 in AIScan's content dimension, and it is a narrower check than it sounds: it looks for a working feed and a correct declaration, not for how good the feed's content is.
Path one: your platform already builds the feed, so confirm the tag
If your platform ships a feed by default, the job is verification, not construction.
- Find your feed's real URL from the table below.
- Fetch your homepage's HTML and search for
rel="alternate":curl -s https://yoursite.com | grep -o 'rel="alternate"[^>]*'. - Confirm the
hrefin that tag matches the URL from step 1: not your homepage, and not a stale path left over from a redesign. - If the tag is missing, wrong, or absent entirely, fix it where the table says to.
| Platform | Feed path | Declared automatically? | If it's missing |
|---|---|---|---|
| Docusaurus | /blog/rss.xml and /blog/atom.xml | Yes, 9 of 9 sites tested declared both | Nothing to fix; re-check after a theme swap |
| WordPress | /feed/ | Yes, via wp_head() | A broken or custom theme can drop the wp_head() call; restore it |
| Ghost | /rss/ | Yes, via the theme's {{ghost_head}} helper | Confirm {{ghost_head}} still renders in your theme's <head> partial |
| Squarespace | <page-url>?format=rss | Yes, injected on every page automatically | Nothing to fix |
| Hugo | /index.xml | No, it depends on the theme; 2 of 8 tested sites omitted it, and one pointed the tag at the homepage instead | Add a <link rel="alternate" type="application/rss+xml" href="{{ \"index.xml\" | absURL }}"> line to the theme's <head> partial |
Hugo is the sharp case here, because the gap isn't "no feed"; it's a feed that exists, works, and simply never gets mentioned. If your theme was copied or heavily customized, that one line is exactly the kind of thing that gets lost in the process, and nothing about the site looking finished will tell you it happened.
Path two: your platform has no native feed, so you build both halves
Next.js, Astro, Webflow's custom code path, and Replit's default Express scaffold don't hand you a feed. You write the XML and the declaration yourself.
- Next.js (App Router): create a route handler at
app/rss.xml/route.tsthat returns aResponsewithcontent-type: application/rss+xmland a hand-built XML string covering your post list. Then declare it once, site-wide, through the Metadata API rather than a hand-written tag: addalternates: { types: { "application/rss+xml": "/rss.xml" } }to your rootmetadataexport, and Next.js renders the<link>tag for you on every page. - Astro: install
@astrojs/rssand export aGEThandler fromsrc/pages/rss.xml.jsthat calls the package'srss()helper with your post collection. Astro writes a valid feed; it does not add the<link>tag, so add that by hand in your shared layout's<head>. Check the final URL against yourastro.config: a globaltrailingSlash: "always"setting can serve the feed at/rss.xml/instead of/rss.xml, which breaks the exact URL you just declared. - Webflow: each CMS Collection has its own RSS toggle in the Collection's settings, publishing at
/<collection-slug>/rss.xmlonce turned on (the exact label has moved between Webflow versions, so check your own Collection settings panel if this doesn't match what you see). Webflow does not add the discovery tag automatically; add it yourself through a Custom Code head embed in Site Settings. - Replit: if your app is a default Express or similar catch-all scaffold, add an explicit route (
app.get('/rss.xml', ...)) that returns real XML with the right content type, and add the<link>tag in your HTML template's<head>. A catch-all with no matching route answers everything with your app shell and a 200, including a request for a feed that was never built, and that failure mode will not show up as an error anywhere in your logs. - Framer: there is no native feed of any kind on the platform as of this writing. The only path is external: publish your posts through a service that exposes its own feed, such as a headless CMS or a newsletter tool with a public archive, and link to that from your site. Otherwise your Framer site opts out of feed-based discovery entirely.
Wix is a genuine gap, not an oversight. We could not confirm a documented root feed path first-party: /blog-feed.xml and /blog/feed.xml both hard-404 on Wix's own marketing site, which doesn't run Wix Blog, and its homepage carries no rel="alternate" tag at all. If you run a Wix Blog site, check your own <head> for the tag before assuming any specific path; don't copy a URL from a guide that hasn't verified it against your account type.
Platform sections
Jump to a platform: Docusaurus, Lovable, Replit. The Hugo, WordPress, Ghost, Squarespace, Next.js, Astro, Webflow, Wix and Framer notes are in the two paths above.
Docusaurus
Docusaurus is the platform where the feed usually exists before you ask for it. @docusaurus/preset-classic includes the blog plugin, and the blog plugin writes RSS and Atom files on every production build. According to the plugin's reference page, retrieved 4 November 2026, the feed "works by extracting the build output, and is only active in production". So the fix is rarely "add a feed". It is "find out why yours is missing", and there are four causes:
| Symptom | Cause | Repair |
|---|---|---|
/blog/rss.xml returns 404 live and there is no blog/ folder | Docs-only mode, or the blog plugin is off | Restore the blog options in the preset and publish at least one dated post |
| The blog exists but the feed 404s | feedOptions.type is null | Delete type: null, or set feedOptions: { type: ['rss', 'atom'] } |
The feed works live but 404s on localhost:3000 | You are on docusaurus start, which never builds feeds | Run npm run build then npm run serve and test there |
| The feed loads but every link points at the wrong host | url in docusaurus.config.js is a placeholder | Set url to your production origin, no trailing slash and no path |
feedOptions: {
type: 'all',
limit: 50,
},
type: 'all'adds JSON Feed at/blog/feed.jsonnext to RSS and Atom. The specs behind the check are JSON Feed 1.1 and RFC 4287 (Atom).limitdefaults to 20 posts. Raise it, or set it tonull, if agents should see your full history.- Author emails. RSS only shows an author when that author has an
emailinblog/authors.yml. - Subpath deploys. If
baseUrlis/repo-name/, request the feed at that prefix.
You never add the <link> tag yourself: the plugin injects rel="alternate" for every feed type it generates, which is why 9 of 9 Docusaurus sites in our September test declared both. A site running a second blog instance gets a second feed, so check that each one is declared and returns 200. Verify with curl -s https://yoursite.com/blog/rss.xml | head -c 300 (it should start with <?xml version="1.0" and an <rss version="2.0" element) and the head grep from the verification section below. The complete platform walkthrough is in the Docusaurus section of the setup guide.
Lovable
A sitemap tells a crawler what exists. It does not tell an agent what changed since last week, and that gap is what check E5 grades. On a Lovable app E5 fails for one of two reasons: no feed file exists, or one exists but nothing in <head> points at it. Which fix applies depends on which of Lovable's two templates the project runs.
| Template | Runs code per request? | How you ship the feed |
|---|---|---|
| TanStack Start (the platform default since 13 May 2026) | Yes, a real server | A file-based server route builds the XML on every request |
| React + Vite (older projects) | No, static files only | A hand-written file in public/, republished each time content changes |
TanStack Start. Ask Lovable to add a route file named src/routes/feed[.]xml.ts. The brackets are required: TanStack Router reads [.] as a literal period (checked against TanStack's server routes docs, 26 September 2026). Export a GET handler that reads your posts table and returns a Response with Content-Type: application/rss+xml; charset=utf-8. It runs on your own domain and updates itself, so there is no republish step. Declare the feed once in src/routes/__root.tsx, inside the head function's links array, per TanStack's document head guide.
React + Vite. There is no server to run a handler, so the feed is a plain file. Ask Lovable to generate public/feed.xml with your current posts as Atom or RSS entries and publish; files in public/ are served from the root, the same way robots.txt and llms.txt are. It works but it is frozen: every new post means editing the file and republishing. The tag is a one-time edit to the single index.html shell. If the rendering gap on E3 and C3 already has you considering the migration, doing it once covers this check too.
Verify with curl -sI https://yoursite.com/feed.xml (expect 200 and a feed content type) and the head grep below. Two exclusions: a preview-only Lovable URL fails every content check because it carries x-robots-tag: noindex, nofollow, so connect a custom domain first; and a feed with fewer than about five items reads as thin. If the scan still fails after a deploy, check the item dates first: a pubDate set to build time on every item reads as staleness. More in the Lovable section of the setup guide and the Lovable title, meta and schema fixes.
Replit
A Replit app can serve /feed.xml two completely different ways, and which one applies depends on a setting you may never have opened: the Deployment type under Adjust settings in the Publishing tool. Per Replit's deployment-types documentation (verified 28 September 2026), "Static Deployments are not compatible with Replit Apps created using Agent", so an Agent-built app is Autoscale by default. The response headers tell you which one is live:
curl -sI https://yourapp.replit.app/ | grep -i '^server\|^x-powered-by'
| Response | Deployment | Backend? | Feed method |
|---|---|---|---|
x-powered-by: Express | Autoscale or Reserved VM | Yes | A route that builds the feed per request |
server: nginx, no x-powered-by | Static | No | A real file in the public directory, rebuilt on each publish |
Autoscale or Reserved VM. Add app.get('/feed.xml', ...) that assembles the feed from whatever holds your content and sets Content-Type: application/rss+xml; charset=utf-8. Register it before any catch-all such as app.get('*', ...): a wildcard above it returns your app shell with a 200, which a status-code check misses.
Static Deployment. Set the public directory (dist for a typical Vite project) and make your build emit feed.xml there. A hand edit to the deployed file lasts only until the next publish, and a feed shipped once and never rebuilt keeps passing E5 while telling agents nothing has changed. If curl shows the wrong content type, a [[deployment.responseHeaders]] entry in .replit scoped to path = "/feed.xml" overrides it, because Content-Type is not a reserved header (Replit's Static Deployment configuration page, 28 September 2026).
Declare it with <link rel="alternate" type="application/rss+xml" title="Your Site" href="/feed.xml" /> in the template that renders your <head> on every route; on Static, edit the built index.html or your generator's layout and check that every page inherited it. Use application/atom+xml or application/feed+json to match what you built. More in the Replit section of the setup guide and the Replit llms.txt walkthrough.
Verify it, don't assume it
Two checks, in order of effort:
- Fetch and grep.
curl -s https://yoursite.com/your-feed-pathshould return XML starting with<?xmland either<rss version="2.0">or<feed xmlns="http://www.w3.org/2005/Atom">. If you get HTML instead, your route isn't matching and something else, often a catch-all, is answering in its place. - Confirm the declaration matches the file.
curl -s https://yoursite.com | grep -o 'rel="alternate"[^>]*href="[^"]*"'should print atypeofapplication/rss+xmlorapplication/atom+xmland anhrefthat, fetched on its own, returns the same XML from step 1. A<link>tag pointing at your homepage, the exact failure the Hugo sweep found, passes a glance and fails this test immediately.
| Check | Pass looks like | Fail looks like |
|---|---|---|
| Fetch the feed URL directly | Starts with <?xml, then <rss or <feed | Starts with <!DOCTYPE html> |
Fetch the homepage and read the tag's href | Resolves to the exact same XML as above | 404s, redirects elsewhere, or matches the homepage's own bytes |
For a third-party read, run npx aiscan-cli yoursite.com or paste the URL at aiscan.site and read check E5. It confirms the feed resolves and is declared; it does not yet independently verify that the declared href and the working feed are the same URL, so the manual step above is still worth the ten seconds even after a clean score.
What this doesn't cover
A feed is not a substitute for llms.txt, a sitemap, or a correct 404; it's one more discovery surface, and check E5 grades only this one. It also won't help a site that publishes nothing on a schedule: a single static landing page with no blog or changelog has nothing worth putting in a feed, and adding an empty one just to pass a check is wasted effort. And a feed can silently break after you change themes or redesign your site, since the <link> tag lives in a template file that a new design doesn't automatically carry forward, so re-run the grep above after any theme change, not just once at launch.
On WordPress, this specific check needs nothing extra: wp_head() handles it without a plugin. If you're also cleaning up robots.txt, robots meta, schema, or llms.txt on the same WordPress site, ThinkRank manages all of those from one plugin instead of three fighting over the same files, which is the more common source of AI-readiness regressions than the feed tag ever is.
Next step
Run npx aiscan-cli yoursite.com (or paste your URL at aiscan.site) and read check E5 alongside D1 and C2. A site that declares its feed correctly alongside a correct robots.txt and llms.txt gives an agent three ways to find what changed instead of one. For the full platform-by-platform setup rather than just the feed, the Hugo and Docusaurus guides cover this same measurement in context, and every platform walkthrough on the blog is collected at /guides.
For adoption numbers rather than setup steps, what 340 sites with a blog actually ship breaks E5 down by format and platform.
Frequently asked questions
My platform already builds a `/feed/` URL. Do I need to do anything else?
Check that it's declared. A working feed with no <link rel="alternate"> tag in <head> is reachable only by someone who already has the URL. Run the grep command in the verification section above against your own homepage before assuming it's handled.
I added the `<link>` tag but AIScan (or a feed reader) still can't find my feed. What's wrong?
Fetch the href from your own tag directly. If it 404s, redirects somewhere unexpected, or returns HTML instead of XML, the tag is pointing at the wrong path, often a leftover from a redesign or a theme migration that moved the feed without updating the declaration.
My feed loads fine at its URL but a validator flags errors. What does that mean?
The file exists and is being served, but its XML doesn't fully conform to RSS 2.0 or Atom: a missing required element, a malformed date, or invalid characters in a title are the usual causes. Paste the feed into the W3C Feed Validation Service to get the specific line; most autodiscovery consumers tolerate small issues, but a validator error is worth fixing before it compounds.
Does Wix have a documented RSS feed for my blog?
Not one we could confirm first-party. The common guessed paths hard-404 on a non-blog Wix site, and the homepage carries no autodiscovery tag. If you run Wix Blog, check your own site's <head> rather than trusting a path from any guide, this one included.
Should I use RSS 2.0 or Atom?
Either. Both formats express the same information: a list of dated items with titles, links, and content. Every mainstream reader, aggregator, and agent that consumes feeds handles both, so use whichever your platform generates by default rather than building a second format nobody asked for.
My theme update removed the `<link rel="alternate">` tag from `<head>`. How do I stop that from happening again?
You mostly can't prevent it, but you can catch it fast: add the grep command from this guide to whatever you already run after a deploy, even a one-line manual check, because a theme change is the single most common way this tag disappears, and nothing else about the site will look broken when it does.
Does adding an RSS feed help directly with AI Overviews or being cited by ChatGPT?
Not directly, and no one should promise that it does. What it does is give any system that periodically re-checks your site, including AI crawlers looking for what's new, a fast, structured way to find recent changes instead of re-fetching every page. That's a discovery improvement, not a ranking or citation guarantee.
How do I know if my `rel="alternate"` `href` actually points at the feed and not my homepage?
Fetch the href value on its own and check the first bytes. Real feed content starts with <?xml followed by <rss or <feed; a homepage returns <!DOCTYPE html>. This exact mistake, the tag technically present but pointing at the homepage, showed up in one of every eight Hugo sites we tested, so a tag existing is not proof it's correct.
Why does my Docusaurus feed 404 on localhost but work in production?
The blog plugin builds feeds from the production build output, so docusaurus start never serves one. Run npm run build then npm run serve and test http://localhost:3000/blog/rss.xml.
How do I get JSON Feed on Docusaurus?
Set feedOptions.type to 'all' in the blog options and rebuild. The plugin then writes /blog/feed.json next to the RSS and Atom files.
What must the Lovable feed route file be called on TanStack Start?
src/routes/feed[.]xml.ts, brackets included. Without them the router treats the dot as a path separator and /feed.xml never matches.
Do I need to migrate a Lovable React + Vite app to serve a feed?
No. A static public/feed.xml passes E5 on the older template, at the cost of editing and republishing it by hand. Migrate for the E3 and C3 rendering gap first.
How do I tell which Replit deployment type my app uses?
Fetch your homepage headers. x-powered-by: Express means Autoscale or Reserved VM, so add a live route. server: nginx with no x-powered-by means a Static Deployment, so ship a file from your build.
Why does my Replit /feed.xml return the app shell instead of XML?
A catch-all route registered above the feed route answers first with a 200. Move app.get('/feed.xml', ...) above any app.get('*', ...) handler and redeploy.

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,…
RSS, Atom or JSON Feed: What 340 Sites With a Blog Actually Ship
Of the 340 sites in our scan corpus that have a blog, news or articles section, 233 serve a feed, and 191 of those feeds are RSS. Atom is on 6 sites. JSON Feed was reported on 36, but most of those…
Canonical host redirects on Next.js, Webflow, Squarespace, Lovable, Replit, Framer and Wix
If example.com and www.example.com both return your site, every page exists twice. The fix is one rule: pick a host, then send the other one to it with a redirect. On a hosted builder that rule is a…
