Fix: Missing structured data (JSON-LD)
Missing structured data means search engines read your page as a flat text document instead of a structured database entry. Without JSON-LD, you forfeit rich results—FAQ dropdowns, recipe carousels, breadcrumbs, and review stars. These UI enhancements in the SERP are not cosmetic. An FAQ snippet routinely doubles the vertical pixel height of your search result, pushing competitors below the fold.
Indxel flags pages missing this machine-readable context with the structured-data-present rule. Google parses application/ld+json scripts to map your content to its Knowledge Graph. If you ship a blog post without Article schema, or a landing page without SoftwareApplication schema, you force Google's parser to guess your content's primary entity. Guessing leads to standard blue links instead of rich snippets.
The structured-data-present rule carries a severity weight of 4/100. It is classified as a warning because a page can still rank without structured data, but it will systematically underperform in click-through rate (CTR) compared to a page displaying rich results.
How do you detect missing structured data?
Run the Indxel CLI against your local build or production URL. The scanner evaluates the rendered DOM specifically for <script type="application/ld+json"> tags containing valid JSON objects with an @context of https://schema.org.
npx indxel check http://localhost:3000
# Output
Running Indxel SEO audit...
/blog/how-to-configure-redis
⚠ structured-data-present No JSON-LD structured data found on page. (Weight: 4)
/pricing
⚠ structured-data-present No JSON-LD structured data found on page. (Weight: 4)
✖ 2 warnings found. Score: 96/100.The structured-data-present rule only verifies the existence of a valid JSON-LD script tag. It does not validate the specific schema requirements (e.g., missing a required author field in an Article). Schema-specific validation is handled by the invalid-structured-data rule.
How to fix missing structured data in Next.js App Router
The Next.js App Router handles metadata natively, but it does not auto-generate JSON-LD. You must inject it manually into your page components. Because JSON-LD is just a script tag containing a JSON string, you inject it using React's dangerouslySetInnerHTML.
The optimal pattern is to create a dedicated TypeScript component for your schema to ensure type safety before stringification.
Bad: Standard page without schema
This page renders standard HTML. Search engines extract the h1 and p tags, but they do not definitively know this is a tutorial with discrete steps.
// app/docs/setup/page.tsx
export default function SetupGuide() {
return (
<article>
<h1>How to set up the Indxel CLI</h1>
<p>Follow these steps to configure your environment.</p>
<h2>Step 1: Install</h2>
<p>Run npm i -g indxel</p>
</article>
);
}Good: Page with injected HowTo JSON-LD
Inject the <script> tag directly into the JSX. Since React escapes string content by default, you must pass the stringified JSON object to dangerouslySetInnerHTML.
// app/docs/setup/page.tsx
import { WithContext, HowTo } from 'schema-dts';
export default function SetupGuide() {
const jsonLd: WithContext<HowTo> = {
'@context': 'https://schema.org',
'@type': 'HowTo',
name: 'How to set up the Indxel CLI',
description: 'Follow these steps to configure your environment.',
step: [
{
'@type': 'HowToStep',
name: 'Install',
text: 'Run npm i -g indxel',
}
]
};
return (
<article>
{/* Inject JSON-LD */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<h1>How to set up the Indxel CLI</h1>
<p>Follow these steps to configure your environment.</p>
<h2>Step 1: Install</h2>
<p>Run npm i -g indxel</p>
</article>
);
}Use the schema-dts npm package. It provides complete TypeScript definitions for the entire Schema.org vocabulary, preventing typos in property names like mainEntityOfPage or datePublished.
How to fix missing structured data in Next.js Pages Router
If you are maintaining a Next.js Pages Router application, you cannot inject the script tag directly into the standard component tree without risking hydration mismatches or placement issues. You must wrap the script tag in next/head.
// pages/faq.tsx
import Head from 'next/head';
export default function FAQPage() {
const faqSchema = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Which structured data types does Indxel validate?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Indxel validates required fields for Article, Product, FAQ, Organization, WebSite, BreadcrumbList, HowTo, and SoftwareApplication."
}
}
]
};
return (
<>
<Head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }}
/>
</Head>
<main>
<h1>Frequently Asked Questions</h1>
{/* Page content */}
</main>
</>
);
}How to fix missing structured data using the Indxel SDK
Writing dangerouslySetInnerHTML repeatedly introduces boilerplate and formatting risks. The Indxel SDK provides a StructuredData component that handles stringification, escaping, and type validation automatically.
// app/blog/[slug]/page.tsx
import { StructuredData } from '@indxel/sdk/react';
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug);
return (
<>
<StructuredData
type="Article"
data={{
headline: post.title,
image: [post.ogImage],
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: [{
"@type": "Person",
name: post.authorName,
}]
}}
/>
<article>
<h1>{post.title}</h1>
{/* Content */}
</article>
</>
);
}The StructuredData component maps the type prop to the correct Schema.org context and strictly types the data object based on Google's required and recommended fields for rich results.
How to prevent missing structured data in CI?
Relying on developers to remember JSON-LD for every new route fails at scale. You must enforce structured data presence at the CI level before code merges to the main branch.
Add npx indxel check --ci to your GitHub Actions pipeline. The --ci flag configures the CLI to exit with a non-zero status code if any rule violations are found, failing the build.
# .github/workflows/seo-check.yml
name: SEO Infrastructure Guard
on:
pull_request:
branches: [main]
jobs:
validate-seo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Run Indxel SEO Audit
run: npx indxel check --ci --diffThe --diff flag ensures the CLI only evaluates pages affected by the current pull request. If a developer creates a new /pricing page without structured data, the CI job fails and blocks the merge. Existing legacy pages missing structured data will not block the PR, preventing CI gridlock.
What are the edge cases for structured data implementation?
Injecting JSON-LD seems straightforward, but dynamic content and client-side rendering introduce specific failure states that search engines penalize.
Unescaped characters breaking JSON
When you populate JSON-LD fields with data from a CMS, you often include user-generated text containing quotes, backslashes, or HTML entities. If you construct the JSON string manually instead of using JSON.stringify(), you will generate invalid JSON.
Google's parser will fail silently on invalid JSON, stripping your page of rich results without notifying Search Console immediately.
Bad:
// This breaks if post.title contains a double quote, e.g., The "Best" Tool
<script type="application/ld+json">
{`
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "${post.title}"
}
`}
</script>Good:
Always pass the raw object to JSON.stringify(). It handles escaping quotes, line breaks, and control characters natively.
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaObject) }}
/>Empty arrays in dynamic schema
If you build an FAQPage schema dynamically from an array of questions, but the array is occasionally empty, you will output an invalid schema. Google requires the mainEntity array in FAQPage schema to contain at least one valid Question object.
// Fails validation if faqs.length === 0
const schema = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": faqs.map(faq => ({
"@type": "Question",
"name": faq.q,
"acceptedAnswer": { "@type": "Answer", "text": faq.a }
}))
};You must conditionally render the entire <script> tag based on the length of the source data.
{faqs.length > 0 && (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
)}Client-side injection delays
If you inject JSON-LD via a useEffect hook in a React client component ("use client"), the structured data is not present in the initial HTML payload. While Googlebot executes JavaScript and will eventually see the schema during the second wave of indexing, this delays rich result eligibility. Bingbot and other crawlers often fail to parse client-side JSON-LD entirely.
Always inject JSON-LD via Server Components or static HTML generation to ensure it is present in the raw source code.
Related rules
invalid-structured-data: Checks if the injected JSON-LD contains all required fields for its declared@type.duplicate-structured-data: Flags pages containing multiple conflicting schemas of the same type, which confuses crawlers.
FAQ
Which structured data types does Indxel validate?
Indxel validates required fields for Article, Product, FAQ, Organization, WebSite, BreadcrumbList, HowTo, and SoftwareApplication schemas. It checks for the presence of @context, @type, and type-specific required fields like review or aggregateRating.
Is structured data a direct ranking factor?
Structured data itself is not a direct ranking factor, but it enables rich results that significantly increase click-through rates (CTR). A higher CTR signals relevance to search engines, which indirectly improves your position in the SERP.
Can I use Microdata instead of JSON-LD?
Google explicitly prefers JSON-LD and recommends it over Microdata or RDFa. JSON-LD decouples your metadata from your HTML structure, making it less prone to breaking when UI components are refactored. Indxel only validates JSON-LD.
Does JSON-LD need to be in the <head> of the document?
No, JSON-LD <script> tags can be placed anywhere in the <head> or <body> of the HTML document. Googlebot parses the entire DOM for application/ld+json tags regardless of their position. Placing it in the body is standard practice in Next.js App Router.
Frequently asked questions
Which structured data types does Indxel validate?
Indxel validates required fields for Article, Product, FAQ, Organization, WebSite, BreadcrumbList, HowTo, and SoftwareApplication. It checks for @context, @type, and type-specific required fields.
Is structured data a ranking factor?
Structured data itself isn't a direct ranking factor, but it enables rich results that significantly increase click-through rates. Higher CTR can indirectly improve rankings.