You're right to question that - "Google says they can handle it" doesn't mean they actually render JavaScript the same way a browser does. I always run URL Inspection in Search Console and compare the "View crawled page" with the "View indexed page". Nine times out of ten with heavy React SPAs, the indexed version is missing key content because Googlebot gives up after the initial HTML shell.
The LLM angle is even more critical now. Most large language model crawlers (OpenAI's GPTBot, Anthropic's Claude-Web, etc.) don't execute JavaScript at all. they parse raw HTML responses. If your React site relies on client-side rendering, those crawlers see an empty <div id="root"> and nothing else. That means zero visibility in AI-generated answers, which is becoming a real discoverability channel.
A solid pattern I've used is a caching layer with pre-rendering. Tools like Prerender.io or a simple headless browser renderer (Puppeteer on a Lambda, for example) generate static HTML snapshots. You then serve those to known crawler user agents via middleware or a reverse proxy, while real users still get the dynamic SPA. Something like this in an Express middleware:
const prerender = require('prerender-node'),
app.use(prerender.set('prerenderToken', 'YOUR_TOKEN')),
That feeds both Googlebot and LLM crawlers a fully rendered DOM they can parse. You'll also want to verify each snapshot via URL Inspection - Google still might re-execute JS if the page has dynamic elements, but providing a clean static version eliminates the risk of invisible content