DEV Community

Cover image for Gatsby blog: Building SEO-friendly blog with BCMS code starter
Momcilo
Momcilo

Posted on

Gatsby blog: Building SEO-friendly blog with BCMS code starter

Gatsby is great for creating static websites, landing pages, and blogs. After building a static site with a headless CMS, it's time to build Gatsby blog with advanced SEO features.

This tutorial will show you how you can optimize your blog for SEO using a simple config

What do you need to build a Gatsby blog

Building a blog in the modern web ecosystem can be a challenging task, especially when it comes to ensuring SEO-friendliness. However, with the right tools like Gatsby and BCMS, this task becomes significantly easier and more efficient.

In this tutorial, you will learn how to build an SEO-friendly blog using Gatsby, as a static site generator, and BCMS, a headless content management system.

Is Gatsby the best choice for blogging?

Gatsby is a static site generator that builds your website as static files that can be deployed easily on various hosting platforms. It leverages powerful pre-configuration to build a website that uses only static files for high-speed page loads, service workers, code splitting, server-side rendering, intelligent image loading, asset optimization, and data prefetching. Gatsby's rich data plugin ecosystem lets you build sites with the data you want from various sources including BCMS.

Gatsby offers numerous advantages for blogging, including:

  • Performance: As Gatsby generates static HTML files, the site loads faster and performs better than traditional dynamic websites.
  • SEO: Gatsby sites are static, hence inherently more SEO-friendly, as search engines can easily crawl and index their content.
  • Security: Since Gatsby sites are static, they have a smaller attack surface and are less prone to security vulnerabilities.
  • Customization: Gatsby allows for extensive customization through its Gatsby plugin ecosystem and React-based architecture, enabling you to create a unique and tailored blogging experience.
  • Content management: Gatsby can be integrated with various headless CMS providing a flexible content management solution.

BCMS: A Headless CMS suited for the Gatsby projects

BCMS is a headless content management system. Unlike traditional CMS, a headless CMS provides developers with greater flexibility as it separates the content management from the presentation layer. This means you can manage your content on BCMS and present it on any platform or device using APIs or BCMS integration packages. BCMS is a perfect companion for Gatsby as it allows for easy content updates and changes without the need to rebuild or redeploy your Gatsby site.

Building an SEO-friendly Gatsby blog with a headless CMS

By combining Gatsby's efficient page load and BCMS's API key for easy content management, you can create a user-friendly and SEO-friendly blog. Gatsby's static nature ensures your blog loads fast, a key factor in SEO ranking. Meanwhile, BCMS allows for easy meta tags and content adjustments, which are crucial for SEO. Furthermore, Gatsby's React-based framework allows the implementation of advanced SEO concepts like structured data and AMP.

This tutorial leverages the BCMS Gatsby Blog starter. If you need to see how everything came together, feel free to head to the Gatsby blog GitHub repo. The article also leverages the BCMS instance so you can always clone to see it in action(More on the BCMS Instance later on).

Gatsby blog tutorial: Table of contents

  1. Setting Up BCMS
  2. Creating content structure in BCMS
  3. Setting up Gatsby using BCMS CLI
  4. Getting data from BCMS with Gatsby
  5. Recap, Next steps and Deployment.

1. Setting up BCMS

To start with, you'll need to create an account on the BCMS website. Once your account is created, you can either decide to use the cloud version of BCMS or the self-hosted version. Whichever you choose make sure to create an account because you will need it to login later.

If you choose the cloud version, create a BCMS instance. The instance is like the repository that stores the content for your application.

If you use the self-hosted version, the instance is what you automatically create using the bcms --cms create command.

After the creation of your instance, you should see a dashboard that looks similar to the photo below:

Image description

2. Creating content structure in BCMS

Now that you have created your instance, the next thing to do is to create your content structure. In this section, you will learn how to create your content structure in BCMS.

What is Content structure?

Content structure also known as Content model refers to the organization and arrangement of information within your blog. This includes defining your posts, categories, authors, tags, and any other elements that make up your content. Having a well-defined structure is crucial for both SEO and user navigation.

In other words, your content structure is like creating the skeleton or the framework in which your content lives.

Creating your content structure in BCMS

