JavaScript rendering: can AI crawlers read your SPA?
The JavaScript problem for AI crawlers
Modern web development relies heavily on JavaScript frameworks such as React, Vue, Angular and Svelte. Single Page Applications (SPAs) load a minimal HTML document and build the entire interface via JavaScript. For human visitors with a modern browser, this is seamless. For AI crawlers that do not execute JavaScript, however, your page is effectively empty.
This problem touches the core of AI visibility. As we discuss in our article about AEO and why it matters, technical machine-readability is one of the three fundamental pillars. A website that is unreadable to AI crawlers cannot be cited, regardless of how excellent the content is.
The problem is not theoretical. GPTBot (OpenAI), ClaudeBot (Anthropic) and PerplexityBot do not execute JavaScript by default. They process the raw HTML response your server returns. If that response contains only an empty div element and a bundle of JavaScript files, the crawler receives no usable content.
<!-- What an AI crawler sees with a typical SPA -->\n<!DOCTYPE html>\n<html lang="en">\n<head>\n <title>My App</title>\n <meta name="description" content="" />\n</head>\n<body>\n <div id="app"></div>\n <script src="/js/app.bundle.js"></script>\n</body>\n</html>\n\n<!-- No heading, no text, no structured data.\n For an AI crawler, this page is completely empty. -->GPTBot, ClaudeBot and PerplexityBot do not execute JavaScript. Only Googlebot (which also feeds Gemini) executes JavaScript, but with a delay of seconds to minutes. Server-side rendering is not an option, it is a requirement for AI visibility.
Comparing rendering methods
There are four main approaches to rendering web pages, each with a different effect on AI visibility.
Client-Side Rendering (CSR)
With CSR, the server sends a minimal HTML document and all content is built via JavaScript in the browser. This is the default mode of most SPA frameworks. For AI crawlers, this is the worst possible situation: they receive no content.
Server-Side Rendering (SSR)
With SSR, the server generates the complete HTML for every request. The client receives a complete document with all content, heading structure and structured data. After loading, JavaScript takes over for interactivity (hydration). This is the ideal approach for AI visibility.
Static Site Generation (SSG)
With SSG, pages are pre-rendered during the build process and served as static HTML files. This combines the advantages of SSR (complete HTML) with extremely fast load times. Ideal for content that does not change frequently.
Incremental Static Regeneration (ISR)
ISR combines SSG with on-demand regeneration. Pages are served statically but updated in the background when they expire. This offers the speed of SSG with the freshness of SSR.
// Comparison: what each rendering method delivers for crawlers
// CSR (bad for AI crawlers)
// Server sends:
// Crawler sees: empty document
// SSR (good for AI crawlers)
// Server sends: fully rendered HTML with content
// Crawler sees: all text, headings, structured data
// SSG (good for AI crawlers)
// Server sends: pre-built HTML file
// Crawler sees: all text, headings, structured data
// Bonus: extremely low TTFB
// ISR (good for AI crawlers)
// Server sends: cached HTML, refreshed in background
// Crawler sees: all text, headings, structured data
// Bonus: combination of speed and freshnessDive deeper: robots.txt for AI crawlers | Schema.org markup for AI | Publication date and freshness
Solutions for existing SPAs
Not every organization can rewrite its frontend architecture overnight. Fortunately, there are intermediate solutions that improve the AI visibility of existing SPAs.
Dynamic rendering (pre-rendering for bots)
Dynamic rendering detects whether a request comes from a bot or a human visitor and serves a different response. Bots receive a fully rendered HTML version, while human visitors get the standard SPA.
# Nginx configuration for dynamic rendering
# Detect known bot user-agents and forward to the pre-renderer
map $http_user_agent $is_bot {
default 0;
~*GPTBot 1;
~*ClaudeBot 1;
~*PerplexityBot 1;
~*Googlebot 1;
~*anthropic-ai 1;
~*ChatGPT-User 1;
}
server {
listen 443 ssl;
server_name example.com;
location / {
if ($is_bot = 1) {
proxy_pass http://prerender-service:3000;
}
try_files $uri $uri/ /index.html;
}
}- Advantage: no major refactor needed. Your existing SPA remains unchanged for human visitors.
- Disadvantage: you must run and maintain a separate pre-render service. There is a risk of cloaking if the rendered version significantly differs from the SPA version.
- Services like Prerender.io, Rendertron (Google) and Puppeteer can serve as pre-render engines.
Hybrid rendering with frameworks
Modern frameworks offer increasingly better support for hybrid rendering, where you choose per page which method is most suitable.
// Next.js: choose the rendering method per page
// Server-Side Rendering (for dynamic content)
export async function getServerSideProps(context) {
const article = await fetchArticle(context.params.slug);
return { props: { article } };
}
// Static Site Generation (for static content)
export async function getStaticProps(context) {
const article = await fetchArticle(context.params.slug);
return {
props: { article },
revalidate: 3600, // ISR: revalidate every hour
};
}
// Nuxt 3: hybrid rendering configuration
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/blog/**': { swr: 3600 }, // ISR for blog posts
'/': { prerender: true }, // SSG for homepage
'/api/**': { cors: true }, // API routes
},
});How to test what AI crawlers see
Before implementing optimizations, it is essential to understand what AI crawlers actually see when they visit your website. There are multiple methods to test this.
- Curl with bot user-agent: curl -A "GPTBot" https://yoursite.com gives you exactly the HTML that GPTBot receives. If you see no content here, AI crawlers see nothing either.
- Google Rich Results Test: although designed for Google, this tool shows the rendered HTML after JavaScript execution. Compare this with the raw HTML to see the difference.
- View Source versus Inspect Element: in your browser, "View Source" shows the raw HTML (what crawlers see), while "Inspect Element" shows the rendered DOM (after JavaScript). The difference is telling.
- Lighthouse SEO audit: Lighthouse checks whether crucial elements are available in the initial HTML, without JavaScript.
# Test what AI crawlers see of your website
# Simulate GPTBot
curl -s -A "GPTBot" https://yoursite.com | head -100
# Simulate ClaudeBot
curl -s -A "ClaudeBot" https://yoursite.com | head -100
# Check if there is content in the initial HTML
curl -s https://yoursite.com | grep -c '<h1\|<h2\|<p'
# If this returns 0, your page is empty for AI crawlersStructured data and JavaScript
A common mistake is injecting Schema.org JSON-LD via JavaScript. If your structured data is only available after JavaScript execution, AI crawlers that do not support JavaScript miss this crucial metadata entirely. Always place JSON-LD in the initial server response, in the HTML head, regardless of your rendering method.
<!-- GOOD: JSON-LD in the initial HTML -->\n<head>\n <script type="application/ld+json">\n {\n "@context": "https://schema.org",\n "@type": "Article",\n "headline": "JavaScript rendering and AI crawlers",\n "author": {"@type": "Organization", "name": "AEO Expert"}\n }\n </script>\n</head>\n\n<!-- WRONG: JSON-LD injected via JavaScript -->\n<script>\n // AI crawlers do NOT see this\n const script = document.createElement('script');\n script.type = 'application/ld+json';\n script.text = JSON.stringify(schemaData);\n document.head.appendChild(script);\n</script>JavaScript is a powerful tool for user experience, but it is a wall for AI crawlers. Every byte of content you deliver exclusively via JavaScript is invisible to the majority of AI models.
Key takeaways
- Most AI crawlers (GPTBot, ClaudeBot, PerplexityBot) do not execute JavaScript and only see the raw HTML response from your server.
- Client-side rendered SPAs are effectively invisible to AI models. Server-side rendering is a requirement for AI visibility.
- Dynamic rendering (pre-rendering for bots) is an intermediate solution for existing SPAs that cannot be immediately rebuilt.
- Always place Schema.org JSON-LD in the initial HTML, never via JavaScript injection, to reach all AI crawlers.
- Test your website with curl and a bot user-agent to verify what AI crawlers actually receive.
Frequently asked questions
Does Googlebot execute JavaScript?
Yes, Googlebot is one of the few crawlers that executes JavaScript. Google uses a headless Chrome browser to render pages. However, there is a delay between the first visit and rendering, which can range from seconds to days. For time-critical content, server-side rendering is therefore also recommended for Googlebot. Other AI crawlers such as GPTBot and ClaudeBot do not execute JavaScript.
Is Next.js or Nuxt better for AI visibility?
Both frameworks offer excellent support for server-side rendering and static site generation. Next.js (React) and Nuxt (Vue) deliver fully rendered HTML by default that AI crawlers can process without problems. The choice between them depends more on your team's experience than on AI-specific considerations. The most important thing is that you actually activate SSR or SSG mode and do not fall back to pure client-side rendering.
Can I get cloaking issues with dynamic rendering?
Google does not consider dynamic rendering as cloaking, provided the rendered content substantially matches what human visitors see. The difference may lie in presentation (CSS, interactive elements), but not in content. If you serve bots fundamentally different content than visitors, you risk a cloaking penalty. Ensure the pre-rendered version contains the same text, headings and structured data as the client-side version.
How do I handle lazy-loaded content?
Content that only loads on scroll (lazy loading) is problematic for AI crawlers, because they do not scroll. Ensure all primary content (text, headings, structured data) is in the initial HTML response. Lazy loading is acceptable for non-essential elements such as images lower on the page or related articles. Use the loading="lazy" attribute for images, but not for content.
Do I need to completely rewrite my React app for AI visibility?
Not necessarily. If you are using React without server-side rendering, there are multiple steps before a full rewrite is needed. Start by adding pre-rendering for bots via a service like Prerender.io. Then consider migrating to Next.js, which offers server-side rendering as standard. A complete rewrite to a traditional server-rendered framework is only necessary if the pre-rendering approach yields insufficient results.
The best JavaScript is the JavaScript that the server has already executed for the crawler. AI visibility begins with the HTML your server returns, not the JavaScript your client executes.
How does your website score on AI readiness?
Get your AEO score within 30 seconds and discover what you can improve.