The smartest way to use structured data in React apps
A very quick introduction to JSON-LD
JSON-LD stands for JSON Linked Data, and it's one of the available formats for describing web pages using structured Data. Others are microdata and RDFa.
Structured data is widely used by Google Search to collect information while crawling web pages. Alongside semantically correct HTML structure, structured data is an easy and the most profitable thing we can do as developers to improve the SEO.
Even though it's just a JSON, It's very powerful and helps to describe lots of things from articles and their authors to online events, food recipes, and charity organizations.
One of the examples I borrowed from the Google guidelines:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Angry Birds",
"operatingSystem": "ANDROID",
"applicationCategory": "GameApplication",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6",
"ratingCount": "8864"
},
"offers": {
"@type": "Offer",
"price": "1.00",
"priceCurrency": "USD"
}
}
</script>
This piece of JSON placed within <script type="application/ld+json" />
tag, describes a software application to have
a better representation of it in the list of search results.
And this is what it looks like on a mobile device at the moment of writing:
How to use it in React apps
I hope so far everything is clear, so let's think about how to integrate this structured data into our React app properly.
It's possible to use it as it is, serializing JSON and placing scripts into <head />
section, as follows:
// ⛔️ Not type-safe
<script>
{JSON.stringify({
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
title: 'Angry Birds',
})}
</script>
But is it name
or title
? Without comparing with the https://schema.org definitions, I can't say for sure.
We can use the power of TypeScript types to protect ourselves from typos and human mistakes. We need 2 packages for this purpose:
yarn add schema-dts react-schemaorg
react-schemaorg
supports both <Helmet />
and other <head />
management
libraries, like next/head
from Next.js. For instance, I assume
we need to use it with Helmet:
import { SoftwareApplication } from 'schema-dts';
import { helmetJsonLdProp } from 'react-schemaorg';
import { Helmet } from 'react-helmet';
// ✅ Type safe
<Helmet
script={[
helmetJsonLdProp<SoftwareApplication>({
'@context': 'https://schema.org',
'@type': 'Software',
title: 'Angry Birds', // TS2345: Argument of type '{ '@context': "https://schema.org"; '@type': "SoftwareApplication"; title: string; }' is not assignable to parameter of type 'WithContext<SoftwareApplication>'.
}),
]}
/>;
Ohh, now it's clear that title
is not the right one:
<Helmet
script={[
helmetJsonLdProp<SoftwareApplication>({
'@context': 'https://schema.org',
'@type': 'Software',
name: 'Angry Birds', // 👍
}),
]}
/>
In addition, we got a handy documentation of all the properties and their types:
// node_modules/schema-dts/dist/schema.d.ts
interface SoftwareApplicationBase extends CreativeWorkBase {
/** Type of software application, e.g. 'Game, Multimedia'. */
applicationCategory?: SchemaValue<Text | URL, 'applicationCategory'>;
/** Subcategory of the application, e.g. 'Arcade Game'. */
applicationSubCategory?: SchemaValue<Text | URL, 'applicationSubCategory'>;
/** The name of the application suite to which the application belongs (e.g. Excel belongs to Office). */
applicationSuite?: SchemaValue<Text, 'applicationSuite'>;
/** Device required to run the application. Used in cases where a specific make/model is required to run the application. */
availableOnDevice?: SchemaValue<Text, 'availableOnDevice'>;
// ...
}
As a result, we have:
- ✅ Type safety
- ✅ Schema documentation
- ✅ Autocomplete in IDE
- ✅ Satisfied colleagues
It's time to deploy and test if everything works fine. ✨