Your React app looks perfect in Chrome. To ChatGPT, it's an empty page.
v1.0 · Data from 1,000,111 domain crawl · February 24, 2026
When a user visits a modern React, Angular, or Vue website, their browser downloads a JavaScript bundle, executes it, and renders the content dynamically. The user sees a beautiful, interactive page.
When an AI crawler visits the same page, it downloads the initial HTML response. If that HTML contains only a <div id="root"></div> placeholder, the crawler sees exactly that — an empty container. The content doesn't exist yet because the JavaScript hasn't executed.
We call this ghost content: content that only exists after JavaScript execution. Like a ghost, it's visible to some observers (browsers) but invisible to others (crawlers).
Google's crawler (Googlebot) has a rendering queue that eventually executes JavaScript and indexes the rendered content. But AI-specific crawlers — GPTBot (OpenAI), ClaudeBot (Anthropic), PerplexityBot, and CCBot — do not render JavaScript. They read the initial HTTP response and move on.
To illustrate the problem concretely, here is what an AI crawler receives when visiting a typical React SPA:
<!-- What GPTBot receives from a typical React SPA --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Acme Corp</title> <link rel="stylesheet" href="/static/css/main.a8b2c3.css"> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <!-- This is ALL the content GPTBot sees. --> <!-- The actual product info, pricing, docs = INVISIBLE --> <script src="/static/js/bundle.f7e8d9.js"></script> </body> </html>
The entire <body> is an empty div and a script tag. The AI has no product information, no pricing, no documentation, no structured data — nothing to extract, nothing to cite.
Contrast this with a server-side rendered (SSR) page:
<!-- What GPTBot receives from a Next.js SSR page --> <html lang="en"> <head> <title>Acme Corp — Enterprise CRM Platform</title> <script type="application/ld+json"> {"@context":"https://schema.org","@type":"SoftwareApplication", "name":"Acme CRM","applicationCategory":"BusinessApplication", "offers":{"@type":"Offer","price":"299","priceCurrency":"USD"}} </script> </head> <body> <main> <article> <h1>Enterprise CRM Platform</h1> <p>Acme CRM helps businesses manage customer relationships with AI-powered insights...</p> <h2>Pricing</h2> <p>Enterprise plan: $299/month</p> </article> </main> </body> </html>
Same product, same company — but GPTBot now receives the actual content, structured data, and semantic HTML. When a user asks ChatGPT "What does Acme CRM cost?", it can accurately answer "$299/month" instead of hallucinating or saying "I don't have that information."
We define the ghost ratio as the percentage of visible page content that is absent from the initial HTML response:
A ghost ratio of 0% means all content is present in the initial HTML (fully server-rendered). A ghost ratio of 100% means the initial HTML contains zero content tokens.
In our 1,000,111-domain crawl, we measured the ghost ratio by comparing:
| Ghost Ratio Range | % of All Domains | Mean ACRI | % in Graveyard |
|---|---|---|---|
| 0–5% (fully SSR) | 44% | 62 | 18% |
| 5–20% (mostly SSR) | 16% | 51 | 29% |
| 20–50% (hybrid) | 11% | 38 | 48% |
| 50–80% (mostly CSR) | 9% | 22 | 72% |
| 80–100% (pure CSR) | 20% | 11 | 91% |
Ghost content is a framework problem, not a developer problem. The default configuration of each framework determines whether content is server-rendered or client-rendered:
| Framework | Default Rendering | Median Ghost Ratio | Graveyard Rate | AI-Ready? |
|---|---|---|---|---|
| WordPress | SSR (PHP) | 3% | 22% | ✓ Yes |
| Hugo / Jekyll | SSG | 0% | 15% | ✓ Yes |
| Next.js (SSR) | SSR/SSG | 8% | 25% | ✓ Yes |
| Astro | SSG/Islands | 2% | 14% | ✓ Yes |
| Nuxt (SSR) | SSR | 10% | 28% | ✓ Yes |
| SvelteKit | SSR | 5% | 20% | ✓ Yes |
| Gatsby | SSG + hydration | 12% | 30% | ~ Mostly |
| Next.js (CSR) | CSR (no SSR) | 85% | 78% | ✗ No |
| Create React App | CSR only | 97% | 93% | ✗ No |
| Angular (default) | CSR only | 96% | 91% | ✗ No |
| Vue CLI (default) | CSR only | 95% | 89% | ✗ No |
WordPress powers approximately 43% of all websites, and its PHP-based architecture means all content is server-rendered by default. This is why WordPress sites dominate the AI-Trust leaderboard — not because WordPress is a "better" framework, but because its default rendering strategy accidentally makes it perfect for AI crawlers.
The lesson: AI readiness is a deployment strategy decision, not a framework quality decision.
Ghost content doesn't just affect the ghosted site — it affects the entire AI citation graph. Here's why:
When a site with meaningful content (e.g., a tech blog) links to a ghosted site, that link exists in the AI graph. But when the AI crawler follows the link, it finds an empty page. The AI-Trust calculation gives this site a low ACRI score, which means links from it also pass minimal trust to others.
This creates a ghost content cascade: one site's CSR architecture doesn't just erase itself — it weakens the trust signal for every site that links to it.
| Ghost Ratio | Mean AI-Trust Score | Median Inbound Links | % with Trust Score >0 |
|---|---|---|---|
| 0–5% | 12.4 | 3 | 68% |
| 5–20% | 8.1 | 2 | 52% |
| 20–50% | 4.3 | 1 | 31% |
| 50–80% | 1.2 | 0 | 12% |
| 80–100% | 0.3 | 0 | 4% |
Sites with ghost ratio above 80% have an average AI-Trust Score of just 0.3. Only 4% of them appear anywhere in the AI citation graph. They are, for all practical purposes, nonexistent in the AI era.
The solution is straightforward: ensure your server delivers real content in the initial HTML response. The three primary strategies are:
The server generates the full HTML for each request. Content is immediately available to all crawlers.
// pages/product.js — Before (CSR)
export default function Product() {
const [data, setData] = useState(null);
useEffect(() => { fetch('/api/product').then(r => setData(r.json())); }, []);
return data ? <ProductView data={data} /> : <Loading />;
}
// pages/product.js — After (SSR)
export async function getServerSideProps() {
const data = await fetch('https://api.example.com/product').then(r => r.json());
return { props: { data } };
}
export default function Product({ data }) {
return <ProductView data={data} />;
}
// Enable Angular Universal SSR ng add @nguniversal/express-engine // server.ts — Express server renders Angular on the server app.engine('html', ngExpressEngine({ bootstrap: AppServerModule })); app.get('*', (req, res) => { res.render('index', { req }); });
// nuxt.config.ts — SSR is default, but ensure it's not disabled export default defineNuxtConfig({ ssr: true, // This is the default — don't set it to false }) // pages/product.vue — use useAsyncData for SSR data fetching <script setup> const { data } = await useAsyncData('product', () => $fetch('/api/product') ); </script>
Pages are pre-rendered at build time. Ideal for content that doesn't change per-request (blogs, docs, marketing pages).
// Next.js — SSG with getStaticProps export async function getStaticProps() { const data = await fetchProductData(); return { props: { data }, revalidate: 3600, // ISR: re-generate every hour }; }
Combines the performance of SSG with near-real-time content. Pages are statically generated but revalidated on a schedule. Next.js, Nuxt, and SvelteKit all support this pattern.
For content-heavy sites, Astro sends zero JavaScript by default. Interactive components ("islands") are hydrated only where needed. This produces ghost ratio ~0% while maintaining full interactivity where required.
--- // src/pages/product.astro — zero JS by default import ProductHero from '../components/ProductHero.astro'; import PricingTable from '../components/PricingTable.astro'; import InteractiveDemo from '../components/Demo.jsx'; --- <main> <ProductHero /> <!-- Static: 0 JS --> <PricingTable /> <!-- Static: 0 JS --> <InteractiveDemo client:visible /> <!-- Hydrates when visible --> </main>
After implementing SSR/SSG, verify that your content is actually present in the initial HTML:
# Check if your main content appears in the raw HTML curl -s https://your-site.com | grep -c "your unique content phrase" # If the result is 0, your content is ghost content # If the result is ≥1, your content is server-rendered # Count content tokens vs total tokens curl -s https://your-site.com | wc -w # Compare with browser-rendered word count
For a comprehensive analysis, run your domain through the SEODiff scanner. It measures ghost ratio, ACRI score, token bloat, and all five visibility pillars — telling you exactly what AI crawlers see and what they miss.
| Ghost Ratio | Interpretation | Action |
|---|---|---|
| 0–5% | Fully server-rendered. AI can read everything. | No action needed. |
| 5–20% | Mostly SSR, minor JS-dependent elements. | Low priority — ensure critical content is in the SSR portion. |
| 20–50% | Hybrid rendering. Significant content hidden. | Audit which content is JS-only and prioritize SSR for key pages. |
| 50–80% | Mostly client-rendered. Most content invisible to AI. | Implement SSR for all content pages (products, docs, pricing). |
| 80–100% | Pure CSR. Your site is a ghost to AI crawlers. | Critical: migrate to SSR/SSG framework or add pre-rendering. |
Ghost content is the single largest contributor to AI invisibility after explicit bot blocking. While robots.txt blocking is a policy choice (and easily reversible in minutes), ghost content is an architectural problem that requires framework-level changes to fix.
The good news: every major JavaScript framework now supports SSR or SSG. The fix is not a paradigm shift — it is a deployment configuration change. Next.js, Nuxt, SvelteKit, and Astro make SSR the default or trivially easy to enable.
The bad news: 20% of the web still runs pure CSR applications with ghost ratios above 80%. These sites are invisible to ChatGPT, Perplexity, Claude, and Google AI Overviews. They have effectively opted out of the fastest-growing search channel in history.
curl your-site.com returns an empty <div id="root">, you are a ghost. Every AI-generated answer about your industry is being written without your content. The fix is one configuration change away.
Run a free scan to see your ghost ratio, ACRI score, and what AI crawlers actually see when they visit your site.
Scan for Ghost Content →No sign-up required · Compares raw HTML vs rendered content · Results in seconds