AEO on Framer: the whole build

Framer does most of an AEO build for you. Every page is pre-rendered to HTML on Framer's servers, the sitemap and robots.txt generate themselves, canonical tags are automatic, and any page returns clean Markdown on request. What's left for you is JSON-LD and semantic structure, both of which need custom code. The traps are all in the CMS.

Oskar Mieta

Founder, Designer & Developer

Summarize with AI

Category

Platform

Reading time

6 min

Framer does most of an AEO build for you. Every page is pre-rendered to HTML on Framer's servers, the sitemap and robots.txt generate themselves, canonical tags are automatic, and any page returns clean Markdown on request. What's left for you is JSON-LD and semantic structure, both of which need custom code. The traps are all in the CMS.

Desses is a Framer Partner and a Framer Pro Expert, which comes from earning $500 in handoff commissions rather than from anyone at Framer deciding I'm good. We sell Framer development from $8,000, so read the rest knowing where my money comes from.

What Framer hands you before you touch a setting

Open a terminal and ask for a page the way OpenAI's search crawler would.

curl -A "OAI-SearchBot" https://your-site.com/insights/your-article
curl -A "OAI-SearchBot" https://your-site.com/insights/your-article
curl -A "OAI-SearchBot" https://your-site.com/insights/your-article

Against a Framer content page that returns two JSON-LD blocks, one H1, the canonical link and roughly 160,000 characters of body text in the initial HTML response. No JavaScript ran. Nothing hydrated.

Framer's documentation says why: "Although Framer sites are built with React, every page is pre-rendered to HTML on our servers before it is served. AI agents and crawlers that do not execute JavaScript still receive the full text of your page."

Pre-rendering is the whole ballgame, because no major AI crawler runs JavaScript on a live fetch. The "Framer is bad for SEO because it's React" line can go in the bin, and the curl output settles it in ten seconds.

The rest ships free with no configuration: sitemap.xml and robots.txt, canonical tags, per-page meta from CMS variables, a noindex toggle, per-layer data-nosnippet, tables in rich text, and nested routes with category and author pages from reference fields. Markdown delivery needs no configuration either, and it's big enough to have its own article, which also covers the head comment that ships on framer.com and not on your build.

The JSON-LD block, and how the CMS fills it

JSON-LD is a block of structured data in a page's head telling a parser what the page is: type, headline, author, dates and image.

Framer ships no schema type natively. Article, Organization, BreadcrumbList, all of it goes in through Custom Code in Project Settings, scoped to specific pages including CMS pages. The scoping is the part people miss: attach a block to one CMS template and every item in the collection gets its own filled-in version.

Here's the block we ship on an article template:

<script type="application/ld+json">{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": {{Title | json}},
  "description": {{Excerpt | json}},
  "image": {{Cover_Image | json}},
  "datePublished": {{Created | json}},
  "dateModified": {{Updated | json}},
  "author": { "@type": "Person", "name": {{Author_Name | json}} }
}</script>
<script type="application/ld+json">{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": {{Title | json}},
  "description": {{Excerpt | json}},
  "image": {{Cover_Image | json}},
  "datePublished": {{Created | json}},
  "dateModified": {{Updated | json}},
  "author": { "@type": "Person", "name": {{Author_Name | json}} }
}</script>
<script type="application/ld+json">{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": {{Title | json}},
  "description": {{Excerpt | json}},
  "image": {{Cover_Image | json}},
  "datePublished": {{Created | json}},
  "dateModified": {{Updated | json}},
  "author": { "@type": "Person", "name": {{Author_Name | json}} }
}</script>

Three things in that block earn their place.

The | json filter escapes the value safely and it goes on every single field. A curly apostrophe, a quotation mark, a line break pasted out of Google Docs. Any one breaks the block, and a broken JSON-LD block fails silently. The page ships, the client is happy, and the structured data has been invalid for four months by the time anyone runs a validator.

