JavaScript SEO is the practice of making content that a browser builds with JavaScript readable by the crawlers that index and cite it.
Googlebot has rendered JavaScript for years, so the old warning about React apps being invisible to Google is out of date. The crawlers behind ChatGPT, Claude and Perplexity are another story, and most site owners have never measured the gap between the two.
What JavaScript SEO is
Three stages, in this order: crawl, render, index. A crawler requests a URL and receives whatever HTML the server sends. If it has a rendering engine, it then executes the page's JavaScript and builds the same DOM a browser would build. Only after that does it decide what the page says and whether to store it.
The JavaScript is never the problem. The problem is content that exists only after the JavaScript has run.
Nothing about SEO and JavaScript changes the ranking rules. A heading is still a heading, a link is still a link, a canonical still consolidates signals. What changes is whether a given crawler ever reaches the stage where it can see them. This JavaScript SEO guide is about that gap.
How Googlebot renders JavaScript
It does, and it has since 2019.
Googlebot runs an evergreen headless Chromium kept in step with the current stable Chrome release. The pipeline has four steps: fetch the URL and parse the raw HTML, queue the page for rendering, execute the JavaScript in headless Chromium, index the rendered output rather than the original response.
Rendering happens on that second pass, and the second pass can lag the initial crawl. Google's guidance is that the wait is usually short now, so don't plan your architecture around a delay figure quoted in a 2019 conference talk.
Most writing about Google SEO and JavaScript is still arguing with a problem Google solved. For Google Search specifically, a modern framework is usually fine. If your React, Vue or Svelte site renders in the browser and Googlebot can fetch the bundles it needs, Google will index what the user sees.
Two things still break, even for Google. Content that depends on a user action never runs, because Googlebot does not click, scroll, hover or log in. And rendering costs real resources, so very large client-rendered sites can see render coverage thin out across the long tail.
Why AI crawlers are different
The crawlers that feed ChatGPT, Claude and Perplexity read your raw HTML and stop.
That is measured JavaScript SEO behaviour, not an assumption. Vercel analysed over 500 million GPTBot requests and found no evidence of JavaScript execution anywhere in the sample. GPTBot fetched .js files in about 11.5% of requests and never ran them. ClaudeBot downloaded JavaScript in roughly 23.8% of requests, also without executing it. Fetching a file and running it are two different operations; these crawlers do the first only.
Three reasons they were built this way, and all three shape JavaScript SEO decisions:
- They are AI-native. OpenAI and Anthropic built their crawlers from scratch, inheriting none of the rendering infrastructure search engines spent two decades building.
- Rendering is expensive. A headless browser per URL costs orders of magnitude more CPU and memory than fetching a document. At crawl scale that is an enormous bill for content most publishers put in the HTML anyway.
- They run under tight timeouts. This bites hardest for bots fetching a page live while a user waits. A two-second render pass is not an option when the answer is already streaming.
Crawler rendering behaviour, as of October 2026:
| Crawler | Renders JavaScript? |
|---|---|
| Googlebot | Yes, evergreen headless Chromium |
| Bingbot | Yes |
| Applebot | Yes |
| Google-Extended | Yes, inherits Googlebot's rendering |
| GPTBot | No. Fetches .js, never executes it |
| OAI-SearchBot | No |
| ChatGPT-User | No |
| ClaudeBot | No, raw HTML only |
| Claude-SearchBot | No |
| PerplexityBot | Mostly no. Partial support reported, unreliable |
| CCBot | No |
| Applebot-Extended | Rides Applebot's crawl, no independent rendering confirmed |
Crawler behaviour changes, so treat the table as an October 2026 snapshot.
Google's AI answers are the exception
Here is the part most articles on this topic flatten.
Google's AI surfaces render JavaScript. AI Overviews and AI Mode run on the same Chromium-based pipeline as Google Search, and Google-Extended is a control token layered on Googlebot's crawl rather than a separate crawler with its own fetcher. Content that Google Search can render is generally available to Google's AI answers as well.
So "AI crawlers don't render JavaScript" is a statement about ChatGPT, Claude and Perplexity. Applied to Google it is false. Believe the flat version and you will conclude a client-rendered site is invisible to every AI surface, then make architecture decisions on a premise that is half wrong.
Access and readability are separate gates, and they fail separately. Our robots.txt guide covers whether a crawler is allowed in. This one covers whether it can read anything once it arrives.
The hydration problem
Pre-rendering fixes most of this, and it is the cheapest fix available.
The HTML for each route is generated once at build time and shipped complete, so the server's first response already carries the headings, the body copy and the structured data. In the browser, JavaScript then attaches event handlers to markup that already exists. That attachment step is hydration. Done properly, a crawler with no JavaScript engine still receives the entire page.
Done carelessly, it produces the most dangerous failure mode in JavaScript SEO, because the site looks perfect to everyone who checks it in a browser.
Ours did. On 14 October 2026 UtilLab shipped this blog to a production build whose pre-render step covered the tool routes and had never been extended to the blog routes. Every article URL returned the homepage's HTML: the same 37,352 bytes on all of them, the same <h1>, the canonical pointing at the homepage. The real article appeared only once React hydrated in the browser.
Googlebot would have rendered its way through to the articles eventually. GPTBot would have seen the homepage at every single article URL, and so would ClaudeBot, Claude-SearchBot and CCBot.
It was caught by fetching the raw HTML before launch, not by opening the pages. In a browser, every one of them was flawless.
A page can satisfy every human who visits it and still be empty to the crawlers that decide whether you get cited. The only check that catches this is a request with no JavaScript execution.
How to check what Google sees
Three JavaScript SEO checks, in increasing order of effort:
- URL Inspection in Search Console. Inspect the live URL, open View Crawled Page, then read the HTML tab. That is the rendered HTML Google holds. The screenshot tab beside it catches CSS and layout failures the HTML alone hides.
- Disable JavaScript in Chrome DevTools. Open the command menu with Ctrl+Shift+P, run Disable JavaScript, then reload. What survives is close to what a non-rendering crawler receives.
- Compare View Source against the rendered DOM. Ctrl+U shows the HTML the server actually sent. The Elements panel shows the DOM after JavaScript has finished. Anything in the second and missing from the first exists only after rendering. That difference is your exposure.
How to check what AI crawlers see
The decisive JavaScript SEO test is one request with no JavaScript execution:
curl -A "GPTBot" https://example.com/ | grep "<h1"
If your real heading is not in that output, your content is not there for any crawler that cannot render. Run it on one URL per template, not just the homepage, which is usually the one route built correctly.
A fuller version of the same test checks the body copy rather than the heading:
curl -sA "ClaudeBot" https://example.com/some-article/ | grep -c "a distinctive phrase from the article"
A count of 0 means the phrase does not exist in the HTML Claude reads.
Without a terminal, the AI Visibility Checker runs the same fetch and reports several signals together: whether the raw HTML carries your main content with JavaScript switched off, which AI crawlers your robots.txt admits, whether llms.txt exists, and whether Organization schema is present for the assistants that use it to attribute a source. Of the JavaScript SEO testing tools here it is the quickest, answering the access question and the rendering question in one pass.
Rendering strategies compared
| Strategy | Visible to Googlebot | Visible to AI crawlers | Build complexity | Best for |
|---|---|---|---|---|
| Client-side rendering (CSR) | Yes, after the render pass | No | Low | Apps behind a sign-in |
| Server-side rendering (SSR) | Yes | Yes | Medium | Pages whose content changes per request or per user |
| Static generation / pre-rendering (SSG) | Yes | Yes | Low to medium | Articles, docs, marketing and tool pages |
| Incremental static regeneration (ISR) | Yes | Yes | Medium | Large catalogues that change often |
| Dynamic rendering | Yes | Only the bots on your list | High | Legacy sites that cannot be rebuilt |
The recommendation for SEO for JavaScript pages is narrow enough to state plainly: pre-render or server-render anything you want crawled or cited. Static generation is the right default for content, because the output is a file on a CDN and there is no runtime left to fail.
Client-side rendering earns its place in a dashboard behind a login, where no crawler is invited and the first paint is already authenticated. Shipping a public marketing or content page as CSR in 2026 trades away every AI surface except Google's to skip a build step you were going to run anyway.
Dynamic rendering, where you detect a bot's user agent and serve it pre-rendered HTML, still works and Google permits it. It also means maintaining a user-agent list forever, and learning about each new crawler only after it has crawled you badly.
Common JavaScript SEO issues and fixes
Eight JavaScript SEO issues worth checking for, each with the symptom that gives it away.
1. Content only in the hydrated DOM. The page looks complete in a browser and View Source shows an empty <div id="root">. Pre-render or server-render the route so the copy ships in the first response.
2. Links as click handlers. Navigation built from <div onClick={...}> or a <button> that calls the router. Crawlers follow href attributes, so those routes are discoverable only through your sitemap. Use a real <a href="/path/"> and let the framework intercept the click.
3. Meta tags and canonicals injected client-side. The title and canonical look correct in DevTools and are absent from View Source. Non-rendering crawlers get no title, and a client-side canonical is unreliable even for Google. Render both on the server.
4. Blocked JavaScript and CSS in robots.txt. A Disallow on /assets/, /static/ or /_next/ stops Googlebot loading the files it needs to render, so it indexes a broken page. The Robots.txt Checker shows which crawlers each rule actually stops and names the line responsible.
5. Infinite scroll with no pagination. Items load on scroll, which crawlers never do, so everything past the first batch is unreachable. Add <a href> pagination behind the scroll, or list every item URL in a sitemap.
6. Soft 404s from client-side routing. A URL that should not exist returns HTTP 200 with a "not found" component drawn in the browser. Google treats the 200 as a real page and may index the error state. Return a genuine 404 status from the server.
7. A pre-render step that silently skips a route. New route group, old build config, and every URL in it serves the fallback HTML. This is the bug described above, and nothing in the build output complains. Compare the routes your pre-render step wrote against the URLs in your sitemap after every build.
8. Content hidden behind an interaction. Tabs, accordions and "read more" panels that fetch their content on click. If it matters for ranking or citation, it belongs in the initial HTML, visually collapsed if you like.
When several routes have been serving the same HTML for a while, rendering is only half the repair. Those pages also need judging on merit, which is what running a content audit decides: what to keep, improve, merge or remove once crawlers can read them.
How page speed affects JavaScript SEO
Large bundles delay Largest Contentful Paint, because the browser cannot paint meaningful content until it has parsed and executed enough JavaScript to produce it. Hydration then occupies the main thread at the moment a user first tries to tap something, which inflates Interaction to Next Paint. Both are Core Web Vitals, and both worsen with every kilobyte of script.
Pre-rendering helps here too. The first paint comes from HTML rather than from a bundle, so LCP stops depending on your JavaScript at all.
The Website Auditor's Core Web Vitals section reports LCP, INP and CLS from real CrUX field data, which matters because lab scores from a fast machine routinely disagree with what mid-range phones experience. Audits run five per visitor per hour.
JavaScript SEO checklist
Twelve checks, each one verifiable rather than advisory. Work through this JavaScript SEO checklist against a production build, never the dev server.
curl -s https://yourdomain.com/ | grep "<h1"returns your real heading, and the same holds for one URL from every template.- The raw HTML of each template contains the body copy, not a loading shell. Test an article, a tool page and a listing page separately.
<title>, the meta description andrel="canonical"all appear in View Source.- Every navigational link is an
<a>element with anhrefpointing at a real URL. - robots.txt does not disallow any path your JavaScript or CSS is served from.
- URL Inspection in Search Console shows the rendered HTML you expect, with no indexing error.
- URLs that should not exist return a 404 status code from the server, confirmed with
curl -I. - Paginated and infinite-scroll lists expose crawlable
<a href>pagination, or every item URL sits in a sitemap. - Your pre-render or static-generation step covers every route group. Route count in the build output matches URL count in sitemap.xml.
- Two unrelated URLs return different HTML byte counts. Identical sizes mean one template is being served everywhere.
- JSON-LD structured data is present in the raw HTML, not injected after load.
- LCP and INP from field data sit inside their thresholds on mobile.
Run the first item today, on the page you most want ChatGPT to cite. It takes four seconds.
Frequently asked questions
What is JavaScript SEO?
JavaScript SEO is the practice of making content that a browser builds with JavaScript readable by the crawlers that index and cite it. It covers how search engines crawl, render and index JavaScript-driven pages, and how to get that content into the initial HTML for the crawlers that never run JavaScript at all.
Is JavaScript bad for SEO?
No. Googlebot has rendered JavaScript since 2019 and indexes client-rendered sites. JavaScript becomes a problem when content exists only after the script runs, because the AI crawlers behind ChatGPT and Claude read the raw HTML and stop there. Pre-rendering or server-rendering removes the risk without changing your framework.
Does Google index JavaScript content?
Yes. Googlebot runs an evergreen headless Chromium, executes your JavaScript and indexes the rendered result. Rendering happens on a second pass after the initial crawl, so it can lag. Content that only appears after a click, a scroll or a sign-in still never runs for Googlebot.
Can ChatGPT read JavaScript websites?
Mostly no. Vercel's analysis of over 500 million GPTBot requests found no evidence of JavaScript execution: GPTBot fetched .js files in about 11.5% of requests and never ran them. If your content is missing from the raw HTML, ChatGPT sees an empty shell where your page should be.
How do I check if my JavaScript content is indexed?
Use URL Inspection in Google Search Console, open View Crawled Page and read the HTML tab: that is what Google indexed. For the crawler's view with no JavaScript, run curl -s https://yourdomain.com/page/ | grep '<h1', or disable JavaScript in Chrome DevTools and reload the page.
Does server-side rendering help SEO?
Yes, mostly for the crawlers that never render. Server-side rendering and pre-rendering both put your content into the initial HTML response, which is the only thing GPTBot, ClaudeBot and CCBot read. For Google Search alone the gain is smaller, since Googlebot renders anyway, but it does remove the render-queue delay.