Corrected on September 17, 2026

The first version of this guide said Google-Extended fetches raw HTML (it's a robots.txt token with no crawler of its own), that Gemini and AI Overviews read the unrendered page (both sit on Google's rendering infrastructure), that 'use client' turns Next.js into a CSR framework, and that Googlebot can't handle IntersectionObserver (Google recommends it). The Vercel numbers were misdated, and a "live audit of 12 guides" had no saved data behind it, so I removed it. Framework names are updated too.

JavaScript SEO is the gap between what your framework renders and what a crawler actually receives. Sometimes the gap is zero: a server-rendered Next.js page ships full HTML, and every crawler reads the same words. Sometimes it's everything: a client-side React app ships an empty <div id="root"> plus a bundle, and a crawler that doesn't run JavaScript gets nothing. That second case costs more in 2026 than it used to, because the crawlers that feed ChatGPT, Claude and Perplexity don't render JavaScript at all.

If you want to check your own site while reading, open Lumina's JS vs No-JS tool in a second tab.

What JavaScript SEO Actually Is

Every page has two HTML versions: the raw HTML the server returns, and the DOM after every script has run. JavaScript SEO is about keeping the important content in the first one, or at least making sure every crawler you care about gets to the second.

You can see both yourself. view-source: in the browser shows the raw HTML. The Elements panel in DevTools shows the rendered DOM. On a static site the two are practically identical. On a client-side SPA built with React, Vue or Angular, the raw HTML is a near-empty shell and the content only exists after the bundle runs. Most real sites sit in between.

Why it matters now: crawlers split into two groups. Googlebot renders JavaScript with a headless Chromium. OpenAI's, Anthropic's and Perplexity's crawlers don't (the data is below). A CSR site that ranks fine on Google can still be close to invisible in ChatGPT.

How Google Renders JavaScript: Crawl, Render, Index

Google's documentation describes three phases: crawling, rendering, indexing. Googlebot queues every page that returns a 200 status for rendering, unless a robots meta tag or header tells Google not to index it. Most JavaScript SEO problems live in the gap between those phases.

Phase 1: crawling. Googlebot fetches the URL and reads the raw response: status code, headers, and whatever HTML exists before any script runs. Links in that raw HTML can be queued for crawling right away.

Phase 2: rendering. A headless Chromium runs the page and builds the DOM. In Google's words, the page "may stay on this queue for a few seconds, but it can take longer than that." There is no guaranteed time.

Phase 3: indexing. Google indexes the rendered HTML: title, headings, links, structured data, body text. Whatever JavaScript added is now available for ranking.

The old "two waves of indexing" model comes from Google I/O 2018, where John Mueller and Tom Greenaway described a first pass on raw HTML and a later pass after rendering, possibly days later. A year later, at the Chrome Dev Summit 2019, Martin Splitt said the median time between crawl and render had dropped to about five seconds. So "days or weeks" is outdated as a rule, but rendering is still a separate step that can be delayed and can fail. If a page's content, links or canonical exist only after rendering, you depend on that step going right.

The renderer doesn't behave like a user.

Google Search doesn't interact with your page. It doesn't click, doesn't type and doesn't scroll like a person. Content that only appears after a click or a scroll event won't be seen. Google doesn't publish a fixed rendering timeout, but slow scripts, failed API calls and blocked resources can all leave the renderer with a partial page.

SSR, CSR, Static, Hybrid: Pick One

Frameworks give you five rendering modes: server-side rendering (SSR), static site generation (SSG), incremental static regeneration (ISR), client-side rendering (CSR) and hybrid setups that pick per route. This choice matters more for JavaScript SEO than any tweak you make later. Four of the five put your content into the raw HTML. CSR doesn't.

Server-Side Rendering (SSR)

The server runs the framework for each request and returns complete HTML. The browser shows it immediately, then JavaScript hydrates it for interactivity. Crawlers that read raw HTML get the whole page. Next.js, Nuxt, SvelteKit, React Router v7 (formerly Remix) and Angular with @angular/ssr all support it. The cost is compute on every request.

Static Site Generation (SSG)

Every page is rendered to HTML at build time and served from a CDN. No per-request compute and nothing to go wrong at request time. Astro by default, Next.js with static params, Gatsby, Hugo, 11ty and plain HTML sites like Lumina work this way. The catch: every content change needs a rebuild, which gets painful with millions of URLs.

Incremental Static Regeneration (ISR)

