Your structured data exists. Crawlers can't see it.
You added JSON-LD to your React app. You validated it with Google's Rich Results Test. You even see rich snippets showing up for your pages in Google.
So why doesn't Perplexity cite your product schema? Why doesn't ChatGPT know your FAQ answers? Why don't social bots pick up your Organization data?
Because your JSON-LD is injected by JavaScript — and most crawlers don't execute JavaScript.
How structured data works in SPAs
In a traditional server-rendered site, JSON-LD lives directly in the HTML:
<head>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Your Company",
"url": "https://your-site.com"
}
</script>
</head>
When any crawler fetches the page, it sees this immediately in the raw HTML. No JavaScript needed.
In a React SPA, you typically inject JSON-LD through a component:
function OrganizationSchema() {
const schema = {
"@context": "https://schema.org",
"@type": "Organization",
name: "Your Company",
url: "https://your-site.com",
}
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
)
}
This works perfectly in the browser. React renders the component, the <script> tag gets inserted into the DOM, and any tool that inspects the rendered page sees valid JSON-LD.
But the raw HTML that the server sends contains none of this. It's just <div id="root"></div>. The JSON-LD only exists after JavaScript executes — which most crawlers can't do.
Which crawlers can see your JavaScript-injected JSON-LD?
| Crawler | Sees JS-injected JSON-LD | Notes |
|---|---|---|
| Googlebot | Yes (with delays) | Renders JS through a secondary queue |
| Google Rich Results Test | Yes | Executes JavaScript for testing |
| GPTBot | No | Raw HTML only |
| ClaudeBot | No | Raw HTML only |
| PerplexityBot | No | Raw HTML only |
| Bingbot | Sometimes | Inconsistent JS rendering |
| Social bots | No | Raw HTML only |
| Schema.org validators | Varies | Some execute JS, some don't |
The irony: Google's testing tools show everything as valid, so you think it's working. But for every other crawler, your structured data doesn't exist.
The schema types that matter most
Not all structured data is equally important. Here are the types with the highest impact for SaaS and product sites:
Organization (essential)
Tells search engines and AI who you are:
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "CrawlReady",
"url": "https://crawlready.com",
"description": "AI and crawler visibility middleware for single-page applications.",
"logo": "https://crawlready.com/logo.png",
"sameAs": [
"https://twitter.com/crawlready",
"https://github.com/crawlready"
]
}
Product (high value for SaaS)
Describes what you sell:
{
"@context": "https://schema.org",
"@type": "Product",
"name": "CrawlReady",
"description": "Pre-rendering middleware that makes JavaScript SPAs visible to search engines and AI crawlers.",
"brand": {
"@type": "Brand",
"name": "CrawlReady"
},
"offers": {
"@type": "Offer",
"price": "9.00",
"priceCurrency": "USD",
"priceValidUntil": "2027-12-31",
"availability": "https://schema.org/InStock"
}
}
FAQ (great for AI citations)
FAQ schema is particularly valuable for AEO because AI systems extract Q&A pairs:
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is CrawlReady?",
"acceptedAnswer": {
"@type": "Answer",
"text": "CrawlReady is AI and crawler visibility middleware for single-page applications. It pre-renders your SPA and serves complete HTML to search engines and AI crawlers."
}
},
{
"@type": "Question",
"name": "Does CrawlReady require code changes?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. CrawlReady deploys as a Cloudflare Worker and requires zero changes to your application code."
}
}
]
}
Article (for blog content)
Marks up blog posts and guides:
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Your Article Title",
"description": "Article description",
"datePublished": "2026-03-25",
"author": {
"@type": "Person",
"name": "Eric Neff"
},
"publisher": {
"@type": "Organization",
"name": "CrawlReady"
}
}
BreadcrumbList (for navigation)
Helps search engines understand your site structure:
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://your-site.com"
},
{
"@type": "ListItem",
"position": 2,
"name": "Blog",
"item": "https://your-site.com/blog"
}
]
}
How to make your SPA's structured data visible
Option 1: Pre-rendering (recommended for existing SPAs)
Pre-rendering middleware like CrawlReady renders your page — including all JavaScript-injected JSON-LD — and serves the complete HTML to crawlers. Your structured data becomes visible to every bot without any code changes.
This is the fastest path if you already have JSON-LD implemented in your React/Vue/Angular components.
Option 2: Inline in index.html (limited)
For Organization schema and other site-wide structured data, you can add it directly to your index.html:
<head>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Your Company",
"url": "https://your-site.com"
}
</script>
</head>
This works for site-wide data but doesn't help with page-specific schema (Product, Article, FAQ) that varies by route.
Option 3: Server-side rendering
Migrate to Next.js, Nuxt, or another SSR framework. The server renders your components — including JSON-LD — and sends complete HTML.
Cost: $30K–$80K+ and 2–4 months for an existing SPA.
Structured data best practices for SPAs
Use JSON-LD, not Microdata or RDFa
Google recommends JSON-LD as the preferred format for structured data. It's easier to implement in React (no need to modify HTML attributes), and it's easier for both humans and machines to read.
One JSON-LD block per entity
Don't stuff all your structured data into a single block. Use separate <script type="application/ld+json"> blocks for different entities:
<OrganizationSchema />
<ProductSchema />
<BreadcrumbSchema />
Use absolute URLs
All URLs in your JSON-LD should be absolute, not relative:
// Good
"url": "https://your-site.com/product"
// Bad
"url": "/product"
Keep it accurate
Google penalizes structured data that doesn't match the visible page content. If your Product schema says the price is $9/mo, that price should be visible on the page.
Validate regularly
Use Google's Rich Results Test to validate your markup. But remember — this tool executes JavaScript, so it will show your JS-injected schema as valid even though non-JS crawlers can't see it.
For a true crawler-perspective validation, run curl -s https://your-site.com | grep "application/ld+json". If nothing comes back, non-JS crawlers see no structured data.
The AI dimension
Structured data isn't just for Google rich snippets. AI systems use it to build knowledge graphs:
- Entity relationships: Schema.org types help AI understand that your Organization makes a Product in a specific Category
- FAQ answers: AI systems often extract FAQ schema as direct answers to user questions
- Authoritative signals: Well-structured data signals a trustworthy, well-maintained source
- Content classification: Article schema helps AI systems categorize your blog content
As AI search grows, structured data becomes more important — not less. The sites with clean, comprehensive JSON-LD will be better represented in AI-generated answers.
Next steps
- Run a CrawlReady audit to see if your structured data is visible to non-JS crawlers
- Implement the schema types above starting with Organization and FAQ
- Read our React SPA SEO guide for the full optimization checklist
- Test with
curlto verify what crawlers actually see
Schema.org types and Google's structured data requirements evolve over time. Validate your markup regularly and check Google's structured data documentation for current guidelines.
Is your site invisible to AI search?
Run a free audit and see exactly what Google, ChatGPT, Perplexity, and 20+ crawlers see on your site. Results in 15 seconds.
Run Free Audit