Structured Data for the Semantic Web with JSON-LD
Tags: jsonld, semantic, web, gatsby
Published: 2021-06-14 12:15:51

---
title: Structured Data for the Semantic Web with JSON-LD
published: true
date: 2021-06-13 00:00:00 UTC
tags: JSONLD,semantic,web,gatsbyjs
series: Building a developer blog with GatsbyJS
canonical_url: https://MihaiBojin.com/personal-site/structured-semantic-data-with-json-ld
---

> 🔔 This article was originally posted on my site, [MihaiBojin.com](https://MihaiBojin.com/personal-site/structured-semantic-data-with-json-ld?utm_source=DevTo&utm_medium=organic&utm_campaign=rss). 🔔

---

I spent today learning about [JSON for Linking Data](https://json-ld.org/) and Struc­tured Data for the Semantic Web.

I started [from this good intro article](https://nystudio107.com/blog/json-ld-structured-data-and-erotica) and then made my way to [Google's Advanced SEO pages](https://developers.google.com/search/docs/advanced/guidelines/get-started).

I decided to implement this functionality for my site's articles, although there are [other types I can implement later on](https://developers.google.com/search/docs/guides/search-gallery).

Creating JSON+LD schema for my site was not as easy as I expected. I couldn't find any GatsbyJS or React plugins that did what I wanted out of the box. The closest one was [react-schemaorg](https://github.com/google/react-schemaorg) which seems to wrap the `<script>` tag generation - not something I'd use a plugin for.

In the end, I wrote code to generate the JSON+LD schema, based on the necessary props for this type.

As I was writing it, I was confused by the examples [provided by Google](https://developers.google.com/search/docs/data-types/article), specifically which `@type` I should use between [Article](https://schema.org/Article), [NewsArticle](https://schema.org/NewsArticle), and [BlogPosting](https://schema.org/BlogPosting).

As far as I can tell, there aren't any major differences from a SEO standpoint; I decided to go with the generic [Article](https://schema.org/Article) type.

I ended up with the following helper code (`src/components/article-schema.js`):

```
function ArticleSchema({
  title,
  description,
  date,
  lastUpdated,
  tags,
  image,
  canonicalURL,
}) {
  // load metadata defined in gatsby-config.js
  const { siteMetadata } = useSiteMetadata();
  // load the site's logo as a file/childImageSharp by its relative path
  const { siteLogo } = useSiteLogo();

  const authorProfiles = [
    SITE_URL,
    `https://twitter.com/${siteMetadata.social.twitter}/`,
    `https://linkedin.com/in/${siteMetadata.social.linkedin}/`
    `https://github.com/${siteMetadata.social.github}`
  ];

  const img = image ? getImage(image.image) : null;
  const imgSrc = image ? getSrc(image.image) : null;

  const jsonData = {
    '@context': `https://schema.org/`,
    '@type': `Article`,
    
    // helper that generates `'@type': 'Person'` schema
    author: AuthorModel({
      name: siteMetadata.author.name,
      sameAs: authorProfiles,
    }),
    url: canonicalURL,
    headline: title,
    description: description,
    keywords: tags.join(','),
    datePublished: date,
    dateModified: lastUpdated || date,
    
    // helper that generates `'@type': 'ImageObject'` schema
    image: ImageModel({
      url: siteMetadata.siteUrl + imgSrc,
      width: img?.width,
      height: img?.height,
      description: image.imageAlt,
    }),

    // helper that generates `'@type': 'Organization'` schema
    publisher: PublisherModel({
      name: siteMetadata.title,

      // helper that generates `'@type': 'ImageObject'` schema
      logo: ImageModel({
        url: siteLogo.src,
        width: siteLogo.image.width,
        height: siteLogo.image.height,
      }),
    }),
    mainEntityOfPage: {
      '@type': `WebPage`,
      '@id': siteMetadata.siteUrl,
    },
  };

  return (
    <Helmet>
      <script type="application/ld+json">
        {JSON.stringify(jsonData, undefined, 4)}
      </script>
    </Helmet>
  );
}
ArticleSchema.defaultProps = {
  tags: [],
};

ArticleSchema.propTypes = {
  title: PropTypes.string.isRequired,
  description: PropTypes.string.isRequired,
  date: PropTypes.string,
  lastUpdated: PropTypes.string,
  tags: PropTypes.arrayOf(PropTypes.string),
  image: PropTypes.object,
  canonicalURL: PropTypes.string,
};

export default ArticleSchema;
```

Since, you can call [react-helmet](https://www.npmjs.com/package/react-helmet) multiple times, I opted to call `<ArticleSchema ... />` directly from `blog-post.js`, the component that renders my blog posts and articles.

Once everything was set-up, I tested the results in Google's [rich results tester](https://search.google.com/test/rich-results).

Here's the end result:

[![Rich Results Tester](https://MihaiBojin.com/static/667a63128762648dc401249f09607852/e9beb/rich-results-test-json-ld.png "Rich results for the JSON+LD article")](/static/667a63128762648dc401249f09607852/a13c9/rich-results-test-json-ld.png)

I then realized that including a `<article>` tag in HTML is also interpreted as Semantic Markup, competing with my JSON+LD definitions, duuuh!

I promptly removed the `<article>`, `<header>`, and `<section>` tags.

Since Google does not define `itemProp` in its schema, specifying it is superfluous, but for now I annotated the article's body as:

```
<div dangerouslySetInnerHTML={{__html: post.html}} itemProp="articleBody" />
```

I now have all my posts correctly configured to show up in Google's [search gallery](https://developers.google.com/search/docs/guides/search-gallery), which in time will hopefully result in more organic traffic to my articles!

---

If you liked this article and want to read more like it, [please subscribe to my newsletter](https://motivated-founder-807.ck.page/db1cf284bf); I send one out every few weeks!