Static pages with a revalidation timer. A page is generated, cached, and regenerated in the background after the timer expires. Next.js supports it natively, and Nuxt does through routeRules on hosts that support it. Astro has no built-in ISR; you get similar behavior only through a host adapter such as Vercel's. For crawlers it behaves like SSG: they get cached HTML.

Client-Side Rendering (CSR)

The server returns a near-empty shell and a script tag. The browser downloads the bundle, fetches data and builds the page. That's plain Vite + React without an SSR layer, Create React App, and many older Vue or Angular SPAs. Avoid it for pages that need to rank or be cited. Googlebot can render it, with the extra step and its risks. Crawlers that don't render JavaScript get the empty shell.

Hybrid (per-route mode selection)

Marketing pages and articles get SSG or SSR, the logged-in dashboard gets CSR. Next.js, Nuxt and SvelteKit all let you choose per route. Astro's islands model goes further: the page is static HTML, and only the interactive components ship JavaScript. For most sites, hybrid is the right answer.

ModeSEO RiskNon-rendering crawlers seeBest For
SSRLowFull contentPersonalized public pages, frequently changing content
SSGLowestFull contentMarketing pages, blogs, docs, product pages
ISRLowFull contentLarge catalogs, news sites, e-commerce
CSRHighEmpty shellLogged-in dashboards, internal tools
HybridLow (per route)Depends on the routeMost modern sites

The 7 Most Common JavaScript SEO Problems

Most are markup or routing fixes. One needs a different rendering mode.

1. Critical content only after hydration. Title and meta tags are in the raw HTML, but the H1, body copy and navigation only appear once the app has run. Googlebot will probably get there; GPTBot and ClaudeBot won't. The fix is SSR or SSG for that route. If a full migration isn't possible yet, render the critical content on the server and let JavaScript take over interactivity.

2. Navigation without real links. A <div onClick> or <span> that navigates via JavaScript looks like a link to people. Google's documentation is blunt: Google can only discover links that are <a> elements with an href attribute. That applies to Googlebot after rendering too, not only to non-rendering crawlers. React Router's <Link> and Next.js's <Link> both output a real <a href>, so use them.

3. Content that needs a scroll or click to load. Native loading="lazy" for images and iframes is fine, and so is IntersectionObserver, which Google explicitly recommends for lazy loading. What breaks is content that loads only on a scroll event, a "load more" click or a tab click with no fallback. Load it when it enters the viewport, or put it in the initial HTML.

4. noscript fallbacks that say nothing. A "please enable JavaScript" message in <noscript> doesn't help any crawler. A non-rendering crawler gets that message instead of your content. Either server-render the content, or treat the noscript block as irrelevant for SEO.

5. Hash-fragment routing. SPAs that route via #/products. According to Google's JavaScript SEO basics, Googlebot can't reliably resolve those URLs. Use the History API with real paths, and make sure the server returns the right HTML for each path.

6. Redirects in JavaScript. A 200 response with window.location = '/new-url' in a script. Google can follow it once the page is rendered, but that's a slower, weaker path than a server-side 301 or 302. Crawlers that don't render never see the redirect. Use server-side redirects wherever you can.

7. Heavy bundles and fragile data fetching. Large bundles, long chains of API calls and scripts that fail in a headless environment all raise the chance that rendering produces an incomplete page. The fixes overlap with Core Web Vitals work: split code, defer what isn't needed, ship less. Lumina's Core Web Vitals guide covers the practical side.

JavaScript SEO and AI Crawlers

The most-used AI crawlers don't execute JavaScript. They request the URL, read the raw HTML and move on. For them, CSR content doesn't exist.

The best public data comes from Vercel's analysis of its own network, published on December 17, 2024. In the month before, GPTBot made 569 million requests and Claude's crawler 370 million. Some of those requests even loaded JavaScript files (11.5% for ChatGPT, 23.8% for Claude), but none of the OpenAI, Anthropic, Meta, ByteDance or Perplexity crawlers executed them. The two exceptions Vercel names: Gemini uses Googlebot's infrastructure and renders JavaScript, and Applebot renders with a browser-based crawler too.

That has two practical consequences. First, Google's AI features (AI Overviews, AI Mode, Gemini) build on Google's rendered index, so a CSR page that Google indexes can still show up there. Google-Extended doesn't change that, because it's a robots.txt token, not a crawler (the AI crawlers guide has the full bot list). Second, ChatGPT, Claude and Perplexity depend on what their crawlers get in raw HTML. If your product descriptions, prices or FAQ answers only exist after hydration, those engines have nothing to quote.

How common is the problem? When I tested 50 large media sites in Germany, Austria and Switzerland in April 2026, seven of them showed 25% or more of their text only after JavaScript ran. The details are in the JS vs No-JS study.

