THE SOCIAL ALGORITHMWhatsApp usWhatsApp
Guide — AI Search / AEO

Is Your React Site Invisible to ChatGPT? A 30-Second Test

By · 5 November 2026
The short answerIf your site builds its pages in the browser, a crawler that does not run JavaScript gets an empty page. A default Vite and React app sends the same 454-byte file for every URL, including pages that do not exist. Run the 30-second test below. If /pricing and a made-up address come back at the same size, your content is not in the HTML, and the fix is to serve real HTML for each page.
454 Braw HTML of a default React build, every URL
0words, headings or links in it
8,994characters of readable text in our own homepage HTML

Who This Affects

A single-page app, built with React, Vue or Angular, sends the browser a nearly empty HTML file and a bundle of JavaScript. The browser runs the bundle and draws the page. People see a normal site. A crawler that does not run the bundle sees the empty file.

If an agency, a freelancer or a site builder gave you a React app, find out which kind you have. It takes less than a minute.

The 30-Second Test

Three ways, easiest first.

  1. View the source. In Chrome, right-click the page and choose View Page Source (not Inspect). Search for a sentence you can see on the page. If it is not there, a crawler that does not run JavaScript cannot read it.
  2. Switch JavaScript off. Open DevTools, run the command Disable JavaScript, and reload. A blank page means the same thing.
  3. Run the script. It asks for each page the way a crawler would, without running JavaScript, and prints what came back. Save it as check.sh and run bash check.sh https://yoursite.com / /pricing.
