Technical

JavaScript Rendering and AI Crawlers: Why GPTBot and ClaudeBot Can't See Your SPA

GPTBot, ClaudeBot, and PerplexityBot fetch a single HTML response and never execute a script. If your content only appears after client-side hydration, these crawlers see an empty shell - not the page a visitor sees. Here is how to spot the gap and fix it.

Neil Walsh·July 2026·7 min read

JavaScript rendering and AI crawlers describes a specific gap in how large language model crawlers retrieve web pages: GPTBot, ClaudeBot, and PerplexityBot each send a single HTTP request, read whatever HTML comes back in that response, and move on. None of them run a headless browser, none of them wait for a script to execute, and none of them make a second attempt. If your page depends on client-side JavaScript to render its main content, these crawlers see the empty app shell that loads before hydration - not the page a human visitor sees a second later.

This matters for AEO because passage-level citability depends entirely on the crawler actually receiving the text. A page can have flawless schema markup, strong E-E-A-T signals, and perfectly conversational copy, and still be invisible to every AI engine if that copy is injected by JavaScript after the initial response. Robots.txt access and a 200 status code both look fine in this scenario; the content is simply not there when the crawler reads it.

Which AI crawlers execute JavaScript, and which do not

The lightweight, high-volume AI crawlers are built for speed and cost efficiency, not fidelity. They impose tight timeouts, typically one to five seconds per fetch, and rendering a full browser session at their crawl scale would be prohibitively expensive. Skipping JavaScript execution keeps retrieval fast and cheap, at the cost of missing anything rendered client-side.

  • GPTBot (OpenAI) - fetches raw HTML only, no JavaScript execution
  • ClaudeBot (Anthropic) - fetches raw HTML only, no JavaScript execution
  • PerplexityBot (Perplexity) - fetches raw HTML only, no JavaScript execution
  • Applebot-Extended (Apple Intelligence) - fetches raw HTML only, no JavaScript execution
  • Meta-ExternalAgent (Meta AI) - fetches raw HTML only, no JavaScript execution
  • Googlebot-Extended (Gemini, Google AI Overviews) - the exception; reuses Google's existing Web Rendering Service and can execute JavaScript, subject to the same rendering queue delays as regular Googlebot

A 200 response in your server logs does not mean the crawler received usable content. If your core content is injected by client-side JavaScript, GPTBot, ClaudeBot, and PerplexityBot all record a successful fetch of a page that, from their point of view, is functionally blank.

Why single-page applications are affected the most

A classic single-page application - a Create React App bundle, a default Vue CLI build, or an Angular app served as static files - ships an HTML document that is little more than a title tag and an empty <div id="root">. The actual markup, text, and structure only exist once the JavaScript bundle downloads, parses, and hydrates that container in the visitor's browser. A crawler that stops at the raw HTML response never sees any of it.

Server-rendered frameworks do not have this problem in the same way, because the HTML returned on the first request already contains the fully rendered markup. The risk resurfaces, though, whenever a server-rendered page still defers its core content to a client-side data fetch - for example a component marked as client-only that calls an API after mount, with no data available in the initial server response. The framework being modern does not guarantee the content is crawlable; the specific rendering path each page takes does.

How to check whether your site has a rendering gap

  1. Fetch the page with curl or view the browser's page source (not the inspector's Elements panel, which shows the post-hydration DOM) and read the raw response body
  2. Compare that raw HTML against what you see in a normal browser tab to spot content that is present visually but missing from the response
  3. Disable JavaScript in your browser (DevTools > Settings > Debugger, or a dedicated extension) and reload the page to see approximately what a non-rendering crawler sees
  4. Repeat the check on your most important pages, not just the homepage - navigation and layout are often server-rendered while the actual body content loads client-side
bash
curl -s -A "GPTBot" https://yoursite.com/your-page | grep -o '<body.*</body>' | head -c 500

If the curl output above is a near-empty shell while the same page looks fully populated in a browser, you have confirmed a JavaScript rendering gap. That gap is invisible to a robots.txt check and to most uptime monitoring, which is why it is easy to miss for months.