BCMS provides the Template feature which you can use to define your content structure. It is located on the left side of the dashboard as seen in the preceding picture. Before creating a template, you need to identify the fields you’re creating the templates for and the properties that need to live on that template.

So now I will list the templates for the pages I will use in this tutorial. Bear in mind that this may differ from how you plan to do yours in other projects, which is very well welcomed. There is no 1 way as to how to approach this. But for this tutorial, these are the templates I’m going to use for the pages in the application:

  1. About Page
  2. Blog (This is the template that will contain the properties for each blog)
  3. Blogs page
  4. Contact Page
  5. Home Page
  6. Header
  7. Footer

Creating Each Template

  • About Page

Image description

These are the properties that will live on the template as shown in the image above:

  • Title and slug (These are default)
  • SEO(This is a group that will contain basic SEO details for the page).
  • Subtitle
  • Description
  • Cover

First, create a group called SEO and in there create two properties named title and description.

Image description

With that created, you can now create the About page template with the properties mentioned above.

Image description

- Blog

Image description

For the category property, these are the values:

Image description

  • Blogs Page

Image description

-Contact Page

Image description

-Home Page

The Home Page needs a Hero section that will contain featured blogs, to do that I will create a new group called Home Hero which will contain a title, subtitle, and a Featured blogs entry pointer. So when you want to create the data for the homepage, you can link the blogs you want to feature to it.

Image description

So in addition to the default Title and slug properties, the properties that will be on the home page will be the SEO group and the home hero group.

Image description

- Header

The Header needs a logo field that will hold the logo for the website and also a nav array property to hold the navigation values in the header.

Image description

- Footer

In addition to default properties, the footer template will
have a group that holds the icon and the URL for the footer.

Image description

So first, create a new group called Social Link.

Image description

After that, you can now create the footer template and add this as a group which will be an array cause you will need more than one Icon and URL.

Image description

Creating Entries

After creating the content structure, the step that remains is to create the data based on these content structures. And in BCMS they’re called entries.

Under the entries tab on the left side of your dashboard, you will see the templates you just created, clicking on the templates leads you to where you can edit and create new entries.

Let’s create the About page entry to demonstrate this.

On the About page entry, fill in the details of the template.

Image description

You can go further to type more information on the text field provided below.

Image description

For texts that utilize images that you can use and reuse in the content piece, use BCMS Widgets.

Widgets unlike groups are used in the entry and are not linked in the template. Please look into the deep dive about BCMS widgets tutorial to learn more about widgets and how to use them.

For this tutorial, I will create a widget named Text and Image that will hold text and an image along with the image position either right or left so it can be styled in my Gatsby code.

Image description

Now on your content, you can use this widget. You use a widget by entering the slash (/) symbol on a new line. You press the slash symbol and then you search for the widget and use it.

Image description

You’ve now learned how to create templates and use entries in BCMS. So go ahead and create all the entries for the templates. if you want to use the same data and images used in the starter, download the finished BCMS instance starter from GitHub and run it. Please do not forget to give us a star on GitHub.

Setting up Gatsby using BCMS CLI

It is time to create the Gatsby application. BCMS makes it easy for us to use the BCMS CLI.

To create a Gatsby application with BCMS, follow the guide in the documentation and you should get up and running in no time.

For this application I will use tailwind CSS to style the pages, so follow the official tailwind CSS installation guide for Gatsby.

If everything is successfully installed, your Gatsby website will be live on localhost:8000

Image description

In addition to that the Graphql explorer will also be live at localhost:8000/___graphql

Image description

Please take note of the graphql explorer which contains all the BCMS data that I will query later. Play around with it as you would a graphql query and try to get the data, this will make more sense when I get the data later.

Getting data from BCMS with Gatsby

After creating the data, what is left is to use the data in the Gatsby Application. I will now show you how to use BCMS in a Gatsby project, using the Blog page, which will contain the SEO for that page, how to navigate to the different blogs in the application dynamically and how to style the content.

This section will demonstrate this using only the Blogs page and will not build out all the other pages as this will make the tutorial lengthy. With the knowledge you will get from this section, you will be well-equipped to build the other pages on your own.

