How to Add Schema Markup to a React Website: A Practical Guide with JSON-LD

If you build React applications and care about SEO, you have probably hit a frustrating wall: search engines need structured data in your HTML, but React renders everything client-side by default. Adding React schema markup the right way means understanding JSON-LD, picking the correct injection method, and handling server-side rendering when it matters.

In this hands-on guide, we walk through exactly how to inject valid Schema.org JSON-LD into a React app using react-helmet, with copy-paste examples for articles, products, and breadcrumbs. We also cover the SSR pitfalls that most tutorials skip.

Why Schema Markup Matters for React Apps

Schema markup is structured data that helps Google, Bing, and other search engines understand the content on your page. When implemented correctly, it can unlock rich results: star ratings, product prices, FAQ accordions, breadcrumb trails, and more.

For React developers specifically, the challenge is twofold:

  • Client-side rendering means crawlers may not see your JSON-LD on first load
  • Dynamic content (product pages, blog articles) needs schema generated per route

JSON-LD is the format Google officially recommends. It lives inside a <script type="application/ld+json"> tag in the document head, which makes it perfect for tools like react-helmet.

react code laptop

Choosing Your Approach: react-helmet vs react-schemaorg vs Next.js

Before writing code, pick the right tool for your stack.

Approach Best For SSR Support Type Safety
react-helmet-async CRA, Vite, custom SSR Yes No
react-schemaorg Teams needing strict typing Yes (via Helmet) Yes (TypeScript)
Next.js Metadata API Next.js 13+ App Router Built-in Partial
Raw script tag in index.html Static, global schema only N/A No

For this guide, we focus on react-helmet-async because it works across most React setups and handles SSR cleanly.

Setting Up react-helmet-async

Install the package:

npm install react-helmet-async

Wrap your app with the provider in index.js or main.tsx:

import { HelmetProvider } from 'react-helmet-async';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
  <HelmetProvider>
    <App />
  </HelmetProvider>
);

Example 1: Article Schema for Blog Posts

Create a reusable component that accepts post data and injects the correct JSON-LD:

import { Helmet } from 'react-helmet-async';

function ArticleSchema({ post }) {
  const schema = {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": post.title,
    "description": post.excerpt,
    "image": post.featuredImage,
    "datePublished": post.publishedAt,
    "dateModified": post.updatedAt,
    "author": {
      "@type": "Person",
      "name": post.author.name,
      "url": post.author.profileUrl
    },
    "publisher": {
      "@type": "Organization",
      "name": "Coding4",
      "logo": {
        "@type": "ImageObject",
        "url": "https://coding4.net/logo.png"
      }
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": post.url
    }
  };

  return (
    <Helmet>
      <script type="application/ld+json">
        {JSON.stringify(schema)}
      </script>
    </Helmet>
  );
}

export default ArticleSchema;

Drop it into any blog post page: <ArticleSchema post={currentPost} />.

Validating Your Output

Always test with Google’s Rich Results Test and the Schema.org Validator. Common mistakes to watch for:

  • Dates must be in ISO 8601 format
  • Image URLs must be absolute, not relative
  • The author field requires a Person or Organization with a name
react code laptop

Example 2: Product Schema for E-commerce

Product schema unlocks price, availability, and review stars directly in search results.

function ProductSchema({ product }) {
  const schema = {
    "@context": "https://schema.org",
    "@type": "Product",
    "name": product.name,
    "image": product.images,
    "description": product.description,
    "sku": product.sku,
    "brand": {
      "@type": "Brand",
      "name": product.brand
    },
    "offers": {
      "@type": "Offer",
      "url": product.url,
      "priceCurrency": product.currency,
      "price": product.price,
      "availability": product.inStock
        ? "https://schema.org/InStock"
        : "https://schema.org/OutOfStock",
      "itemCondition": "https://schema.org/NewCondition"
    },
    "aggregateRating": product.reviewCount > 0 ? {
      "@type": "AggregateRating",
      "ratingValue": product.averageRating,
      "reviewCount": product.reviewCount
    } : undefined
  };

  return (
    <Helmet>
      <script type="application/ld+json">
        {JSON.stringify(schema)}
      </script>
    </Helmet>
  );
}

Notice the conditional aggregateRating: Google penalizes pages that declare ratings without actual reviews, so only include the field when data exists.

Example 3: BreadcrumbList Schema

Breadcrumbs are simple but high-value. They replace your URL in search results with a clean navigation path.

