Next.js gives you every AEO control Framer gives you, and you build each one yourself. Metadata, canonical tags, JSON-LD, a Markdown version of every page, and a log you can read. Budget a day of work on an App Router site that already exists. The Markdown endpoint is the piece Framer ships free and the one worth building first.
Oskar Mieta
Founder, Designer & Developer
Summarize with AI
Category
Platform
Reading time
6 min
Next.js gives you every AEO control Framer gives you, and you build each one yourself. Metadata, canonical tags, JSON-LD, a Markdown version of every page, and a log you can read. Budget a day of work on an App Router site that already exists. The Markdown endpoint is the piece Framer ships free and the one worth building first.
Every API below is written against Next.js 16.3.4, the current stable release, and the App Router. One file convention was renamed in version 16 and this article uses the new name.
Desses builds in Next.js and React, from $8,000 over about six weeks, and we're also a Framer Partner selling Framer development at the same starting price. We get paid whichever way this decision goes. Read the disqualification section first: most marketing sites that land here belong on Framer.
Keep themeColor, colorScheme and viewport out of that object. They've been deprecated inside metadata since Next.js 14. The canonical is the line people skip, and a site with tracking parameters and a paginated archive serves one article at four URLs.
Copy the .replace(/</g, '\\u003c') with the rest. It rewrites every < as its unicode escape, which stops a </script> arriving from your CMS from closing the tag early and turning the rest of that field into live markup. Next's own guide is blunt: JSON.stringify "does not sanitize malicious strings used in XSS injection". Any snippet without that line is one untrusted field away from a stored XSS.
It belongs in a server component because the script has to be in the bytes that come back from the first request. Build the same object in a useEffect and it appears after hydration, where no major AI crawler will see it.
Framer returns a Markdown version of any page from an Accept: text/markdown header or ?md on the URL, with no configuration. Next.js has no equivalent until you write one, and it's about forty lines.
A page is always the leaf of its route subtree, so the handler lives one segment down. Route handlers take a params promise and return a plain Response, which is where the content type gets set.
One trap worth the paragraph. Negotiating on Accept means one URL has two representations, and a CDN holding the cached HTML will serve it to the next client asking for Markdown, which is what Vary: Accept exists to prevent. The ?md path sidesteps it, since the query string is part of the cache key.
The real cost sits upstream. If your content is MDX or arrives from a CMS as Markdown, post.markdown already exists and this is a morning. If your pages are hand-built JSX, you're writing a serializer.
Whether AI crawlers ever ask for the Markdown is unmeasured, ours included, and llms.txt is the cautionary tale. The argument for this endpoint is that it lives on the page's own URL, where a crawler already is.
Most of the time, for most teams, and this is the section I'd want read out loud on the call.
Everything above is a file in a repository. Changing a meta description means a branch, a review and a deploy, and a marketer who wants to reword a hero on a Tuesday files a ticket. If nobody on the team can open proxy.ts without help, the Markdown endpoint breaks during an upgrade and nobody notices for months. Version 16 renamed that file. Somebody had to run the codemod.
Framer does the same jobs with none of that, editable by whoever is holding the laptop. If a marketing site is the whole scope and there's no engineer on the team, that's the answer, and article 09 compares the platforms properly.
Next.js earns its place on a short list of conditions. The marketing site sits next to a product surface with auth or dashboards. You need numbered pagination URLs or RSS, and Framer produces neither. Your content pipeline is genuinely custom, though Framer's Server API closed most of that gap in February 2026. Picking the heavier tool for a job the lighter one does costs you every month afterwards.
Curl a page with JavaScript off and read what comes back before touching anything else. Then metadataBase in the root layout, and generateMetadata on every dynamic route with an explicit canonical. Article and Organization JSON-LD from server components, sanitized, matching the visible text.
The Markdown route handler and the proxy rewrite, revalidated in the same webhook as the page. Then app/robots.ts and app/sitemap.ts, generated from code so the search-bot names sit under code review, and a log drain, because server logs are the only method that shows whether retrieval bots reach you at all.
If you're on Next.js and want to know what an agent reads, send me a URL. I'll fetch it with JavaScript off, send back what came out of the initial HTML, and tell you what the Markdown route would take on your setup. I'm around this week.
No evidence either way, and no independent party has compared the platforms on retrieval. Both serve full text in the initial HTML when they're set up properly, and that's the part deciding whether a crawler can read you. The rest is content and entity strength.
What's the Next.js equivalent of Framer's `?md`?
A route handler that returns text/markdown, plus a proxy.ts rewrite when the request carries ?md or an Accept: text/markdown header. Code for both is above. Framer ships the same behaviour on optimised pages, no configuration.
Why does the JSON-LD snippet need that `.replace()`?
Because JSON.stringify doesn't sanitize XSS payloads, and you're passing its output to dangerouslySetInnerHTML. A </script> sitting in a CMS field closes the tag early and everything after it renders as markup. Escaping every < to \u003c makes that impossible. Next's docs carry the warning directly above the example, and community serializers like serialize-javascript do the same job.
Do I need llms.txt?
Probably not. Ahrefs found 97% of the valid llms.txt files across 137,000 domains got zero requests in May 2026, and that sample came from an SEO-tool-using population with adoption several times higher than anyone else measures. Google has said no three times. If you want one anyway it's a static file in public/, fifteen minutes.
Can I do all this on the Pages Router?
Yes, with different APIs. next/head for metadata, getStaticProps for the build, API routes for the Markdown endpoint. The metadata export and generateMetadata are App Router only.
Should the marketing site live in the same repo as the app?
Whoever needs to deploy a copy change decides. One repo means marketing waits on the app's release process. Two repos means two deployments and a design system to keep in sync. Split them when a non-engineer touches the marketing site weekly.
Questions people ask about this
Does Next.js get cited more than Framer?
What's the Next.js equivalent of Framer's `?md`?
Why does the JSON-LD snippet need that `.replace()`?
Do I need llms.txt?
Can I do all this on the Pages Router?
Should the marketing site live in the same repo as the app?