Updated July 29, 2026
10 min read
Tactics

How to Make a React App Visible to Crawlers and AI

Prerendering, Server Rendering, and How to Check What a Crawler Actually Receives

Short answer

Serve the full page text as static HTML, because no major AI crawler executes JavaScript. Prerendering or server rendering a React app puts the body copy, the internal links and the JSON-LD into the initial response, which is the only version of the page ChatGPT, Claude and Perplexity ever see.

GB
Written byGabor BartaCo-founder, Oppira

Gabor leads product and content at Oppira. He has spent over a decade building tools and writing about competitive intelligence, social media analytics, and growth marketing for B2B SaaS companies.

Published July 29, 2026

1. Why is a client-rendered React app invisible to AI crawlers?

Because AI crawlers download HTML and stop there. Vercel and MERJ, analysing roughly 1.3 billion AI crawler requests, found that none of the major AI crawlers execute JavaScript, so text rendered in the browser never reaches them.

The measurement is specific enough to be worth quoting. Across the requests Vercel and MERJ analysed in December 2024, GPTBot accounted for 569 million and ClaudeBot for 370 million. GPTBot downloaded JavaScript files 11.5% of the time and ClaudeBot 23.8%, and neither executed any of them. Downloading a bundle is not running it.

So a single-page application that ships an empty root element and fills it after hydration presents nothing to retrieve. The heading exists, the answer exists, the internal links exist, and none of them are in the bytes the crawler received. This failure is silent, because the page looks perfect in a browser and passes every visual check a marketing team would run.

The same dataset found a second problem that compounds the first. The ChatGPT fetcher hit 404s on 34.8% of its requests and ClaudeBot on 34.2%, against 8.2% for Googlebot. AI crawlers follow stale and malformed links far more often than Googlebot does, so broken internal links waste a much larger share of their crawl.

2. What are my options for rendering a React app to HTML?

Four workable approaches: prerender at build time, server-render per request, regenerate on demand, or proxy through a rendering service. Prerendering at build time fits marketing pages, guides and glossaries best.

Prerendering
Running an application once per route at build time and writing the resulting HTML to a file, so the server can return a complete page without executing any JavaScript.
Also known as: Static site generation, SSG, Static prerendering
Rendering approaches for a React app, what each one hands a crawler, and where each one fits
ApproachWhat the crawler receivesCostBest fit
Client-side rendering onlyAn empty root element and a script tagNoneNothing that needs to be found. Invisible to every AI crawler
Prerender at build timeA complete HTML file per route, including JSON-LDA build step, and a rebuild whenever content changesMarketing pages, guides, glossaries and comparison pages
Server-side rendering per requestHTML generated fresh for every requestA running Node process and a hydration budgetPages whose content depends on the request, such as search results
On-demand or incremental regenerationCached HTML, regenerated after a set interval or on a triggerA hosting platform that supports itLarge catalogues where a full rebuild is too slow to run on every edit
Third-party prerender proxyHTML rendered by an external service and cachedA monthly fee and one more dependency in the critical pathA legacy SPA you cannot restructure yet
Dynamic rendering by user agentHTML for bots, JavaScript for humansTwo code paths that drift apart over timeNothing. Google treats it as a workaround, and divergent payloads risk being read as cloaking
Rendering approaches for a React app, what each one hands a crawler, and where each one fitsFor a site whose content changes on deploy rather than per user, prerendering at build time is both the cheapest option and the one with the fewest moving parts at request time.

The decision is per route, not per app. A marketing site with a logged-in dashboard behind it can prerender every public page and leave the application shell entirely client-rendered, because nobody needs a crawler to read a dashboard.

3. How do I prerender a React SPA, step by step?

Enumerate every public route, render each one to an HTML file at build time, write the head tags and JSON-LD into that file, then serve the file directly instead of falling back to the app shell.