Test this yourself in 60 seconds.

Open view-source: on an important page and search for a sentence from your body copy. If it's there, crawlers that don't render get it too. If you only see <div id="root"></div> and a script tag, that's all they get. Lumina's JS vs No-JS tool runs the same comparison and shows raw and rendered word counts side by side.

The fix is the one Google has recommended for years: server-side or static rendering. CSR was always an extra risk for Google indexing. For ChatGPT, Claude and Perplexity it's a hard wall, and the same architectural change solves both.

Framework Cheat Sheet: Next.js, Nuxt, SvelteKit, Astro, React Router

Every major framework has a rendering mode that works for SEO. Defaults and API names differ.

Next.js

The App Router (Next 13+) uses React Server Components by default, and pages are rendered on the server, either at build time or per request. generateStaticParams pre-renders dynamic routes, and export const revalidate = 60 turns on ISR. A common misunderstanding: 'use client' doesn't make a page client-rendered. Client components are still pre-rendered to HTML on the server; the directive only marks where JavaScript is shipped for interactivity. The real mistake is loading content in useEffect in the browser, which leaves the pre-rendered HTML with nothing but a spinner. Fetch content on the server instead.

Nuxt

Universal rendering (SSR) is the default. nuxt generate pre-renders all routes for static hosting, and routeRules in nuxt.config.ts lets you mix SSR, prerendering, ISR and client-only rendering per route. The mistake to avoid: setting ssr: false globally because a login flow was easier that way. That turns the whole site into a CSR app.

SvelteKit

Server-side rendering by default. +page.server.ts runs only on the server, +page.ts runs on both. export const prerender = true or @sveltejs/adapter-static gives you static output. The mistake to avoid: export const ssr = false on public routes.

Astro

Pages render to static HTML by default and ship no JavaScript unless you add it. Interactive components are opt-in islands: a React, Vue, Svelte or Solid component with client:load or client:visible ships JavaScript, everything else stays HTML. Per-request rendering needs a server adapter. For content sites where SEO comes first, it's the framework I'd recommend to a team starting fresh.

React Router v7 (formerly Remix)

Remix merged into React Router with version 7. In framework mode you get server rendering, loaders that run on the server and forms that work without JavaScript, the same model Remix had. You can also pre-render routes at build time. The mistake to avoid: running it in SPA mode for public pages.

How to Test JavaScript SEO

1. Raw vs rendered. Ctrl-U (Cmd-Option-U on Mac) shows the raw HTML, the Elements panel shows the rendered DOM. If the raw HTML is missing your H1, your body copy or your navigation links, AI crawlers don't get them and Google depends on rendering. Lumina's JS vs No-JS tool does the comparison for you: word counts before and after JavaScript, plus lists of the headings, links and images that only appear after rendering.

2. Google Search Console URL Inspection. Enter the URL, click "Test live URL", then "View tested page" and the HTML tab. That's what Google rendered, including resources that failed to load. Compare it with your DevTools view. Differences point to scripts blocked by robots.txt, API calls that timed out or content the renderer never reached.

3. Raw fetch as a bot. curl -A "GPTBot" https://your-site.com/page shows what your server returns for that user agent. It's a good test of whether the content is in the raw HTML and whether your server treats bot user agents differently. It's not a perfect copy of what OpenAI receives, though: firewalls and bot management often check IP ranges, so a spoofed user agent from your laptop can get a different answer than the real crawler.

Three more tools help:

  • Lumina JS vs No-JS. Free. Compare mode renders one URL with and without JavaScript and lists what's missing. Whole-site mode reads up to 300 URLs from your sitemap as raw HTML, flags pages that look like empty framework shells and exports the table as CSV.
  • Lumina Crawler Access Checker. Tests your robots.txt against 37 search and AI crawlers, including GPTBot, ClaudeBot, PerplexityBot and Google-Extended. A bot that's blocked in robots.txt doesn't get to your JavaScript question in the first place.
  • Google Rich Results Test. Renders the page and shows which structured data Google finds after JavaScript runs. Works with URLs and pasted code.

Dynamic Rendering: Why It Is Not the Answer

Dynamic rendering means serving users the JavaScript app and serving bots a pre-rendered HTML version, for example through Prerender.io. Google still processes pages set up this way. But Google's documentation calls it "a workaround and not a long-term solution for problems with JavaScript-generated content in search engines" and recommends server-side rendering, static rendering or hydration instead. Three reasons I wouldn't build on it today.