Here is the link to the finished application on Github in the starters repository https://github.com/bcms/starters/tree/master/gatsby/blog. If you need to look at it at any point, you can always visit the Github, or you can clone and follow along directly.

Creating the Blogs Page

Under the pages folder, create a new folder named blogs and under there create a new index.tsx file. I will be using functional components syntax to build this component, so import the react package as well as the FC package together.


import React, { FC } from 'react';

Enter fullscreen mode Exit fullscreen mode

You also need to import Graphql from Gatsby:

import { graphql } from 'gatsby';

Enter fullscreen mode Exit fullscreen mode

After it is time to initialize the component:

const BlogsPage: FC<{
  data: PageData<BlogsPageData> & {
    blogs: {
      nodes: Array<{
        bcms: {
          meta: {
            en: BlogEntryMeta;
          };
        };
      }>;
    };
  };
}> = ({ data }) => {


  return (

  );
};

Enter fullscreen mode Exit fullscreen mode

With this component setup, you can see that there are missing quite a few things here. Which are the PageData, BlogsPageData BlogEntryMeta types.

On the main folder of your application create a new folder named types and in there create a pages folder which will hold all the types for your different pages. Since I’m creating the blogs page, I will create a new blogs.ts and input the types for blogs.ts there.

import {
  BCMSPropEnumData,
  BCMSPropMediaDataParsed,
  BCMSPropRichTextDataParsed,
} from '@becomes/cms-client/types';
import { BlogEntryMeta, BlogsPageEntryMeta } from '../../bcms/types';

export interface BlogLite { //BlogsPage
  title: string;
  slug: string;
  cover: BCMSPropMediaDataParsed;
  description: BCMSPropRichTextDataParsed;
  date: number;
  category: BCMSPropEnumData;
}

export interface BlogsPageData { //BlogsPage extending BlogLite
  meta: {
    en: BlogsPageEntryMeta;
  };
  blogs: BlogLite[];
}

export interface BlogPageData { //BlogPage
  meta: {
    en: BlogEntryMeta;
  };
  content: {
    en: BCMSPropRichTextDataParsed;
  };
}

Enter fullscreen mode Exit fullscreen mode

At the top, you can see I imported BlogEntryMeta and BlogsPageEntryMeta. These are types automatically generated by the BCMS CLI, if you click on them, you can see it generated the exact types for the properties you created in your template.

After doing that I created 3 different types, one that holds the basic details of each of the blogs that will be shown on the blog page and then one for the blog itself, that is when you click on a blog and then it displays the full content for you.

Still, on the pages folder create an index.ts where you can import the pages:

export * from './blogs';
// export * from './contact'; (for the different data types you're creating later
// export * from './home';

Enter fullscreen mode Exit fullscreen mode

After doing that, go back to the main types folder and create a new file called page-props.ts. This file contains the typing for the different pages I will have in my application and as seen below they’re the location, page(main content), header, and footer.

import { HeaderEntryMeta } from '../bcms/types';
import { FooterEntryMeta } from '../bcms/types/entry/footer';

export interface PageData<Page = { meta: { en: { title: string } } }> {
  location: string;
  page: {
    bcms: Page;
  };
  header: {
    bcms: {
      meta: {
        en: HeaderEntryMeta;
      };
    };
  };
  footer: {
    bcms: {
      meta: {
        en: FooterEntryMeta;
      };
    };
  };
}

Enter fullscreen mode Exit fullscreen mode

After doing that, create a new file named index.ts where you import all types you just created:

export * from './pages';
export * from './page-props';
Enter fullscreen mode Exit fullscreen mode

You can now navigate back to your index.tsx file under blogs and import the types folder you just created along with the BlogEntryMeta:

import { BlogsPageData, PageData } from '../../types';
import { BlogEntryMeta } from '../../bcms/types';
Enter fullscreen mode Exit fullscreen mode

At this point, your code should look similar to this:

Image description

But something is still missing. The data. on line 8 I’m referencing a data variable that hasn’t been declared yet. So how do you get data from BCMS, well you do that by using Graphql. So at the bottom of the page, I will query the data that I need for this page using GraphQL.