Five steps. Oppira's own public pages are a prerendered React SPA built this way, and step five is the one that most often gets skipped and quietly undoes the other four.

  1. Enumerate every public route from your data, not by hand. Derive the route list from the same data files that render the pages, so a new guide or glossary term cannot be published without a prerendered file. A hand-maintained list drifts within a month and the drift is invisible.
  2. Render each route to a standalone HTML file at build time. Run the app once per route in a headless environment and write the resulting markup to disk at the path the URL implies. The output has to be a complete document, with the body copy present and no dependency on a later fetch.
  3. Write the head tags and JSON-LD into the same file. Title, meta description, canonical link and structured data all belong in the static HTML. Markup injected after hydration is invisible to every AI crawler, which makes a client-side head manager useless for retrieval.Generate the JSON-LD from the same data object that renders the visible page, so the two cannot disagree.
  4. Serve the prerendered file, not the SPA fallback. Configure hosting so a request for a known route returns that route file, and only unknown paths fall through to the app shell. A catch-all rewrite to index.html silently undoes the entire prerender.
  5. Keep the sitemap and the prerendered set in sync. Generate the sitemap from the same route list, with an accurate lastmod per URL. A sitemap listing pages that were never prerendered sends crawlers to shells, and one omitting real pages hides them.

A useful sanity check on the whole pipeline: the build should fail, not warn, when a route in the data has no prerendered output. Silent gaps are the normal failure mode here, because nothing about the site looks broken when a single page is missing its HTML.

4. What has to be in the initial HTML response?

Everything you want retrieved: the full body copy, every heading, the internal links, the head tags and the JSON-LD. Anything arriving after the first response does not exist for an AI crawler.

Six things to confirm in the raw response body, in rough order of importance:

  • The complete body text, including any content hidden behind a tab, an accordion or a "read more" toggle. Collapsed is fine, absent is not.
  • Every heading, in order, as real h1 to h3 elements rather than styled divs.
  • The answer sentence for the page and for each section, as rendered text rather than a value fetched from an API.
  • All internal links as anchor elements with href attributes. A div with a click handler is not a link and will not be followed.
  • Title, meta description and canonical link, written per route rather than inherited from the shell.
  • The JSON-LD script block, matching the visible content exactly.

Tabs and accordions deserve a specific note because they are the most common accidental hiding place. Content that is present in the HTML and hidden by CSS is retrievable. Content that mounts only when a tab is clicked is not, and the difference is invisible in a browser.

The same rule applies to anything loaded from your own API at runtime, including a list of related pages, a rating, or a table of numbers. If it matters for retrieval, it has to be in the build output rather than fetched on mount.

5. Should I serve different HTML to crawlers than to users?

No. Serve one payload to everyone. User-agent branching creates two code paths that drift apart, and a bot payload that differs materially from the human one is what Google defines as cloaking.

Google's spam policies define cloaking as presenting different content to users and to search engines with the intent to manipulate rankings. A prerendered page that is byte-identical for every requester is not cloaking, and it is also simpler. A branch that sends bots a fuller page is both a policy risk and a maintenance problem.

The practical failure is more mundane than the policy one. Once two payloads exist, one of them stops being tested. The bot path keeps rendering last quarter's template, or drops a section that was added to the human path, and nobody notices because nobody browses the site as GPTBot.

There is one adjacent trap worth naming: gating the prerendered response on a Googlebot user-agent check. That pattern was common when the only crawler that mattered was Googlebot, and it now excludes OAI-SearchBot, PerplexityBot, Claude-SearchBot and Bingbot from the only readable version of the site.

6. How do I verify that a crawler sees the content?

Request the page with a plain HTTP client and read the response body. If the answer sentence, the headings and the internal links are not in that text, no crawler has them either.

Four checks that take about twenty minutes for a whole site section:

  1. Fetch a page with curl and no JavaScript, then search the response body for a distinctive sentence from the middle of the page. Missing text is the whole diagnosis.
  2. Repeat the fetch with each AI crawler user agent, including OAI-SearchBot, PerplexityBot and Claude-SearchBot, and diff the responses against the default one. They should be identical.
  3. Extract every href from the response and request each one, checking the status codes. The ChatGPT fetcher hit 404s on 34.8% of its requests in the Vercel and MERJ data, so dead internal links cost real crawl budget.
  4. Confirm the JSON-LD block is in the same response, and that its headline, dateModified and author match the visible page.