function BreadcrumbSchema({ items }) {
  const schema = {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": items.map((item, index) => ({
      "@type": "ListItem",
      "position": index + 1,
      "name": item.name,
      "item": item.url
    }))
  };

  return (
    <Helmet>
      <script type="application/ld+json">
        {JSON.stringify(schema)}
      </script>
    </Helmet>
  );
}

// Usage
const breadcrumbs = [
  { name: "Home", url: "https://coding4.net/" },
  { name: "Blog", url: "https://coding4.net/blog/" },
  { name: "React Schema Markup", url: "https://coding4.net/blog/react-schema-markup/" }
];

Handling Multiple Schemas on One Page

It is perfectly valid to include several JSON-LD blocks on the same page. For example, an article page can have Article, BreadcrumbList, and Organization schemas at once. You have two options:

  1. Multiple Helmet components, each with its own script tag. React-helmet-async merges them correctly.
  2. A single graph object using @graph to bundle entities together.

The graph approach looks like this:

const schema = {
  "@context": "https://schema.org",
  "@graph": [
    { "@type": "Article", ... },
    { "@type": "BreadcrumbList", ... },
    { "@type": "Organization", ... }
  ]
};
react code laptop

The SSR Problem (and How to Solve It)

Google’s crawler can now execute JavaScript, but relying on client-side rendering for structured data is risky. Indexation can be delayed by days, and other crawlers (Bing, social platforms) often skip JS entirely.

Here are your real options:

  • Next.js App Router: Use the generateMetadata function or inline a <script> tag in server components
  • Remix: Use the meta export or render directly in route components (they SSR by default)
  • Vite + react-helmet-async with SSR: Call HelmetProvider with a context object on the server and inject collected tags into your HTML template
  • Static prerendering: Tools like react-snap or vite-plugin-prerender bake schema into HTML at build time

Next.js App Router Pattern

If you are on Next.js 14 or 15, the cleanest pattern looks like this:

// app/blog/[slug]/page.tsx
export default async function Page({ params }) {
  const post = await getPost(params.slug);

  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": post.title,
    // ...
  };

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <article>{post.content}</article>
    </>
  );
}

This renders the JSON-LD server-side with zero extra dependencies.

Common Mistakes to Avoid

  • Escaping quotes manually: Use JSON.stringify instead of writing JSON inline as a string
  • Mismatched content: The data in your schema must match what users see on the page
  • Stale schema after navigation: When using SPAs, ensure your schema component re-renders on route change
  • Forgetting absolute URLs: Always use full https:// URLs for images, authors, and pages
  • Adding schema for content that does not exist: Do not declare a FAQ schema if the page has no visible FAQ

Testing Checklist

  1. Run the page through the Google Rich Results Test
  2. Validate with the Schema.org Validator for syntax errors
  3. Check the rendered HTML with view-source: to confirm SSR worked
  4. Monitor the Enhancements tab in Google Search Console after deployment
  5. Re-test after every major content or template change

FAQ

Can Google read JSON-LD added by React on the client side?

Yes, but with limits. Googlebot renders JavaScript, but indexation is delayed and not guaranteed. For mission-critical pages, server-side rendering or static prerendering is strongly recommended.

Should I use react-helmet or react-helmet-async?

Use react-helmet-async. The original react-helmet is no longer actively maintained and has known issues with concurrent React features.

Can I put multiple JSON-LD scripts on the same page?

Yes. Google explicitly supports multiple JSON-LD blocks per page. You can also combine them into a single @graph object if you prefer cleaner output.

Do I need react-schemaorg or is plain JSON-LD enough?

Plain JSON-LD is enough for most projects. The react-schemaorg library adds TypeScript types, which is useful for large teams or codebases that want strict schema validation at compile time.

How do I add FAQ schema in React?

Use the same pattern as the examples above with @type: "FAQPage". The mainEntity array should contain Question objects, each with an acceptedAnswer. Only add FAQ schema when the questions and answers are actually visible on the page.

Will schema markup directly improve my rankings?

Schema is not a direct ranking factor, but it improves click-through rates by enabling rich results. Higher CTR can indirectly boost rankings over time.

Wrapping Up

Adding structured data to a React app is no longer painful once you have the right pattern. Pick react-helmet-async for classic React or Vite projects, use server components in Next.js, and always validate your output. Start with Article, Product, and Breadcrumb schemas: they cover the majority of rich result opportunities and take less than an hour to implement.

Need help auditing your React app’s SEO or implementing schema at scale? The team at Coding4 builds production-grade React applications with SEO baked in from day one.

Leave a Comment

Your email address will not be published. Required fields are marked *