Structured Data for the Semantic Web with JSON-LD

Structured Data for the Semantic Web with JSON-LD

·

3 min read

How to add semantic data to your articles with JSON for Linking Data (JSON+LD) and have Google display them in their search gallery.

🔔 This article was originally posted on my site, MihaiBojin.com. 🔔

I spent today learning about JSON for Linking Data and Struc­tured Data for the Semantic Web.

I started from this good intro article and then made my way to Google’s Advanced SEO pages.

I decided to implement this functionality for my site’s articles, although there are other types I can implement later on.

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 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, specifically which @type I should use, Article, NewsArticle, and 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 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 multiple times, I opted to call &lt;ArticleSchema ... /&gt; 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.

I then realized that including a &lt;article&gt; tag in HTML is also interpreted as Semantic Markup, competing with my JSON+LD definitions, duuuh!

I promptly removed the &lt;article&gt;, &lt;header&gt;, and &lt;section&gt; 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= itemProp="articleBody" />

I now have all my posts correctly configured to show up in Google’s 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; I send one out every few weeks!

Â