#!/usr/bin/env bash
# What a crawler that does not run JavaScript receives.
# Usage: bash check.sh https://example.com / /pricing
base="${1%/}"; shift; paths=("${@:-/}")
printf "%-26s %-5s %-7s %-4s %-6s %s\n" PATH HTTP BYTES H1 LINKS CANONICAL
for p in "${paths[@]}" /this-page-does-not-exist; do
  out=$(curl -sL -m 20 -w $'\n%{http_code}' "$base$p"); code=${out##*$'\n'}; html=${out%$'\n'*}
  printf "%-26s %-5s %-7s %-4s %-6s %s\n" "$p" "$code" \
    "$(printf %s "$html" | wc -c | tr -d ' ')" \
    "$(printf %s "$html" | grep -oi '<h1' | wc -l | tr -d ' ')" \
    "$(printf %s "$html" | grep -oi '<a [^>]*href' | wc -l | tr -d ' ')" \
    "$(printf %s "$html" | grep -oiE '<link[^>]+rel="canonical"[^>]*>' | grep -oE 'href="[^"]*"' | head -1)"
done

How to read the result. For each real page you want HTTP 200, a size in the thousands of bytes, exactly one h1, some links, and a canonical that is that page's own address. The made-up page at the bottom should return 404. If every row has the same size, the site is serving one file for everything.

What We Measured

We built a default Vite 8.3.6 and React 19.3.1 app (the React template from npm create vite@latest, unchanged), built it for production, and served it the way single-page-app hosts do: any address that is not a file falls back to index.html with status 200. Then we ran the script.

PathHTTPBytesh1LinksCanonical
/20045400none
/pricing20045400none
/blog/hello20045400none
/this-page-does-not-exist20045400none

The file is 454 bytes. Its body is <div id="root"></div> and nothing else. The app itself is a 222 KB JavaScript bundle (69 KB compressed) that a crawler would have to run to see anything.

Then our own site, live on 5 November 2026.

PathHTTPBytesh1LinksCanonical
/20023,557115its own
/pricing20037,025123its own
/about20037,689123its own
/blog20049,252142its own
/this-page-does-not-exist4042,53614none

Our homepage is also a React app. It passes because the readable version of the page, 8,994 characters in the raw HTML, sits inside the root element and is replaced by the app when JavaScript runs. It is the same copy, and a build check fails if it drifts from the app's source. The other pages on this site are plain HTML files, and a made-up address returns a real 404.

Why It Happens

Google's documentation describes three steps for JavaScript pages: crawl, render, index. The render step happens when Google's resources allow, so a page can wait in a queue for longer than a few seconds. Google also says not every bot can run JavaScript, and still calls server-side rendering or pre-rendering a great idea (Google Search Central).

For AI crawlers the evidence is a study by Vercel and MERJ, published on 17 December 2024. From server logs, it reported that the crawlers of OpenAI, Anthropic, Meta, ByteDance and Perplexity did not render JavaScript, while Googlebot, which also feeds Gemini, and Apple's crawler did (Vercel).

Two cautions. The study is almost two years old and we have not re-measured it. It also covers named crawlers, and we do not have logs showing how every ChatGPT feature fetches a page. Check each vendor's current documentation. The test above does not depend on any of this: if the words are not in the HTML, a crawler that does not run JavaScript cannot read them.

Three Fixes, Ranked by Effort

1. Put the real content in the HTML. Write the readable version of the page inside the root element and let the app replace it when it loads. This is what our homepage does. It suits a handful of pages. The risk is drift between the copy and the app, so add a build check that compares them, and show crawlers the same content people get.

2. Prerender at build time. The build produces a complete HTML file for every page, and the browser takes over afterwards. For a Vite and React app, use a static-generation tool, or move to a framework that does it, such as Next.js, React Router in framework mode or Astro. This is the right answer for most marketing sites and blogs.

3. Render on the server. This is for pages that come from a database and change often, like a marketplace with a page per product. The server builds the HTML for each request.

One stopgap you will see recommended is dynamic rendering: serving a prerendered copy only to bots. Google describes it as a workaround, not a recommended solution, and suggests server-side rendering, static rendering or hydration instead (Google Search Central).

Three Bugs That Travel With It

Every address returns 200. On many single-page-app hosts a made-up address returns the same file with status 200, so crawlers cannot tell a missing page from a real one. Google calls this a soft 404. A made-up address should return 404.

The same canonical everywhere. If the canonical tag in the shared HTML file points to the homepage, every page tells search engines it is a copy of the homepage. Google says to set the canonical in the HTML rather than with JavaScript. Each page needs its own.

No links in the HTML. If the navigation is drawn by JavaScript, the HTML has no <a href> links, and a crawler that does not run JavaScript has nowhere to go except the sitemap. Real links let crawlers find your pages.

All three show up in the table above: the same size on every row, no canonical, a 200 on the made-up address, and zero links.

How to Check It Worked

Run the script again. Each real page should return its own size, one h1, links and its own canonical, and the made-up address should return 404. Run it again after every big change to the site, because a template update can quietly undo the fix.

What This Does Not Do

Being readable is the entry ticket, not the win. A site that every crawler can read can still go uncited. On our own site, ChatGPT sent 8 of 191 visits between 4 July and 3 November 2026, and we cannot prove which of our changes mattered. The full list is in our case study.

We do not guarantee that any site will be cited by ChatGPT, Gemini or Perplexity. We can make a site readable, consistent and measurable, and report what happens, including when it is nothing.

Sources

FAQ

Can ChatGPT read a React website?

Only what is in the HTML it fetches. If the page is built in the browser, the AI crawlers that Vercel and MERJ measured in December 2024 did not run the JavaScript, so they would receive an empty shell. Run the test in this article to see what your site sends.

Does Google see my React site?

Google can run JavaScript, but it does so in a separate step that happens when its resources allow, so it can be delayed. Google also says not every bot can run JavaScript and still recommends server-side rendering or pre-rendering.

Is an llms.txt file enough?

No. An llms.txt file points to your pages. If those pages are empty shells, it points to nothing. Make the pages readable first.

Do I have to rebuild my site in Next.js?

No. For a few pages, putting the readable content inside the root element is enough. For a marketing site or blog, prerender at build time. Server-side rendering is for pages that come from a database and change often.

Will fixing this get my site cited by ChatGPT?

It makes citation possible, not likely. Being readable is the entry ticket. We cannot promise that any site will be cited, and nobody honestly can.

Related reading

What ChatGPT sent our own agency site · AI search (AEO) services · We asked ChatGPT, Gemini and Perplexity about ourselves