{{Created}} and {{Updated}} are built into every CMS item, so nobody hand-maintains a date field and the schema dates can't drift from the CMS dates. {{Author_Name | json}} pulls through a reference field: change the name in one place and every article that writer published updates.

Framer also ships {{variable | unsafeRaw}}, and its own warning says malformed content can break your site or introduce security risks. A CMS field is user input, and the user is a client's marketing hire eighteen months from now who has never heard of JSON escaping. Escaped by default, always, written into the handover doc.

Native, custom code, or not possible

What you want

Where it comes from

Notes

Pre-rendered HTML for non-JS crawlers

Native

Server-side, every page, no setting

Markdown version of any page

Native

Accept: text/markdown header or ?md on the URL

sitemap.xml

Native

Auto-generated and maintained by Framer

robots.txt

Native

Auto-generated, overridable by upload on Pro

Canonical tag

Native

Automatic, with manual override

Title, meta description, OG image per CMS item

Native

Via CMS variables on the template

Per-page noindex

Native

Toggle

data-nosnippet

Native

Set per layer

Redirects with wildcards and capture groups

Native, Pro+

Not on Basic

Static file hosting including /.well-known/

Native, Pro+

This is where an llms.txt upload would live

CMS pagination

Native

Infinite scroll or Load More, client-side past batch one

Nested routes like /insights/:slug

Native

Plus category and author pages via reference fields

Tables in rich text

Native

Since March 2025

Programmatic CMS writes and publishing

Native, beta

Server API, JavaScript SDK over a persistent WebSocket

Article, Organization, BreadcrumbList, any schema

Custom code

Custom Code in Project Settings, scoped per page

Breadcrumbs themselves

Custom code

The visible component as well as the markup

Semantic tags (article, section, nav)

Custom setting

Default to div, set per frame in the Accessibility panel

A REST endpoint for the CMS

Build it yourself

Framer's SDK is WebSocket; a REST wrapper is on you

RSS feed

Not possible

No workaround

Numbered pagination URLs like /insights/page/2

Not possible

Pagination has no URL of its own

The traps are in the CMS

Heading tags in Framer are assigned per text layer, and nothing stops you putting an H4 directly under an H2. No warning, no validator. On a page built by three people over two months, the outline turns into a mess that no human looks at and every parser does. The fix is structural: lock heading tags to text styles, one style per level. Then walk the outline before publish.

Semantic tags default to div until you say otherwise, per frame in the Accessibility panel. A page of nested divs reads fine as text and hands a parser no structure at all.

Now the one that actually costs money. Changing a CMS slug doesn't create a redirect. Framer's redirects article puts it plainly: a sub-path changed from the canvas or the CMS won't update your redirect settings on its own. The old URL just stops existing, and every link into it goes with it. Slugs are cheap to change. That's the problem.

This belongs in the handover doc as a written rule. Ours reads: slugs are final at publish, and if one has to change, the person changing it creates the manual redirect in the same session. I've seen a client rename six slugs in an afternoon to make them "more consistent" and take out a year of accumulated links. Nobody noticed for five weeks.

Which plan you actually need

Basic gives you 2 CMS collections and 1,000 items, with no site redirects. An Articles plus Categories plus Authors hub is three collections. That single fact decides the plan before any AEO consideration enters it.

Pro starts at 10 collections, then $40 per additional 10 up to 40. Items start at 2,500, then $20 per 10,000 up to 40,000. Pro also carries redirects, staging and full static file hosting, which makes it the plan that fixes the slug trap.

Pro is the floor.

The test to run before you launch

Two curls against a real published page.

curl -A "OAI-SearchBot" https://your-site.com/insights/your-article | wc -c
curl -H "Accept: text/markdown" https://your-site.com/insights/your-article
curl -A "OAI-SearchBot" https://your-site.com/insights/your-article | wc -c
curl -H "Accept: text/markdown" https://your-site.com/insights/your-article
curl -A "OAI-SearchBot" https://your-site.com/insights/your-article | wc -c
curl -H "Accept: text/markdown" https://your-site.com/insights/your-article

