Partner with CodeWalnut to drive your digital growth!
Tell us about yourself and we will show you how technology can help drive business growth.
Thank you for your interest in CodeWalnut.Our digital expert will reach you within 24-48 hours.
Oops! Something went wrong while submitting the form.
Headless CMS

SEO compliance with Contentful and Next JS

April 12, 2024
5 min
CMS Automation
Linkedin iconX logoFacebook icon

Recently, one of our clients wanted to add FAQ Schema to 180+ pages on a website powered by the Contentful headless CMS (one of the best headless CMSs to manage your website’s content). But we were on a very tight schedule.

Like any other developer, I started searching for a solution and found a few posts suggesting to create a new field in Contentful where the editor can pass the necessary information and the front-end converts it into a JSON-LD format as shown below.

Creating a new field in Contentful

CMS Elements

Reading it from the code.

 Javascript

import { SITE_OWNER_GITHUB, SITE_OWNER_LINKEDIN, SITE_OWNER_TWITTER, SITE_OWNER, SITE_URL } from '../../lib/constants'

export default function PostSchema({ post }) {

    const url = post.canonicalUrl !== undefined && post.canonicalUrl !== null ? post.canonicalUrl : `${SITE_URL}/blog/${post.slug}`;

    return (
      <script
                defer
                type="application/ld+json"
                className="yoast-schema-graph yoast-schema-graph--main"
                dangerouslySetInnerHTML={{__html: `
                {
                  "@context": "https://schema.org",
                  "@type": "BlogPosting",
                  "mainEntityOfPage": {
                    "@type": "WebPage",
                    "@id": "${url}"
                  },
                  "headline": "${post.title}",
                  "image": [
                    "${post.metaImage != null ? post.metaImage.url : null}"
                  ],
                  "datePublished": "${post.publishDate}",
                  "dateModified": "${post.updatedAt}",
                  "author": {
                    "@type": "Person",
                    "name": "${post.author.name}",
                    "url": "${SITE_URL}/author/${post.author.slug}",
                    @id":"${SITE_URL}/author/${post.author.slug}#person",
                    "sameAs":
                    [
                      "${post.author.twitter}",
                      "${post.author.github}",
                      "${post.author.linkedin}"
                    ]
                  },
                  "publisher": {
                    "@type": "Person",
                    "name": "${SITE_OWNER}",
                    "sameAs":
                    [
                      "https://twitter.com/${SITE_OWNER_TWITTER}",
                      "https://github.com/${SITE_OWNER_GITHUB}",
	               "https://www.linkedin.com/in/${SITE_OWNER_LINKEDIN}/",
                      "${SITE_URL}/about"
                    ]
                  }
                }
                `}}
              >
              </script>
    )
  }

This was a good approach, but not for me, as I said, I was already on a tight schedule. I had already implemented this approach in my previous projects using other CMS-based websites. But this time, we could not afford to spend hours manually updating every page in the CMS.

So, how did we overcome this challenge?

I decided to give a shot at automating the schema generation. Ask me why? Because of the time and effort it could save.

First, development was necessary to create a component that reads from CMS and generates the schema, which was inevitable. Secondly, the amount of time it saves in the future. Automating would mean that any page that’s created in the future shall also have the schema generated without needing additional time from the content team to manually add it on every page. Hence automation was the best option; only if we could do it.

Let’s try,” we said, looking at the bright side.

Where to start? What to do? How do I know if it’s meeting the requirements?

We started off studying the JSON structure of Contentful and found that all the FAQs were coming from one reusable component and they had the same structure, i.e. a question and an answer in the rich text format, by looking at the data, we concluded that we could use the `component` attribute in the JSON to exact the data from the page, no matter where it is present in the page.

This way I can also ensure that the schema will get generated irrespective of the position of the FAQs within the page, as long as they are present.

This is the power of a well-architected project, investing adequate time in writing structured and maintainable code may cost you more time initially but it would save 2x time in the future, if not more.

Leveraging the consistent architecture, I developed a script that would map the questions and answers into an array, but just about then, another hurdle arose.

Since the answer was in a Contentful rich text format, it cannot be directly injected into the schema. In addition, the rich text parser was returning a React component which is useless in our context.

In the meanwhile, I started my research about JSON-LD structures and learned that we can inject text and HTML into the answer section of the schema.

Eureka! The moment of enlightenment!!

That’s the answer. We can convert the rich text into plain HTML and insert the HTML into the schema. I used a library that converts rich text into HTML.

Rich Text to HTML npm Package

Using this library, I wrote a function to fetch all the FAQs for the page’s JSON and mapped them into a JSON-LD format.

 export function generatePageFaqSchema({ pageData }: { pageData: TemplateUseCaseFragment }) {
 try {
const faqComponentJson = pageData?.sectionsCollection?.items.find(
     (section) => section?.component["__typename"] === "ComponentFaq",
   );


   const faqs = faqComponentJson?.component?.faqCollection?.items;


   const mainEntity = faqs?.map((faq) => {
     const answer = documentToHtmlString(faq.body.json);


     return {
       "@type": "Question",
       name: faq.heading,
       acceptedAnswer: {
         "@type": "Answer",
         text: answer,
       },
     };
   });


   const pageSchema = {
     "@context": "https://schema.org",
     "@type": "FAQPage",
     mainEntity,
   };


   return pageSchema;
 } catch (error) {
   return null;
 }
}

And then

Thereafter, in our pages folder, I can call this function by passing the page’s JSON into this function and inserting the returned schema into a <script> tag as suggested in the Next.JS documentation for JSON-LD.

export default async function Page(props: Props) {
 const useCaseData = await getUseCaseData(props.params.slug);
 let pageSchema;
  
   if (!useCaseData) {
       notFound();
   } else {
       pageSchema = generatePageFaqSchema({ pageData: useCaseData });
   }


 return (
   <>
     {!!pageSchema && (
       <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(pageSchema) }} />
     )}
     <UseCaseDetail useCase={useCaseData} />;
   </>
 );
}

Tested the generated HTML to verify if the schema is valid using a schema validator tool by copying the source and pasting it to the schema validator tool (ctrl + U for Chrome and options + command + U for Safari)

present

The schema validator tool will show any valid schema present on the page, if any.

Schema Validator Tool

With this, we can successfully automate the schema markup generation for our web pages powered by Contentful CMS. Your content team can take a Tea break without any hassle to update the page schema markup.

Happy hacking…

Author

Renuka Prasad
Renuka Prasad

Related posts