Table of contents
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: whether the site publishes a dated content feed at all. Run AIScan against a Lovable app today and E5 fails for one of two reasons: no feed file exists, or one exists but nothing in <head> points at it. Both are fixable in under half an hour, and which fix applies depends on which of Lovable's two site templates the project runs.
Quick summary
| If your Lovable project is on... | Feed method | Where it lives | Time |
|---|---|---|---|
| TanStack Start (default since 13 May 2026) | A file-based server route returns real XML on every request | Same domain, e.g. /feed.xml | 20 to 30 minutes |
| React + Vite (older projects, not migrated) | A static file dropped in public/ | Same domain, frozen at whatever was last published | 10 minutes, plus a republish every time content changes |
| Either template, published only to a preview URL | Neither helps yet | Preview responses carry x-robots-tag: noindex, nofollow, so no crawler reads the feed regardless | Connect a custom domain first |
Check first: run npx aiscan-cli yoursite.com, or paste the URL at AIScan. It names E5 directly, alongside the rest of the content dimension: C1 Markdown negotiation, C3 structured HTML, E3 server rendering. The content checks page covers what each one grades.
Why a feed earns its own file
An RSS, Atom or JSON feed is a cheap, dated changelog. Verified on AIScan's own check guidance for E5, the two specs it cites are RFC 4287 for Atom and JSON Feed 1.1; RSS also satisfies the check even though it predates both. A sitemap lists URLs and rarely carries a lastmod value anyone bothers updating. A feed's whole job is dates, which is the one thing a crawler can't infer from a page it hasn't fetched yet.
| What the feed needs | What AIScan checks for it | What AIScan cannot see |
|---|---|---|
| A parseable RSS, Atom or JSON Feed document | The file exists, validates, and is declared in <head> | Whether the dates inside it are true |
| A publish date per item | Present, in the format the spec expects | Whether it matches when the content actually went live |
That second column is on you. A feed with stale or fabricated dates is worse than no feed, because it hands an agent a false freshness signal instead of an honest absence of one.
Why the fix forks on your template
According to Lovable's own measurement writeup, fifteen published Lovable apps were fetched with both a browser user agent and GPTBot on 1 September 2026. Data fetched from apps on TanStack Start returned full body copy to both. Apps still on the older React + Vite template returned near-empty pages to anything that wasn't a verified crawler.
| Template | Runs code per request? | How you ship the feed |
|---|---|---|
| TanStack Start (new default) | Yes, a real server | A file-based route builds XML from your data on every request |
| React + Vite (legacy) | No, static files only | A hand-written file in public/, rebuilt and republished manually |
According to Lovable's SEO and AI search page, older projects use "on-request pre-rendering on deployed public URLs, served only to verified search and AI crawlers," while unverified requests get "the regular single-page app." In its own words, Lovable's upgrade guide says TanStack Start "renders each page into finished HTML on the server before sending it. Everyone receives the same complete page, every time," and dates the switch to the platform default at 13 May 2026.
The same split decides how you ship a feed. React + Vite has no server to run code on request, so anything at a URL has to already exist as a file. TanStack Start has real server routes, so it can build the feed from your actual data on every request.
Path A: TanStack Start, a server route
Ask Lovable's AI to add a route file at src/routes/feed[.]xml.ts. The brackets around the dot are required; TanStack Router treats [.] as a literal period rather than a route-segment separator, verified on TanStack's own routing docs on 26 September 2026. Export a GET handler that queries whatever holds your posts (your Lovable Cloud / Supabase table) and returns real XML:
export const Route = createFileRoute('/feed.xml')({
server: {
handlers: {
GET: async () => {
const items = await getPublishedPosts() // your own query
const xml = buildRss(items) // title, link, pubDate per item
return new Response(xml, {
headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' },
})
},
},
},
})
This runs on the same domain as the app, so /feed.xml is a real path, not a redirect to a different subdomain, and it updates itself: no republish step when you add a post.
Path B: React + Vite, a static file in public/
An older project has no server to run that handler on, so the feed has to be a plain file. Static files placed in Lovable's public/ folder are served from the site root automatically, the same mechanism that serves robots.txt and llms.txt. Ask Lovable to generate public/feed.xml with your current posts written out as Atom or RSS entries, then publish. It works, but it's frozen: every new post means editing the file and republishing, and nothing enforces that you remember. If the rendering gap on E3 and C3 already has you considering the TanStack Start migration, doing it once covers this check too instead of maintaining a file by hand indefinitely.
Declare it in <head>
Either path needs the same tag, wherever your <head> actually lives:
<link rel="alternate" type="application/rss+xml" title="Blog" href="/feed.xml" />
On TanStack Start, that belongs in src/routes/__root.tsx, inside the head function's links array (createRootRoute({ head: () => ({ links: [...] }) })), per TanStack's document head guide. It is not hand-edited into a static HTML file, because there isn't one. On the older Vite template it's a one-time edit to the single index.html shell in the project root, since a plain SPA serves that one file for every route.
Verify
curl -sI https://yoursite.com/feed.xml
# expect: HTTP/1.1 200, content-type: application/rss+xml or application/xml or application/json
curl -s https://yoursite.com/ | grep -o '<link rel="alternate"[^>]*feed[^>]*>'
# expect: the tag above, not empty
curl -s "https://aiscan.site/api/public/v1/scan?url=https://yoursite.com" | grep -o '"E5"[^}]*'
If the scan still fails E5 after a fresh deploy, check the item dates first. A feed with every pubDate set to build time rather than actual publish time is the most common miss on the static path, and it reads as staleness rather than freshness.
Exclusions
A preview-only Lovable URL fails every content check regardless of what you fix, because of the noindex header above; connect a custom domain before troubleshooting anything else. A feed with fewer than about five items reads as thin. Hold off publishing it until there's enough history to make the dates meaningful, and add the file once there is.
Once the feed is live, run the AIScan scan again to confirm E5 alongside the rest of the content dimension, then check C3, title tag, meta description and schema and the full rendering picture. The content dimension guide and more Lovable fix guides cover the rest.
Frequently asked questions
My Lovable app returns a 404 for /feed.xml. What's wrong?
On TanStack Start, confirm the route file is named exactly feed[.]xml.ts with the brackets, not feed.xml.ts — without them the router treats the dot as a path segment and the route never matches. On the older Vite template, confirm public/feed.xml actually exists and was included in the last publish; files added to public/ only take effect after a new deploy.
AIScan still reports E5 as failing after I added the feed. Why?
Re-scan a few minutes after the deploy finishes, not before; Lovable's publish step needs to finish first. If it still fails, check the head tag with a plain curl rather than a browser: some setups only render the tag client-side, which a scanner that doesn't run JavaScript never sees.
view-source doesn't show the feed link tag even though I added it. What happened?
This is a rendering issue, not a feed issue. On React + Vite the tag lives in the static index.html and should always be in view-source; if it isn't, the wrong file was edited. On TanStack Start, confirm the tag is inside head.links in __root.tsx, not injected client-side with a useEffect or a helmet-style component that runs after the page loads.
Do I need to migrate to TanStack Start just to pass this one check?
No. A static public/feed.xml satisfies E5 on the older template too, at the cost of updating it by hand. Migrate for the E3 and C3 rendering gap first; the feed is a five-minute bonus once you're there.
Does it matter whether I use RSS, Atom, or JSON Feed?
Not for the check. All three pass E5. JSON Feed is the least code to hand-write correctly since it's plain JSON with no XML escaping to get wrong; Atom and RSS both need CDATA or entity-escaped text in titles and descriptions.
How many items should the feed include?
Enough to show a real publishing cadence — roughly 10 to 20 of the most recent items is standard practice across feed readers that consume this format. Fewer than five reads as thin.
Does AIScan check that my publish dates are accurate?
No. It confirms the feed parses and carries a date field in the right format, not that the date matches when you actually published. That part is worth getting right anyway: a feed with fabricated or stale dates misleads any agent using it as a freshness signal.
My feed worked when I tested it locally but 404s after I publish. What happened?
This is almost always the static-file path: a file added to public/ needs an actual publish/deploy cycle to reach the live domain, and a local preview or an unsaved chat session doesn't count. Republish, then re-run the curl check above.
Related guides
How to publish an RSS/Atom/JSON feed and declare it in the head on Docusaurus
Docusaurus is the one platform in this series where the feed usually exists before you ask for it. @docusaurus/presetclassic includes the blog plugin, and the blog plugin writes RSS and Atom files on…
How to publish an RSS/Atom/JSON feed and declare it in the head on 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…
Add an RSS Feed AI Agents Can Actually Find
Two staticsite 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 defaulton feature, same…
How to ship one h1, title, meta description and JSON-LD on Lovable
| C3 signal | Where it lives on Lovable | How you fix it | |||| | Title + meta description | Generated per page while Lovable builds your app | Ask in the project chat, there is no settings field | |…