Fixing the gap

Server-side rendering (SSR)

The most durable fix is rendering the page's HTML on the server for every request, so the first response already contains full content. Modern meta-frameworks make this the default rather than an opt-in - the fix is usually locating the specific components that were built as client-only and moving their data fetching to the server.

Static generation and prerendering

For content that does not change per request, generating static HTML at build time achieves the same result without server compute on every visit. This is the natural fit for blog posts, documentation, and marketing pages - exactly the content AEO strategy is usually trying to get cited.

Dynamic rendering as a stopgap

Where a full framework migration is not feasible in the short term, a dynamic rendering service detects crawler user agents and serves them a prerendered HTML snapshot of the same page a visitor would eventually see, while human visitors still get the interactive client-rendered version. This is a legitimate stopgap, not a manipulation, provided the snapshot and the live page show the same content - serving different substantive content to crawlers than to visitors is what turns this into cloaking.

Whichever fix you choose, the goal is unchanged: make sure the first HTTP response your server returns already contains the text you want an AI engine to cite. AI crawler log analysis and a correctly configured robots.txt both assume the crawler can read your content once it arrives; a JavaScript rendering gap breaks that assumption before either one is relevant. That same read-what-arrives logic is why sitemap.xml, robots.txt, and llms.txt carry unequal weight with AI crawlers - only two of the three are consistently read at all.

Run a free CiteRank audit to check whether your page's core content is present in the raw server response. The technical readiness check flags pages where visible content and crawlable content have drifted apart.

Frequently asked questions

Do AI crawlers like GPTBot render JavaScript?

No. GPTBot, ClaudeBot, and PerplexityBot all fetch a page with a single HTTP request and read the raw HTML response without executing any JavaScript. Content that only appears after client-side rendering or hydration is invisible to them, even though the same page looks complete in a browser.

Does this affect Next.js and other modern frameworks?

Pages rendered on the server, whether through server-side rendering or static generation, are not affected, because the initial HTML response already contains the full content. The risk only resurfaces on specific pages or components that are deliberately built as client-only and fetch their data after the page loads in the browser, regardless of which framework is used.

What is the fastest fix for a React single-page application?

Migrating fully to server-side rendering or static generation is the most durable fix, but the fastest stopgap is a dynamic rendering service that detects crawler user agents and serves them a prerendered HTML snapshot matching the content a visitor eventually sees.

Is serving prerendered HTML to crawlers considered cloaking?

No, provided the prerendered snapshot shows the same substantive content as the live page. Cloaking is the practice of showing crawlers different content from what visitors see; dynamic rendering that serves an accurate snapshot of the same page is a recognised, legitimate technique for handling JavaScript-heavy sites.

Does Googlebot-Extended render JavaScript for Gemini and AI Overviews?

Yes, this is the main exception. Googlebot-Extended reuses Google's existing Web Rendering Service, the same infrastructure regular Googlebot uses, so it can execute JavaScript and see a fully rendered page. It is still subject to the same rendering queue delays as standard Googlebot, so relying on it exclusively is not a substitute for fixing the underlying rendering gap.

How can I test whether GPTBot actually sees my page content?

Fetch the page with curl using a GPTBot user agent string, or view the browser's raw page source rather than the DevTools Elements panel, and compare that raw HTML against what a normal browser tab displays. A gap between the two confirms the crawler is not seeing your rendered content.

Does a JavaScript rendering gap affect regular SEO as well as AEO?

It can, but the impact differs. Googlebot for standard search results does execute JavaScript, so traditional SEO rankings are often unaffected. GPTBot, ClaudeBot, and PerplexityBot do not, so a page can rank well on Google while being entirely uncitable to every other major AI engine - the two problems require checking separately.

Free tool

See your AEO score in seconds

Paste your URL and get a full audit across all 9 AEO signals - schema, crawlers, E-E-A-T, and more.

Audit my site - it's free

Related reading

Technical

Why Schema.org markup is the single biggest lever for AI citation

May 2026
Technical

Is your robots.txt accidentally blocking ChatGPT and Claude?

May 2026