export const query = graphql`
  {
    page: bcmsBlogsPage {
            bcms {
        meta {
          en {
            seo {
              description
              title
            }
            slug
            subtitle
            title
          }
        }
      }
    }
    blogs: allBcmsBlog {
      nodes {
        bcms {
          meta {
            en {
              cover {
                _id
                alt_text
                caption
                height
                name
                src
                svg
                width
              }
              date
              slug
              title
              category {
                selected
                items
              }
              description {
                name
                type
                value
              }
              seo {
                title
                description
              }
            }
          }
        }
      }
    }
  }
`;

Enter fullscreen mode Exit fullscreen mode

Take note that these queries aren’t auto-generated, Do you remember the GraphQL explorer I showed you some pictures above? using that explorer shows you the possible data BCMS has made available for you which you can query.

So for this page, I queried the data that I needed which is the blogs page and then I went deep down into the query to get the exact fields I needed for the different blogs that are going to show up on the blogs page including detailed fields of the cover_image to be able to render them well. This is a powerful feature Graphql has, which I can utilize by using Gatsby.

Previewing Page Data

After getting the data needed on the page, now let’s preview the data. To do that I will add some HTML and tailwind CSS markup to the return statement on the about.tsx page:

<div className="relative pt-10 pb-8 md:py-[72px] lg:pb-[100px] xl:pb-[128px]">
        <div className="container">
          <div>
            <div className="flex flex-col-reverse items-center text-center mb-8 md:gap-4 md:mb-10 lg:mb-12">
              <h1 className="leading-none font-medium tracking-[-0.41px] md:text-2xl md:leading-none lg:text-[56px] lg:leading-none">
                {data.page.bcms.meta.en.title}
              </h1>
              <h2 className="text-sm leading-none tracking-[-0.41px] text-appGray-600 mb-2.5 md:text-base md:leading-none lg:text-xl lg:leading-none lg:mb-5">
                {data.page.bcms.meta.en.subtitle}
              </h2>
            </div>
          </div>
        </div>
      </div>
Enter fullscreen mode Exit fullscreen mode

If you go over to localhost:8000/blogs now you’ll see the content showing up on the page like this.

Image description

Great, the blog page is coming to life. So far you’ve learned how to query data from BCMS using graphql and how you can use them in your component. So what remains is to finish the design for the blog page, and this is how I will do it

  • Create a search component
  • Create a Category and blog filtering utilities.
  • Create the TopGradient component to give the page a good feel
  • Create a card component for displaying the blogs

Creating A search component

Under the src folder, I will create a new folder called components that will hold all I need.

Then I will create a search.tsx file for the search component

//components/search.tsx
import SearchIcon from '../assets/icons/search.svg';
import classNames from 'classnames';
import React, { FC } from 'react';

interface Props {
  value: string;
  onEnter: () => void;
  onChange: (value: string) => void;
  className?: string;
}

export const Search: FC<Props> = ({ value, onEnter, onChange, className }) => {
  const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    onChange(e.target.value);
  };

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    onEnter();
  };

  return (
    <form
      onSubmit={handleSubmit}
      className={classNames(
        'flex items-center border border-appGray-100 rounded-[48px] px-4 max-w-[848px] mx-auto md:px-6 lg:px-8',
        className,
      )}
    >
      <img
        src={SearchIcon}
        alt="Search"
        className="w-[14px] h-[14px] mr-1.5 md:w-6 md:h-6 md:mr-2.5 lg:w-8 lg:h-6 lg:mr-[14px]"
      />
      <label className="w-full">
        <input
          value={value}
          type="search"
          placeholder="Search"
          className="placeholder:text-appText bg-transparent py-[11px] text-sm leading-none tracking-[-0.41px] w-full focus:outline-none md:text-lg md:leading-none md:py-4 lg:text-2xl lg:leading-none lg:py-[21px]"
          onInput={handleInput}
        />
      </label>
    </form>
  );
};

Enter fullscreen mode Exit fullscreen mode

The component needs the classname dependency and a search icon which I referenced to work. So install the classname package and download the search icon below.

Back to your index.tsx page, you can now import the component for later use:

import {Search} from '../../components/search'
Enter fullscreen mode Exit fullscreen mode

Creating utilities to manage categories and enabling filtering

