Your content only exists after JavaScript runs
Googlebot renders JavaScript; the fetchers behind ChatGPT, Claude and Perplexity mostly do not. How to check what they see, and how to server-render or pre-render on each framework.
What is actually happening
A single-page application sends the browser a tiny HTML file — a <div id="root"> and a script tag — and the script then builds the page. A person with a browser never notices. A crawler that does not execute the script gets the tiny file and stops: as far as it can tell, the page has a title and nothing else. The AI visibility check reports this as “the page is empty until JavaScript runs” and counts the words it found — if that number is under about forty, this is your problem.
Step 1 — confirm it from the source, not the inspector
# What a non-rendering crawler receives:
curl -s -A "Mozilla/5.0 (compatible; ClaudeBot/1.0)" https://example.com/ \
| sed 's/<[^>]*>/ /g' | tr -s ' \n' | head -c 600If that prints your headline and first paragraph, you are fine and something else is going on. If it prints menu labels and “You need to enable JavaScript to run this app”, read on.
Step 2 — pick the fix that matches how the site is built
Already on a framework that can server-render
Next.js, Nuxt, SvelteKit, Remix and Astro all render on the server by default. Sites built on them that still ship empty HTML have usually opted out per page — a component marked client-only that wraps the whole page, data fetched in a useEffect instead of on the server, or a build configured for a static export with the content loaded afterwards. Find the page component and move the data fetch to the server side (Next.js: a Server Component or getServerSideProps; Nuxt: useAsyncData; SvelteKit: +page.server.ts). The markup then arrives with the content already in it.
A bare React / Vue / Angular app
Two honest options. The thorough one is migrating the app to a server-rendering framework — Next.js for React, Nuxt for Vue, Angular Universal for Angular. It is real work, and it is the fix that also improves speed and Google indexing. The quick one is pre-rendering: at build time, run the app in a headless browser for every route and save the resulting HTML as static files. Vite’s vite-plugin-ssr/vike, react-snap and Angular’s prerender builder do this. It works for sites whose content is the same for every visitor — marketing pages, docs, blogs.
Content that changes per request, on an app you cannot rebuild
A dynamic rendering service sits in front of your site, detects crawler user-agents, renders the page in a headless browser and serves them the finished HTML while humans get the app as before. Prerender.io is the established one; Cloudflare Workers can do it yourself with Browser Rendering. It is a workaround rather than a fix — you are now maintaining two versions of every page — but it can be turned on in an afternoon.
Step 3 — do not lose the labels in the process
Whichever route you take, make sure the server-rendered HTML also includes the <title>, meta description, canonical and any JSON-LD. Client-side “helmet” libraries that set the title after load have the same problem as the content did. The structured data guide covers what to put there.
Check it worked
Re-run the check: “Content without JavaScript” should read Present with a word count that matches what is on screen, and the headline should move on to whatever is next — or to all clear.
Common questions
- Google indexes my site fine. Why would AI crawlers be different?
- Googlebot runs a full headless Chrome and executes your JavaScript, at some delay. The fetchers behind ChatGPT, Claude and Perplexity are much simpler: they request the URL and read whatever HTML comes back. OpenAI has said GPTBot does not execute JavaScript; Anthropic's and Perplexity's crawlers behave the same way in practice. If your content is not in that first response, they see a header, a footer and an empty div.
- How do I see what a non-rendering crawler sees?
- View the page source (Ctrl+U), not the DevTools inspector — the inspector shows the page after JavaScript ran. Search the source for a sentence from your main content. If it is not there, it is not there for AI crawlers either. The AI visibility check does this automatically and counts the visible words.
- Is this the same problem as 'unused JavaScript'?
- No. Unused JavaScript is about speed — shipping code that never runs. This is about where the content lives. A page can be fast and still empty without JavaScript, and slow while fully server-rendered.
- My site is Shopify / Wix / Squarespace / WordPress. Do I have this problem?
- Almost certainly not. Those platforms render pages on the server; the HTML they send already contains the content. The problem is specific to sites built as a JavaScript application — React, Vue or Angular with a bare index.html — and deployed without server-side rendering or pre-rendering.
- What about a headless CMS with a React front end?
- That is the most common way to end up here. The CMS is fine; the front end is what decides. Move it to a framework that server-renders (Next.js, Nuxt, SvelteKit, Astro) or put a pre-rendering layer in front of it. The content does not have to move.
Check a page against this
Enter any public URL. You’ll get the one thing stopping AI crawlers from reading the page (if anything is), then a crawler-by-crawler table and what the HTML actually tells a machine.
Other guides
- How to fix a slow LCP — Your main image or headline takes too long to appear.
- How to fix layout shift — Content jumps around while the page loads.
- How to fix slow INP — Taps and clicks take a moment to do anything.
- How to fix a slow server response — Your server is slow to start replying at all.
- How to eliminate render-blocking CSS and JS — CSS and JS files stop anything appearing on screen.
- Why you're seeing "no field data" — Google has no real-visitor data for your site yet.
- How to fix oversized and outdated images — Your images weigh far more than they need to.
- How to cut unused JavaScript — You ship JavaScript this page never runs.
- How to fix your cache headers — Returning visitors re-download files they already have.
- Why your score is different on every run — The score is different every time you test.
- Fast on desktop, slow on mobile — Desktop looks fine; the phone test says otherwise.
- Why Search Console still shows the old numbers — You fixed it weeks ago and Search Console hasn't noticed.
- How to stop plugins loading on every page — A form plugin's CSS on pages with no form — times every plugin.
- Which AI crawlers to allow in robots.txt — You block training bots and accidentally block the ones that cite you.
- Your firewall is blocking AI crawlers — robots.txt allows everyone, yet AI crawlers get a 403 before they read a byte.
- Structured data that AI crawlers actually use — The page is readable but nothing tells a machine what it is.
- llms.txt — what it is, what it isn't, and a template — A cheap bet, not a fix — here's the honest evidence and a template.