Iron Goo
---
title: "Do AI Crawlers Render JavaScript?"
seoTitle: "Do AI Crawlers Render JavaScript?"
description: "Most AI crawlers do not run JavaScript. Use this ten-minute test on three pages to check whether buying facts appear in the HTML your server sends."
datePublished: "2026-09-23T12:34:00Z"
dateModified: "2026-11-01T19:20:00Z"
category: "seo"
imageAlt: "A woman checks a list beside a monitor comparing a content-filled page with an almost empty page."
tags: ["javascript-seo", "ai-crawlers", "rendering", "technical-seo", "ai-search"]
faq: true
headQuery: "do ai crawlers render javascript"
featuredImageFile: "/blog/art/en/blank-page-render-hero.c5945d38.webp"
---

Most AI crawlers did not render JavaScript in the published tests. If you are asking “do AI crawlers render JavaScript,” the useful answer starts with the date: [Vercel and MERJ reported on 17 December 2024](https://vercel.com/blog/the-rise-of-the-ai-crawler) that none of the major AI crawlers in their analysis rendered it. ChatGPT and Claude crawlers fetched JavaScript files without executing them; those files accounted for 11.50% and 23.84% of their respective fetches.

Googlebot renders JavaScript, and Vercel says Gemini uses Googlebot's infrastructure and AppleBot renders through a browser-based crawler. For a fetch that does not run JavaScript, facts added by a script after load are invisible. The practical test is whether your buying facts are in the HTML your server sends.

## What newer tests and our logs show

A controlled test of assistant URL fetches found that several assistants answered from the raw HTML. In [Andre Alpar's Search Engine World test, dated 9 June 2026](https://www.searchengineworld.com/do-ai-assistants-actually-render-your-javascript-when-grounding-we-put-it-to-the-test), each assistant received a unique, unlinked URL with a decoy number in the HTML and a real number loaded by a script from a second endpoint.

ChatGPT, Claude, Gemini, Perplexity, Meta AI and Copilot reported the decoy. Grok ran the script on one node and still answered with the decoy; DeepSeek, ERNIE, Qwen, Kimi and Mistral ran the JavaScript. The test used one site, one prompt per assistant and one point in time, and tested user-triggered URL fetches, not background crawlers.

Gemini is the surprise: [Search Engine Roundtable reported on 5 May 2025](https://www.seroundtable.com/googles-ai-crawler-renders-javascript-39353.html) that Google's Martin Splitt said in an interview with Kenichi Suzuki of Faber Company that Google's Web Rendering Service is shared across services including Web Search and Gemini, yet Gemini's live fetch answered from raw HTML in this test, so treat each path as unknown. [OpenAI's crawler documentation](https://developers.openai.com/api/docs/bots) does not say whether its bots run JavaScript.

**First-party log aside:** In irongoo.com nginx access logs from 18 October to 1 November 2026, verified against OpenAI's published IP lists, GPTBot made 547 requests: 464 for pages and 5 for JavaScript files. OAI-SearchBot made 420 requests: 127 for pages, 2 for JavaScript files and 281 for Next.js `?_rsc=` data URLs that do not appear in the page HTML. Those `_rsc` requests suggest some OpenAI fetch path now runs router code somewhere; that is our untested inference from one site, and it does not change the test or fix.

Google processes JavaScript in a separate rendering step; the [full crawl and render audit](/guides/seo/technical-seo-and-crawl-cost) covers how to check that process and how long rendering can take.

## Test three pages in ten minutes

A ten-minute test checks whether buying facts survive in the HTML sent for three pages. Our rule of thumb is to choose three pages and three exact phrases per page, then repeat the commands for each page.

::::flow
:::step{title="1. Choose pages and phrases"}
Pick a service page, a pricing page, and a contact or locations page. Copy a price, a town or service area, and a service sentence from each where available; a missing buying fact is a reason to choose another exact phrase a buyer would need.
:::
:::step{title="2. Check the browser"}
Open `view-source:` followed by each page URL and search for the phrases. Then disable JavaScript in Chrome DevTools and reload: phrases still visible are a preliminary pass; phrases that disappear need the command-line checks.
:::
:::step{title="3. Count a phrase as GPTBot"}
Run the first command with your page URL and exact phrase. A count above zero means the phrase occurs in the response; zero when the browser shows it is a failure.
:::
:::step{title="4. Compare user agents"}
Run the second command for each page. Matching status and similar byte size are a pass for this check; a different status or a much smaller GPTBot response needs an access investigation.
:::
:::step{title="5. Check readable text"}
Run the Python checker for all three phrases on each page. `IN THE TEXT` passes; `MISSING` and `ONLY INSIDE A SCRIPT` need a fix.
:::
::::

In Chrome DevTools, press `Ctrl+Shift+P` on Windows or `Cmd+Shift+P` on a Mac, type `Disable JavaScript`, select the command and reload. View source shows the server's HTML; the JavaScript-disabled reload shows what the browser can display without running the page's scripts.

:::callout{type="warn" title="Inspect shows the rendered page"}
Inspect or Elements and your ordinary browser view show the page after scripts have run. A price visible there does not establish that the server sent it as readable HTML.
:::

For step 3, replace the example URL and phrase with your own. Repeat for each phrase; `grep -c` counts matching lines, so a positive count establishes a match, not the number of occurrences.

```bash
curl -sL -A "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot" \
  https://example.com/services/ | grep -c "Prices start at"
```

For step 4, replace the URL and compare the two lines of status and downloaded bytes. Sending GPTBot's user-agent string from your own machine shows how your server and CDN treat that string; it does not reproduce OpenAI's IPs, pipeline or parser.

```bash
for ua in "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot" \
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"; do
  curl -sL -o /dev/null -w "%{http_code} %{size_download}\n" -A "$ua" https://example.com/services/
done
```

For step 5, save the following as `checkpage.py`. It needs Python 3 and nothing to install. Run it with `python3 checkpage.py https://example.com/services/ "Prices start at" "Springfield"`, replacing the URL and phrases with yours.

```python
import sys, urllib.request, urllib.error
from html.parser import HTMLParser

url, phrases = sys.argv[1], sys.argv[2:]
ua = "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot"
req = urllib.request.Request(url, headers={"User-Agent": ua})
try:
    with urllib.request.urlopen(req, timeout=20) as r:
        status, html = r.status, r.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
    status, html = e.code, e.read().decode("utf-8", "replace")

class Text(HTMLParser):
    skip, parts = 0, []
    def handle_starttag(self, tag, attrs):
        if tag in ("script", "style", "noscript", "template"): self.skip += 1
    def handle_endtag(self, tag):
        if tag in ("script", "style", "noscript", "template"): self.skip -= 1
    def handle_data(self, data):
        if not self.skip: self.parts.append(data)

p = Text(); p.feed(html)
text = " ".join(" ".join(p.parts).split())
print(f"HTTP {status}, {len(html):,} bytes of HTML, {len(text):,} characters of readable text")
for phrase in phrases:
    if phrase in text: verdict = "IN THE TEXT"
    elif phrase in html: verdict = "ONLY INSIDE A SCRIPT"
    else: verdict = "MISSING"
    print(f"{verdict:22} {phrase}")
```

View-source search can also find a phrase inside a script tag, such as a framework's JSON data blob, even when the phrase is absent from text extracted after scripts are discarded. The checker labels that result `ONLY INSIDE A SCRIPT`; its label can also catch a match elsewhere in the HTML that its text extraction excludes, so inspect the source around the match. For the broader distinction, see [what an AI fetcher reads from a page](/blog/what-ai-reads).

```text
HTTP 200, 116 bytes of HTML, 59 characters of readable text
IN THE TEXT            Prices start at $149

HTTP 200, 94 bytes of HTML, 0 characters of readable text
MISSING                Prices start at $149

HTTP 200, 175 bytes of HTML, 0 characters of readable text
ONLY INSIDE A SCRIPT   Prices start at $149
```

:::figure{src="/blog/art/en/blank-page-render-fig.97f04f9a.webp" alt="Three test verdicts distinguish readable page text, a missing phrase and a phrase sealed inside a script." width="1440" height="810"}
The three verdicts separate text on the page from a missing phrase and a phrase held inside a script. A source search can find the last one even when the readable-text check cannot.
:::

## What do the test results mean?

The results distinguish an access problem from content that depends on JavaScript. Check access first, because a crawler receiving a different response cannot test the same page as your browser.

| What you see | What it means | First fix |
|---|---|---|
| `IN THE TEXT` for every phrase, with the same status and size for both user agents | The tested facts are present in readable server HTML, and both user agents received matching responses. | Keep the result as a baseline for the next release. |
| A different status or a much smaller response for GPTBot | The server, CDN or firewall may be blocking or treating that user agent differently. | Check the access rule first; [other reasons Google visibility differs from AI visibility](/blog/google-vs-ai) can also affect discovery. |
| `MISSING` | The phrase is absent from the response; if the browser shows it, a script may load it after the page. | Find the component that supplies the phrase and render its content in HTML. |
| `ONLY INSIDE A SCRIPT`, or readable text near zero for the whole page | The data may ship without page markup; near-zero text can indicate a client-side app shell. | Render the affected component on the server, or prerender the public page. |

If the content is in the raw HTML but pages still do not show up in Google, the cause is elsewhere; check [why a website is not ranking on Google](/blog/google-rankings). A hypothetical 40-person drainage company shows “Prices start at $149” and a list of towns on its service page, both supplied by a pricing widget after load. The browser shows both phrases, but curl as GPTBot counts `0` for each and the checker prints `MISSING` for both. The developer has a specific component to investigate, not a vague instruction to make the site “AI friendly.”

## Fix the buying facts first

Our fix-first rule is to render the components that answer buying questions on the pages that carry them. Start with prices, service areas, specifications or comparison tables, FAQ answers, reviews, opening hours and contact details. Server-side rendering, static generation or prerendering can put those facts in the HTML; interactive extras such as chat widgets and calculators can remain client-side.

Google describes [dynamic rendering](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering), which serves bots a separate prerendered version, as a past workaround rather than a long-term solution. If the fix requires rebuilding page templates on a server-rendered setup, [Iron Goo's website foundation service](/services/foundation) is one option.

After the release, run the same three commands on the same pages and phrases. Expect `IN THE TEXT` for every phrase, with no unexplained difference in the user-agent responses.