A pass on the first is a character count in the tens or hundreds of thousands, your H1 present, the canonical link there, and your JSON-LD block with real values filled in. If {{Title}} is still sitting in the output as literal text, the custom code isn't scoped to that CMS template and the block is decorating nothing.

A pass on the second is YAML frontmatter, then your body as Markdown, then the ## Navigation section Framer appends. Nothing back at all means the page hasn't been optimised yet, or the site is rate-limited.

Run both against every template. A template that fails takes every item under it down with it, and one that passes covers the whole collection. While you're there, run the site through Framer's AEO scanner.

When Framer is the wrong platform for this

Two hard ceilings. Forty CMS collections, forty thousand items, fully paid and fully loaded, with no plan above them. A publisher with a decade of archive or a documentation site with per-version content will hit one.

Large editorial operations with roles, approvals and scheduled multi-locale publishing are not a Framer job either. Saying that costs me work, and it's the reason the rest of this article is worth reading. If that describes you, Framer against Webflow and Next.js covers the ceilings in detail.

What we do on a build, in order

Text styles first, with heading tags locked to them before a page gets laid out. Retrofitting heading structure onto a finished site is miserable work. Semantic tags set per frame as each template gets built, because a cleanup pass at the end always misses two.

CMS structure next: Articles, Categories, Authors, with the reference fields wired so author and category pages generate themselves. Then the JSON-LD block in Custom Code, scoped per template, every field through | json. Then the answer to the page's question in visible text near the top, one topic per URL. Then the two curls, on every template.

Then the handover doc, carrying the slug rule, the heading-style rule and the redirect procedure. A build that only works while I'm the one touching it isn't finished. That sequence is what our Framer development work covers, from design through handover.

If you're on Framer and want to know what a crawler sees, send me your URL. I'll run both curls against your three most important pages, walk the heading outline, and send back what came out plus what I'd change. Twenty minutes of my time, costs nothing, and if it comes back clean I'll tell you that and we're done.

Questions people ask about this

Does my Framer site ship a comment telling AI agents it's server-rendered?

No. That comment sits in a custom-code snippet on framer.com's own pages. Curling a Framer-hosted customer site returns pre-rendered HTML with no such comment in the head. The pre-rendering is real on every site. The note about it belongs to framer.com.

What happens if I change a CMS slug in Framer?

The old URL stops working and no redirect is created. Framer's redirects documentation states that a sub-path changed from the canvas or the CMS won't update your redirect settings. Any link, citation or bookmark pointing at the old slug breaks immediately. Create the redirect manually in the same session, which requires Pro or above, or set a rule that published slugs don't change.

Will adding schema markup get my Framer site cited by AI?

Probably not on its own. Ahrefs' May 2026 controlled study of 1,885 pages found AI Overviews citations went down 4.6% after JSON-LD was added, statistically significant, with AI Mode and ChatGPT changes indistinguishable from noise. We ship Article and Organization because they're cheap and do conventional search work, and skip FAQPage because Google's FAQ rich result stopped appearing on 7 May 2026.

Can I publish to Framer from a script or a git repo?

Yes, since February 2026. Framer's Server API updates and publishes a project from any server, syncs collections with external sources and changes settings. It's a JavaScript SDK over a persistent WebSocket, in beta, and Framer's FAQ points at an example if you need REST endpoints in front of it. Budget for the wrapper and for your own error handling.

Does Framer generate an llms.txt file?

No. It's a manual upload under Site Settings, Hosting, Files, on Pro or Enterprise. Given that Ahrefs found 97% of domains with a valid llms.txt received zero requests in May 2026, I'd spend the hour on something else.

Questions people ask about this

Does my Framer site ship a comment telling AI agents it's server-rendered?

What happens if I change a CMS slug in Framer?

Will adding schema markup get my Framer site cited by AI?

Can I publish to Framer from a script or a git repo?

Does Framer generate an llms.txt file?