Fix AI visibility / React (single-page apps)
Why AI crawlers can't read your React (single-page apps) site
React is not invisible to AI, but a client-rendered single-page app ships an empty div and builds the page in the browser, so a crawler that does not run JavaScript receives a shell with almost no text.
ChatGPT and most AI crawlers fetch your URL and read the raw HTML without running JavaScript. A Create React App or Vite single-page app returns an index.html whose body is essentially an empty div with an id of root, plus a script tag. Your headings, copy, and product details are added by JavaScript after the page loads in a real browser. The crawler does not run that JavaScript, so it sees the shell and moves on.
This is a rendering choice, not a limit of React. The same components can be rendered to HTML on the server or at build time, which is what the fixes below do. Being read is a prerequisite for being cited, and it never guarantees citation.
How React (single-page apps) renders
By default, Create React App and the standard Vite React template produce a static index.html that contains little more than a single empty div with an id of root and a script tag. In a browser, the JavaScript bundle downloads, React mounts, and the visible content appears in the DOM. A full browser render, the way a person sees the page, shows everything. A raw HTTP fetch with no JavaScript, the way GPTBot, ClaudeBot, and PerplexityBot fetch, shows the empty shell. These crawlers may download JavaScript files but do not execute them, so anything drawn client-side never reaches them. Content becomes invisible to no-JavaScript crawlers exactly when it is produced by client-side rendering: any text, headings, links, or metadata that only exist after React runs in the browser. Routes or sections loaded only after fetch, content behind client-side data loading, and meta tags set client-side (for example by react-helmet without prerendering) are all absent from the raw HTML. Nothing about React forces this. It is the consequence of rendering only in the browser, and it applies to client-only SPAs, not to React apps that are prerendered or server-rendered.
The fix
-
Confirm your content is client-rendered
Look at what a crawler actually receives. Run a raw fetch of your own page, for example curl -A LantadBot https://yoursite.com, or open view-source in the browser. If the body is essentially one empty div with an id of root and a script tag, and your headings and copy are missing, your content is client-side rendered and no-JavaScript crawlers are getting the shell. If the raw HTML already contains your text, you are prerendered or server-rendered and readable.
-
Pick a rendering strategy
There are three durable fixes. Static prerendering renders each route to real HTML at build time and fits marketing pages, docs, and content that does not change per request. Server-side rendering renders on each request and fits dynamic or personalized content. Moving to a server-rendering framework replaces the client-only setup entirely. Choose by how often the content changes and how much of the app you want to keep.
-
Add static prerendering to your Vite app
If your routes are known at build time, prerender each one to HTML so the raw fetch already contains your text. On React Router v7 framework mode, set the prerender option in react-router.config.ts and the build writes an .html file per listed route into build/client. On React Router v6 or a plain Vite SPA, vite-react-ssg renders your components to static HTML at build and is still maintained for v6 users. After this, the no-JavaScript fetch contains your headings, copy, and links instead of an empty div.
-
Or move to server rendering
For content that changes per request, render on the server. The React team deprecated Create React App on February 14, 2025, and its docs now point new projects to a framework, recommending Next.js first and React Router with Vite second. Both render your components to HTML on the server (SSR) or at build (SSG), so a no-JavaScript fetch receives full content. Moving off a client-only SPA also gives you routing, data loading, and metadata handling that produce server-rendered HTML by default.
-
Verify with a Lantad scan
Run the page through Lantad. It fetches as LantadBot with no JavaScript, diffs that against a full browser render, and scores Prose Parity, the share of rendered visible text present in the raw HTML. On a client-only SPA, Prose Parity is typically near zero. After prerendering or SSR, it should climb toward 100 and the raw HTML should contain your headings and body copy. Re-scan after each deploy to catch routes that slip back to client-only rendering.
Common questions
- Does this mean React is bad for AI visibility or SEO?
- No. React can render to HTML on the server or at build time. The empty shell comes from client-only rendering, not from React itself. Once you prerender or server-render your routes, crawlers receive full content in the raw HTML.
- Do I have to rewrite my whole app?
- Not necessarily. If your routes are known at build time, adding static prerendering to your existing Vite app can be enough, and your components stay the same. Rewriting onto a server-rendering framework is the larger option, and it makes sense when you need per-request server rendering or you are already moving off deprecated Create React App.
- ChatGPT can run JavaScript in a browser, so why does this matter?
- Most AI crawlers, including GPTBot, ClaudeBot, and PerplexityBot, fetch raw HTML and do not execute JavaScript. Independent analysis of hundreds of millions of crawler fetches found no evidence of JavaScript execution. Content injected client-side reaches these crawlers as an empty shell, so serving readable HTML is what puts your content in front of them. Being read is a prerequisite for citation, though it never guarantees it.
See what AI reads on your React (single-page apps) site
Run a free scan: paste a URL and see exactly what AI crawlers can and cannot read, with a graded report and ranked fixes.