Why JavaScript Breaks Your AI Citation Eligibility
Vercel and MERJ analyzed 500M+ GPTBot requests and found zero JavaScript execution — so content rendered client-side is invisible to AI crawlers. If your answer only appears after JS runs, you can't be cited. Here are the SSR, SSG, and prerender fixes, with code.
JavaScript breaks AI citation eligibility because most AI crawlers don't run it — Vercel and MERJ analyzed over 500 million GPTBot requests and found zero JavaScript execution. If your content is assembled client-side, it isn't in the raw HTML the crawler reads, so you're invisible no matter how good the content is. The fix is to render on the server.
The short answer
AI crawlers read raw HTML and don't execute JavaScript (Vercel/MERJ, 500M+ GPTBot requests). Client-rendered content isn't in that HTML, so it can't be cited. Move citable content to SSR, SSG, or prerender so it's in the initial response.
What the AI crawler receives
Client-rendered
An empty shell — <div id="root"></div> and a script bundle — with no content, because the crawler never runs the JavaScript that would build the page.
Server-rendered
The full HTML in the first response: headings, the answer passage, links — everything present and citable, no JavaScript required.
Why can't AI crawlers see client-rendered content?
AI crawlers can't see content built by client-side rendering because they fetch the raw HTML and don't run the JavaScript that would build it. The Vercel/MERJ analysis of 500M+ GPTBot requests found no JavaScript execution at all — the crawler takes the server's initial response and nothing more. A client-rendered app typically ships a near-empty shell like this:
<!-- What the server sends for a client-rendered app -->
<!doctype html>
<html>
<head><title>My App</title></head>
<body>
<div id="root"></div>
<script src="/static/bundle.js"></script>
</body>
</html>A browser runs bundle.js, which fetches data and fills #root with your content.
A crawler doesn't — so it sees an empty #root and concludes the page has no
content. This is the access gate failing at the rendering layer:
permission was fine, but there's nothing to read.
How do you confirm this is your problem?
Confirm it by comparing the raw HTML to the rendered page: if your answer is on
screen but missing from curl, JavaScript is the culprit.
# If this prints nothing but the text is clearly on the page, it's client-rendered
curl -s -A "GPTBot" https://yourdomain.com/ | grep -i "your answer phrase"The full procedure — including the JS-disabled browser test — is in how to check if AI crawlers can read your site.
What are the fixes?
The fix is to get your content into the initial HTML response through server-side rendering, static generation, or prerendering. All three produce HTML that already contains your answer before any JavaScript runs.
| Strategy | When it renders | Best for |
|---|---|---|
| Static generation (SSG) | At build time | Content that changes infrequently — docs, articles, marketing |
| Server-side rendering (SSR) | At request time, on the server | Personalized or frequently-changing content |
| Prerendering | Cached HTML served to bots | Existing SPAs you can't fully rewrite |
| Client-side only (CSR) | In the browser, after fetch | Logged-in app views that needn't be cited |
Static generation or server-side rendering (Next.js)
In an App-Router framework, components render on the server by default, so the answer is in the HTML. A Server Component that fetches data renders fully before it reaches the browser — or the crawler:
// app/guides/[slug]/page.tsx — renders on the server; HTML ships with content
export default async function GuidePage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const guide = await getGuide(slug); // runs on the server
return (
<article>
<h1>{guide.title}</h1>
{/* This answer is in the initial HTML — crawlers can read it */}
<p>{guide.answer}</p>
</article>
);
}For static generation, pre-build the pages so they're served as ready HTML:
// Pre-render every guide at build time (SSG)
export async function generateStaticParams() {
const guides = await getAllGuides();
return guides.map((g) => ({ slug: g.slug }));
}Prerendering for an existing SPA
If you can't rewrite a single-page app, add a prerender layer that serves cached, fully-rendered HTML to crawlers. The deeper patterns are in how to make a single-page app citable by AI.
Can you still use a JavaScript framework?
Yes — the problem was never JavaScript frameworks; it's shipping an empty shell that only fills in client-side. React, Vue, Svelte, and their meta-frameworks (Next.js, Nuxt, SvelteKit, Remix) all support server rendering. Render citable content on the server, then hydrate for interactivity: crawlers get full HTML, users get the app.
The 'works in my browser' trap
Client-rendered content looks perfect in your browser, in screenshots, and in
Inspect Element — so the problem hides until you check the raw HTML. Don't
trust the rendered page; trust curl and View Source. The content
that isn't in the initial HTML doesn't exist as far as the crawler is
concerned.
Render for crawlers
0 / 5
Each unchecked box is a place a competitor can beat you to the AI answer.
Where this fits in the Canon
This is the rendering half of the access pillar — permission (robots.txt) gets the crawler in, but rendering decides whether there's anything for it to read. Once content is in the HTML, extractability decides how liftable it is.
Related: server-side vs client-side rendering for AI crawlers, how to make a single-page app citable, and how to check if AI crawlers can read your site.
Frequently asked questions
- Why does JavaScript break AI citations?
- Because most AI crawlers don't execute JavaScript. Vercel and MERJ, analyzing over 500 million GPTBot requests, found zero evidence of JavaScript execution — the crawler reads the raw HTML and stops. If your content is rendered client-side, it isn't in that raw HTML, so the crawler sees an empty shell and can't cite what isn't there.
- Do any AI crawlers run JavaScript?
- Google's Gemini is the main exception, because it can use Google's existing rendering infrastructure. For OpenAI's GPTBot and OAI-SearchBot, Anthropic's crawlers, and PerplexityBot, you should assume no JavaScript execution. Designing for the non-rendering case keeps you citable everywhere rather than only on the one engine that renders.
- How do I fix client-rendered content for AEO?
- Render the content on the server so it's in the initial HTML response. Use server-side rendering (SSR) or static site generation (SSG) in a framework like Next.js, Nuxt, or Remix, or add prerendering for a pure single-page app. The goal is that a curl fetch — with no JavaScript — already contains your answer text.
- Is client-side rendering always bad for AEO?
- Not always — it's fine for content that doesn't need to be cited or indexed, like logged-in dashboards. It's only a problem for public, citable content. The fix isn't to abandon JavaScript frameworks; it's to render the citable content on the server and hydrate for interactivity, so crawlers get full HTML and users still get an app.
Last updated .