Back to index.tsx file on the blogs page, I will now write a function before the return statement that handles filtering and sorting, but I won’t use it in the template just yet. This component will make use of the useEffect, useState, and useMemo hooks from react. Import them at the top of your code:

import React, { FC, useEffect, useMemo, useState } from 'react';
Enter fullscreen mode Exit fullscreen mode

After importing the necessary packages I need, I will go into the return statement of the component and write the Searching, filtering, and categories function.

const [searchVal, setSearchVal] = useState('');
  const [selectedCategory, setSelectedCategory] = useState('');

  useEffect(() => {
    const searchParams = new URLSearchParams(location.search);
    const sParam = searchParams.get('s');

    if (sParam) {
      setSearchVal(sParam);
    }
  }, []);

  const categories = useMemo(() => {
    return (
      data.blogs.nodes.reduce((acc, e) => {
        if (
          e.bcms.meta.en.category.selected &&
          !acc.includes(e.bcms.meta.en.category.selected)
        ) {
          acc.push(e.bcms.meta.en.category.selected);
        }
        return acc;
      }, [] as string[]) || []
    );
  }, [data.blogs]);

  const filteredBlogs = useMemo(() => {
    return (
      data.blogs.nodes.filter((blog) => {
        let show = true;
        if (searchVal) {
          show =
            show &&
            blog.bcms.meta.en.title
              .toLowerCase()
              .includes(searchVal.toLowerCase());
        }
        if (selectedCategory) {
          show =
            show && blog.bcms.meta.en.category.selected === selectedCategory;
        }
        return show;
      }) || []
    );
  }, [data.blogs, searchVal, selectedCategory]);

  const inputFocusHandler = (): void => {
    return;
  };
Enter fullscreen mode Exit fullscreen mode

Creating the Page Top Gradient

On the components folder, create a new file named top-gradient.tsx and then copy the code below to create the beautiful top gradient design:

import classNames from 'classnames';
import React, { FC } from 'react';

interface Props {
  className?: string;
}

export const TopGradient: FC<Props> = (props) => {
  return (
    <div
      className={classNames(
        'absolute -top-9 left-1/2 -translate-x-1/2 -translate-y-full bg-[#D9FAA1] blur-[110px] w-[428px] h-[428px] rounded-full',
        props.className,
      )}
    />
  );
};

Enter fullscreen mode Exit fullscreen mode

And after that, on your index.tsx you can then import this component for later use:

import {TopGradient} from '../../components/top-gradient'
Enter fullscreen mode Exit fullscreen mode

Creating Blogs Card component

This component will handle the card that appears on the blog page.

Image description

From the card above, you can see I need an image, a date, the title, and the descriptions.

BCMS returns dates as numbers, these numbers are milliseconds so I’ll have to format it to be able to use the date.
To do that, I will create a new folder under src named utils and then create a new file under it named date.ts

//~src/utils/date.tsx
export const dateUtil = {
  format(val: number) {
    const date = new Date(val);
    return `${date.getDate()} ${date.toLocaleString('default', {
      month: 'short',
    })}, ${date.getFullYear()}`;
  },
};

Enter fullscreen mode Exit fullscreen mode

Now, you can create the card component. Under components, create a new folder named blogs then under it, create a file named card.tsx and import the necessary dependencies:

import React, { FC } from 'react';
import { BlogLite } from '../../../types';
import { Link, graphql } from 'gatsby';
import { BCMSImage } from 'gatsby-source-bcms/components';
import OpenIcon from '../../assets/icons/open.svg';
//Link to download Icon below
import { dateUtil } from '../../utils/date';

interface BlogsCardProps {
  blog: BlogLite;
}

Enter fullscreen mode Exit fullscreen mode

The next thing to do is to create the component and the design for the card design.