Do this after every significant template change, not once. Rendering regressions are introduced by ordinary refactors: a component moved behind a lazy import, a section switched to fetch its own data, a route added without a prerender entry. All three look fine in a browser.

Server logs are the confirming evidence. Segment requests by user agent and watch which pages each crawler fetches and what status it gets, verifying against the published IP ranges rather than trusting the user-agent string, because spoofing is common.

7. Does prerendering help Google too, or only AI engines?

It helps Google as well, though less dramatically. Googlebot does render JavaScript, so a client-rendered page can rank, but rendering is deferred and prerendering removes the dependency entirely.

Googlebot is the exception among crawlers that matter: it executes JavaScript. That is why client-rendered marketing sites sometimes rank perfectly well and their owners conclude rendering is a solved problem. It is solved for exactly one engine.

Even for Google, prerendering removes a class of risk rather than adding a boost. Rendering happens on a separate pass, which means content can be indexed late or partially, and a script error can cost a page its body copy. Static HTML has none of those failure modes.

The stronger argument is coverage. Google states that eligibility for AI Overviews and AI Mode requires a page to be indexed and eligible to be shown with a snippet, and every other engine reads raw HTML only. One prerender pass satisfies all of them, so there is no scenario where the JavaScript-dependent version is the better choice for a public page.

Key Takeaways

No major AI crawler executes JavaScript

Vercel and MERJ analysed roughly 1.3 billion AI crawler requests and found GPTBot and ClaudeBot downloaded JavaScript but executed none of it.

Prerendering at build time fits marketing pages best

One HTML file per route, written at build time, with the head tags and JSON-LD in the same file. No request-time rendering to go wrong.

A catch-all rewrite to index.html undoes the prerender

Hosting must return the route file for known paths and fall back to the app shell only for unknown ones. This is the most common silent failure.

Serve one payload to everyone

User-agent branching drifts out of sync and a materially different bot payload is what Google defines as cloaking. Gating on Googlebot excludes every AI crawler.

Broken internal links cost more here than in search

The ChatGPT fetcher hit 404s on 34.8% of requests and ClaudeBot on 34.2%, against 8.2% for Googlebot, in the Vercel and MERJ data.

Verify by fetching, not by looking

Request the raw HTML and search it for a sentence from the middle of the page. A browser check cannot distinguish prerendered from hydrated content.

Frequently Asked Questions

Sources

  1. The rise of the AI crawler Vercel with MERJ, December 17, 2024.Roughly 1.3 billion AI crawler requests. Source of the no-JavaScript-execution and 404-rate findings. Dated December 2024, so re-verify with your own logs.
  2. AI features and your website Google, July 2026.Primary. States that eligibility for AI Overviews and AI Mode requires a page to be indexed and snippet-eligible.
  3. Spam policies for Google web search Google, July 2026.Primary. The definition of cloaking, which is what a materially different bot payload risks being classified as.
  4. Overview of OpenAI crawlers OpenAI, July 2026.Primary. Documents OAI-SearchBot, GPTBot, ChatGPT-User and OAI-AdsBot, and publishes the IP range files for verifying each one.
  5. Does Anthropic crawl data from the web? Anthropic, July 2026.Primary. Documents ClaudeBot, Claude-User and Claude-SearchBot and their separate purposes.
  6. Perplexity Crawlers Perplexity, July 2026.Primary. Documents PerplexityBot and Perplexity-User, and publishes IP range files for verification.
  7. Optimizing your website for generative AI features on Google Search Google, July 10, 2026.Primary. Recommends semantic HTML and clear heading structure, and rejects AI-specific files and markup.
Oppira

Turn reading into a reaction

Oppira watches your competitors, keeps your playbook current, and drafts the response. Start free and see your market clearly by tomorrow.