Google itself steers you away from it. It was a bridge for a time when Googlebot's rendering was weaker, and Google hasn't formally removed support.

It doubles your maintenance. You run two versions of every page: the app for users and the pre-rendered HTML for bots. Content changes, caching and bug fixes have to work in both, and the version your team tests in the browser isn't the one the crawler gets.

It only helps the bots on your list. Dynamic rendering switches on user agent. Setups built years ago often list Googlebot and Bingbot but not GPTBot, ClaudeBot or PerplexityBot, so those crawlers fall through to the empty shell. SSR or static rendering doesn't depend on a list: every crawler gets the same HTML.

If a dynamic-rendering setup works for you today, there's no need to tear it out tomorrow. Check that the AI crawlers you want are on the bot list, and plan the move to SSR or static for your next rebuild.

FAQ

What is JavaScript SEO in simple terms?+
It's making sure search engines and AI crawlers can read the content that JavaScript puts on your page. If text, links or product data only appear after a script runs in the browser, a crawler that doesn't execute JavaScript gets an empty page. Googlebot renders JavaScript, as a separate step that can be delayed or fail. The crawlers behind ChatGPT, Claude and Perplexity don't render it at all.
Does Google render JavaScript on every page?+
Google queues every page that returns a 200 status for rendering, unless a robots meta tag or header says not to index it. Its documentation describes three phases: crawling, rendering, indexing. Rendering can happen within seconds but can take longer, and there's no guaranteed time. Pages that depend entirely on client-side JavaScript do get indexed, but they rely on that extra step working.
Do AI crawlers like ChatGPT and Claude execute JavaScript?+
No. Vercel's December 2024 analysis of its own network found that OpenAI's crawlers (GPTBot, OAI-SearchBot, ChatGPT-User), ClaudeBot, PerplexityBot, Meta-ExternalAgent and Bytespider fetch HTML and sometimes JavaScript files, but never execute the scripts. The exceptions were Gemini, which uses Googlebot's rendering infrastructure, and Applebot. So content that only appears after hydration is missing for ChatGPT, Claude and Perplexity.
Which JavaScript framework is best for SEO?+
The rendering mode matters more than the framework. Next.js, Nuxt, SvelteKit, Astro, React Router v7, Angular with @angular/ssr and Gatsby all support server or static rendering. Plain client-side React with Vite or Create React App doesn't. Pick the framework your team knows, then use server or static rendering for public pages. If SEO is the main constraint and you're starting fresh, Astro is a strong default because it ships no JavaScript unless you opt in.
How do I test if my JavaScript SEO is working?+
Three checks. View the source of an important page (Ctrl-U) and look for your body copy and links in the raw HTML. Run the URL through Lumina's JS vs No-JS tool to compare word counts and see what only appears after JavaScript runs. Then use URL Inspection in Search Console to see what Google actually rendered. If the content is only in the rendered version, AI crawlers are missing it and Google depends on rendering.
Is dynamic rendering still recommended?+
No. Google's documentation calls dynamic rendering a workaround and not a long-term solution, and recommends server-side rendering, static rendering or hydration instead. Existing setups still work, but they double your maintenance and only help the bots on the user-agent list. If a setup doesn't include GPTBot, ClaudeBot or PerplexityBot, those crawlers get the empty app shell. For a new site, pick SSR or static rendering.

Where to Start

Five steps, in order.

Measure the render gap

Run Lumina's JS vs No-JS tool on your most important templates: homepage, a category page, an article or product page. The difference between raw and rendered word count is the content AI crawlers don't get.

JS vs No-JS →
Switch public pages to SSR or SSG

On Next.js, Nuxt, SvelteKit or Astro this is usually a per-route setting. On plain Create React App or Vite + React, plan a move to a framework that renders on the server.

Framework cheat sheet →
Fix JS redirects and fake links

Search your code for window.location = and replace those with server-side 301s. Search for navigation built on onClick without an href and turn it into real <a href> links.

See the 7 problems →
Check which bots may fetch

Run Lumina's Crawler Access Checker and confirm robots.txt allows the crawlers you want, for example OAI-SearchBot and PerplexityBot for AI search.

Crawler Access →
Check before every release

Add a raw-HTML check to your release checklist for changes that touch rendering. A page that quietly switches from SSR to CSR is expensive to discover weeks later in your rankings.

JS vs No-JS →

See what crawlers get from your pages

Lumina's free JS vs No-JS tool compares raw and rendered HTML for any URL and lists the headings, links and images that only appear after JavaScript runs. Whole-site mode checks up to 300 pages from your sitemap. No signup.

Run the JS vs No-JS Tool →