export const BlogsCard: FC<BlogsCardProps> = ({ blog }) => {
  return (
    <article>
      <Link to={`/blog/${blog.slug}`}>
        <BCMSImage
          media={blog.cover}
          options={{
            sizes: {
              exec: [
                {
                  width: 800,
                  height: 800,
                },
              ],
            },
          }}
          className="w-full cover aspect-[1.48] rounded-2xl overflow-hidden mb-4 xl:aspect-[1.04] xl:mb-6"
        />
        <div className="text-xs leading-none font-medium tracking-[-0.82px] text-appGray-400 mb-2 xl:text-base xl:leading-none xl:mb-4">
          {dateUtil.format(blog.date)}
        </div>
        <div className="flex items-center mb-2.5 xl:mb-4">
          <div className="text-sm leading-none font-medium tracking-[-0.41px] mr-2 xl:text-2xl xl:leading-none">
            {blog.title}
          </div>
          <div className="flex items-center justify-center p-0.5 rounded bg-[#F2F2F2] xl:p-1">
            <img src={OpenIcon} alt="Icon" className="w-3 h-3 xl:w-4 xl:h-4" />
          </div>
        </div>
        //Space for Description
      </Link>
    </article>
  );
};
Enter fullscreen mode Exit fullscreen mode

Please take note that I left a space for the description.

And then at the bottom of the page, you input the graphql data you need for that card:


export const query = graphql`
  fragment Blog on BlogEntry {
    bcms {
      meta {
        en {
          cover {
            _id
            alt_text
            caption
            height
            name
            src
            svg
            width
          }
          slug
          title
          date
          description {
            name
            type
            value
          }
          seo {
            title
            description
          }
        }
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

This GraphQL data expands the fragments that I will be passing on to it in the next step where I add everything together on the blogs/index.tsx page.

On the index.tsx page now, import the Blog Component

import {BlogsCard} from '../../components/blogs/card'
Enter fullscreen mode Exit fullscreen mode

Hello

<body>
    <div className="relative pt-10 pb-8 md:py-[72px] lg:pb-[100px] xl:pb-[128px]">
      <div className="container">
        <div>
          <div className="flex flex-col-reverse items-center text-center mb-8 md:gap-4 md:mb-10 lg:mb-12">
            <h1 className="leading-none font-medium tracking-[-0.41px] md:text-2xl md:leading-none lg:text-[56px] lg:leading-none">
              {data.page.bcms.meta.en.title}
            </h1>
            <h2 className="text-sm leading-none tracking-[-0.41px] text-[#5B5B5E] mb-2.5 md:text-base md:leading-none lg:text-xl lg:leading-none lg:mb-5">
              {data.page.bcms.meta.en.subtitle}
            </h2>
          </div>
          <div className="max-w-[848px] mx-auto">
            <Search
              onChange={(value) => setSearchVal(value)}
              onEnter={inputFocusHandler}
              value={searchVal}
              className="mb-3 lg:mb-6"
            />
            <div className="grid grid-cols-2 gap-2.5 md:grid-cols-3 lg:grid-cols-5 lg:gap-[18px]">
              {categories.map((category, index) => (
                <button
                  key={index}
                  className={`flex items-center justify-center w-full px-6 py-2.5 rounded-[32px] border transition-colors duration-300 text-xs leading-none tracking-[-0.41px] capitalize md:text-lg md:leading-none md:py-3 lg:text-2xl lg:leading-none lg:px-6 lg:py-[14px] ${
                    category === selectedCategory
                      ? 'border-[#181A2C] bg-[#181A2C] text-white'
                      : 'border-[#E0E0E0]'
                  }`}
                  onClick={() => setSelectedCategory(category)}
                >
                  {category.toLowerCase()}
                </button>
              ))}
              <button
                onClick={() => setSelectedCategory('')}
                className={`col-span-2 flex items-center justify-center w-full px-6 py-2.5 rounded-[32px] border transition-colors duration-300 text-xs leading-none tracking-[-0.41px] md:col-span-1 md:col-start-1 md:row-start-1 md:text-lg md:leading-none md:py-3 lg:text-2xl lg:leading-none lg:px-6 lg:py-[14px] ${
                  selectedCategory === ''
                    ? 'border-[#181A2C] bg-[#181A2C] text-white'
                    : 'border-[#E0E0E0]'
                }`}
              >
                All
              </button>
            </div>
          </div>
        </div>
      </div>
      <TopGradient />
    </div>
    <div className="container mb-8 md:mb-20 lg:mb-[104px]">
      {filteredBlogs.length > 0 ? (
        <div className="grid grid-cols-1 gap-6 mb-8 md:grid-cols-2 md:mb-20 lg:grid-cols-3 xl:gap-10 lg:mb-[128px]">
          {filteredBlogs.map((blog) => (
            <BlogsCard
              key={blog.bcms.meta.en.slug}
              blog={blog.bcms.meta.en}
            />
          ))}
        </div>
      ) : (
        <div className="leading-none tracking-[-0.41px] text-center mb-8 md:mb-20 lg:text-lg lg:mb-[128px]">
          There are no blogs for the applied filter in "{searchVal}"
        </div>
      )}
      <div>
        <h2 className="leading-none font-medium tracking-[-0.41px] mb-7 md:text-2xl md:leading-none md:mb-10 lg:text-[32px] lg:leading-none lg:mb-12">
          All posts
        </h2>
        <div className="grid grid-cols-1 auto-rows-fr gap-6 p-4 border border-appGray-[#D1D1D1] rounded-2xl md:grid-cols-2 lg:grid-cols-3 xl:gap-10 xl:p-8">
          {data.blogs.nodes.map((blog, index) => (
            <BlogsCard key={index} blog={blog.bcms.meta.en} />
          ))}
        </div>
      </div>
    </div>
    </body>
Enter fullscreen mode Exit fullscreen mode

If you check your browser on the blogs tab, localhosT:8000/blogs you should see that the page is displaying the cards nicely along with the filtering and searching functionality.

Image description

But there’s one more thing that needs to be done. If you check the bottom of the blog cards on the live URL, you’ll see a description attached, but this one I am building is without descriptions.

Image description

The reason for this is that I’m missing a ContentManager component. If you can recall, when creating the template for the blogs, the description is a Rich_Text property and not a normal string. So a common string interpolation like this below won’t work.

<p>
   {blog.description}
</p>
Enter fullscreen mode Exit fullscreen mode

So to get this to work, I need to use the ContentManger component.

Creating A content manager component

On the components folder, create a new file named content-manager.tsx. This component is responsible for handling the content that BCMS does not return as a straightforward string. So content BCMS returns in blocks or arrays or content returned with images and texts combined. Two of such contents are the RichText field and widgets.

Since this component is also responsible for handling the widgets, then it is the best time to create the design for the text_with_image widget which I created earlier. To create the widget, create a new folder under the components folder named widgets and then create a new file named text-with-image.tsx.

Below is how I decided to style my widget in line with what is in the starter.

import { BCMSImage } from 'gatsby-source-bcms/components';
import React, { FC, useMemo } from 'react';
import { ContentManager } from '../content-manager';
import { TextWithImageWidget as TextWithImageWidgetType } from '../../../bcms/types';
import { BCMSEntryContentParsedItem } from '@becomes/cms-client/types';

interface Props {
  data: string;
}

export const TextWithImageWidget: FC<Props> = ({ data }) => {
  const parsedData: TextWithImageWidgetType = JSON.parse(data);

  const hasText = useMemo(() => {
    const text = parsedData.text as BCMSEntryContentParsedItem[];
    return text && text[0].value;
  }, []);

  return (
    <div
      className={`flex flex-col gap-6 mb-6 md:mb-8 lg:gap-8 lg:items-start lg:mb-12 ${
        parsedData?.image_position?.selected === 'LEFT'
          ? 'lg:flex-row-reverse'
          : 'lg:flex-row'
      }`}
    >
      <ContentManager items={parsedData.text || []} className="prose" />
      <BCMSImage
        media={parsedData.image}
        className={`aspect-[2.07] rounded-lg overflow-hidden w-full cover flex-shrink-0 lg:rounded-2xl ${
          hasText
            ? 'lg:aspect-[1.14] lg:w-[500px] lg:mb-0 xl:w-[728px]'
            : 'lg:aspect-[2.43]'
        }`}
      />
    </div>
  );
};

export default TextWithImageWidget;

Enter fullscreen mode Exit fullscreen mode

This widget expects a data prop to be passed in and it queries the data, gets the images along with the data and renders them with a nice design while taking into account the image position as defined in the CMS.

On the widgets folder create a new file named index.ts where you import this widget along with other widgets you might create in the future.

export * from './text-with-image';
Enter fullscreen mode Exit fullscreen mode

Now back to the content-manager.tsx file. This component will now receive Rich text and widget data, parse them and display them accordingly.

import React, { useRef, useEffect, FC } from 'react';
import { BCMSPropRichTextDataParsed } from '@becomes/cms-client/types';
import { BCMSContentManager } from 'gatsby-source-bcms/components';
import { navigate } from 'gatsby';
import { BCMSWidgetComponents } from 'gatsby-source-bcms/components/content-manager';
import { TextWithImageWidget } from './widgets';

interface ContentManagerProps {
  items: BCMSPropRichTextDataParsed;
  widgetComponents?: BCMSWidgetComponents;
  className?: string;
}

export const ContentManager: FC<ContentManagerProps> = ({
  items,
  widgetComponents,
  className,
}) => {
  const managerDOM = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const parseInternalLinks = (): void => {
      if (managerDOM.current) {
        const links = managerDOM.current.querySelectorAll('a');
        links.forEach((link) => {
          const href = link.getAttribute('href');
          if (href && href.startsWith('/')) {
            link.target = '_self';
            const clickHandler = (event: Event): void => {
              event.preventDefault();
              void navigate(href);
            };
            link.addEventListener('click', clickHandler);

            return () => {
              link.removeEventListener('click', clickHandler);
            };
          }
        });
      }
    };

    parseInternalLinks();
  }, []);

  return (
    <div ref={managerDOM} className={className}>
      <BCMSContentManager
        items={items}
        widgetComponents={
          widgetComponents || {
            text_with_image: TextWithImageWidget,
          }
        }
      />
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode

Now go to the blogs under components and then the card.tsx file to utilize the ContentManager component. Import it at the top of the component:

import { ContentManager } from '../content-manager';
Enter fullscreen mode Exit fullscreen mode

After doing that, go into the card and then replace the comment that leaves space for description with the code below:

<ContentManager
          items={blog.description}
          className="text-sm leading-[1.3] tracking-[-0.41px] text-appGray-500 xl:text-xl xl:leading-[1.3]"
        />
Enter fullscreen mode Exit fullscreen mode

Go back to your browser now and you should see the description beautifully displayed.

Image description

Recap

In this article, you've learned how to add data to BCMS, how to retrieve data from BCMS using the power of Graphql and Gatsby, and how to beautifully render them on your code.

Next Steps

The Knowledge, you’ve gained so far, is sufficient for you to be able to build applications with BCMS and Gatsby comfortably. But this application can still be improved further, dive into the starter codebase and study how it is made, learn, and model the way the project is structured.

This tutorial project didn’t use the SEO details I input when creating the templates and entries, but the starter codebase used a PageWrapper component that uses react-helmet to send those SEO details to the browser. So in essence, the starter uses the recommended structure for creating Gatsby applications and also shows how to perfectly retrieve and render BCMS Content.

Clone the starter codebase if you haven't cloned before, complete this project using the structure you see or do it in another way and get all pages running, from the homepage to the header and footer and the contact pages. Build another different application entirely, there are application ideas in the BCMS starters website and by doing that, you’ve gotten comfortable building applications with Gatsby and BCMS

Deploying Gatsby Websites

If you used a self-hosted BCMS instance, You will first need to host in on a live server. You need to do this because it is currently only stored on your machine. However, with a BCMS Cloud Version, you won't have to worry about deployment because it will be handled for you, allowing you to continue using the same server from development into production.

To Deploy your local BCMS instance, follow the steps in this tutorial on the BCMS documentation. For now, DigitalOcean is the only server BCMS provides deployment steps for, but it will be updated for other services in the near future

After deploying the instance, it's now time to deploy the Frontend.

But first, keep in mind that your instance URL has been changed to a production URL, which means that instead of localhost:8080, you will be using a new URL provided by DigitalOcean or one that you have created on DigitalOcean. So before pushing your Frontend to production, I will advise you test the new URL in your codebase locally first before pushing it live

To do that, go to all the places in your codebase where BCMS_API_ORIGIN is declared and change the value to your new URL. After doing that test your app to see if it is functioning as before, if all is successful it should be running as usual and you can now move on to deploy the Frontend.

You can now deploy the Gatsby Frontend to Netlify easily by following the official guide on the Gatsby Website.

Top comments (0)