SEO schemas
Omri BarmatsOmri Barmats
 | 9 months ago
In this post, I cover the structured data markups I used for Weasker.com, explaining why I chose them and how I implemented them in my Next.js code

How I add structured data markups to my Next.js 14 website

If you're here I assume you already know what structured data markups are, and their importance for SEO, but in case you don't I highly recommend you read this guide.

Relevant links:
1. Google supported structured data
2. Schema.org
3. Next.js official documentation
4. Weasker public GitHub repository
5. Weasker user page + Code
6. Weasker question page + Code
7. Google's validation tool
8. Schema.org validation tool
9. JSON-LD Official
10. Schema DTS
11. Dangerouslysetinnerhtml
12. SEO META 1 COPY Chrome Extension | Firefox Add-on

How I add SEO schemas to my Next.JS code

The code examples below come from my project Weasker.com, that you can explore in this public GitHub repository. Since Weasker.com is an active project, the code presented here may differ from the updated version in the GitHub repo.

I will show you how I added markups for two Weasker page types:

1. Profile page (example)

2. Question page (example)

Profile page markup

The structured data markup I used for the "Profile page" was pretty obvious and it's a markup called profilePage.

Here are google's guidelines for the profilePage markup:

value.alt

Once I had the markup I wanted to use I went ahead and added a JSON-LD object as so:

  const jsonLd: WithContext<ProfilePage> = {
    "@context": "https://schema.org",
    "@type": "ProfilePage",
    mainEntity: {
      "@type": "Person",
      name: user.displayName || userName,
      jobTitle: badgesSingularNames,
      image: pfp || defaultImages.defaultUserImage,
      url: `https://www.weasker.com/user/${params.user}`,
      award: badges.map((item) => {
        return `${(item.badge as Badge).singularName} Badge`;
      }),
    },
  };

If you're using typescript I recommend installing the schema-dts package. It will make your life much easier knowing which properties are available to you for each markup type.

JSON-LD is a way to add detailed information to web pages, so search engines can understand and display them better and you can read more about is here.

Let's break down the key-value pairs from the code above:

// The ProfilePage type comes from the schema-dts package
  const jsonLd: WithContext<ProfilePage> = {
    //@context - set to schema.org, this is always the same
    "@context": "https://schema.org",
    //@type - The markup you chose, in my case ProfilePage
    "@type": "ProfilePage",
    // mainEntity = object where you insert specific dynamic data for the page
    mainEntity: {
      // @type - usually a person, but could also be Avatar or a pet for example
      "@type": "Person",
      // name - of the profile holder
      name: user.displayName || userName,
      // jobTitle - in my case is the array of badges a user was awarded 
      jobTitle: badgesSingularNames,
       // image - pfp of the user 
      image: pfp || defaultImages.defaultUserImage,
      // url - Link to the user's page
      url: `https://www.weasker.com/user/${params.user}`,
      // award - in my case is the array of badges a user was awarded 
      award: badges.map((item) => {
        return `${(item.badge as Badge).singularName} Badge`;
      }),
    },
  };

Once the JSON-LD object is set up I can go ahead and insert it in my code as the first element under 'return' like such:

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

That's it! My markup is set up, and now I verify that Google can read it and it's working properly.

First let's look at the website itself.

If you go on https://www.weasker.com/user/trexrell44 for example and enter the browser's DevTools (F12)

Search the inspector for "schema.org" and you should see the JSON-LD object we created.

value.alt

To check that Google can read my markups, I use this Google tool:
https://search.google.com/test/rich-results

value.alt

After this we are good to go but if you want you can also use schema.org Schema Markup Validator to validate your structured data markup.

value.alt

Question page markup

To set up a structured data markup for wesaker.com question page (example) I used the same technique as above. Only creating the object might have been a bit more complex.

At first I wasn't sure if I should use the QAPage markup or the FAQ markup.

However, after some research, it became clear that the correct choice was the QAPage markup.

Here are google's guidelines for the QAPage markup:

value.alt

As you can see in this question page example. It's a page where users can submit answers to a single question.

Here is the JSON-LD object I created for it:

 const jsonLd: WithContext<QAPage> = {
    "@context": "https://schema.org",
    "@type": "QAPage",
    mainEntity: {
      "@type": "Question",
      name: relevantQuestion.mediumQuestion,
      text: relevantQuestion.longQuestion,
      answerCount: relevantAnswers.length,
      suggestedAnswer: relevantAnswers.map((item) => {
        return {
          "@type": "Answer",
          text: convert(item.answer.answer.textAnswer),
          url: `https://www.weasker.com/question/${params.badge}/${
            params.interview
          }/${params.question}#${(item.user as User).seo.slug}`,
        };
      }),
    },
  };

The convert function in my code is a custom utility that transforms text data into a format suitable for JSON-LD. It ensures that the text strings conform to the structured data requirements, such as replacing special characters or formatting dates correctly. This function is key to making sure that the data is both human-readable and machine-friendly.

Let's break down the jsonLd object here:

 // The QAPage type comes from the schema-dts package
  const jsonLd: WithContext<QAPage> = {
    //@context - set to schema.org, this is always the same
    "@context": "https://schema.org",
    //@type - The markup you chose, in this case QAPage
    "@type": "QAPage",
    //mainEntity = object where you insert specific dynamic data for the page
    mainEntity: {
      // @type - The single question of this page
      "@type": "Question",
      // author - Person or Organization. Information about the author of the question.
      author: {
        "@type": "Organization",
        name: "weasker",
        url: `${process.env.SITE_URL}`,
      },
      // datePublished - The date and time the question was posted in ISO 8601 format.
      datePublished: interview.createdAt,
      // name - The full text of the short form of the question.
      name: relevantQuestion.mediumQuestion,
      // text - The full text of the long form of the question. 
      text: relevantQuestion.longQuestion,
      // answerCount - The total number of answers to the question.
      answerCount: relevantAnswers.length,
      // suggestedAnswer -One possible answer, but not accepted as a top answer
      suggestedAnswer: relevantAnswers.map((item) => {
        return {
          "@type": "Answer",
          // text - The full text of the answer. 
          text: convert(item.answer.answer.textAnswer),
          //url - A URL that links directly to this answer.
          url: `https://www.weasker.com/question/${params.badge}/${
            params.interview
          }/${params.question}#${(item.user as User).seo.slug}`,
          // author - Information about the author of the answer.
          author: {
            "@type": "Person",
            name: item.user.displayName || item.user.userName,
            url: `https://www.weasker.com/user/${item.user.seo.slug}`,
          },
          //The date and time the question was answered in ISO 8601 format.
          datePublished: item.answer.answer.createdAt,
          //The date and time the answer was edited in ISO 8601 format.
          dateModified: item.answer.answer.updatedAt,
        };
      }),
    },
  };

Validation is then done similarly to the above, using the browser's DevTools (F12), Google's Rich Results Test, and the Schema Markup Validator.