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 challenging, 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.
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.
Below is the video overview shown earlier before, watch this video to determine what templates and properties to create or use the finished application to https://blog-starter.thebcms.com/
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.
With that created, you can now create the About page template with the properties mentioned above.
Blog
For the category property, these are the values:
Blogs Page
Contact Page
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.
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.
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.
Footer
In addition to default properties, the footer template will have a group that holds the icon and the URL for the footer.
So first, create a new group called Social Link.
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.
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.
You can go further to type more information on the text field provided below.
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.
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.
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.
3. 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`
In addition to that the Graphql explorer will also be live at localhost:8000/___graphql
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.
4. 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. 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';
You also need to import Graphql from Gatsby:
import { graphql } from 'gatsby';
After it is time to initialize the component:
const BlogsPage: FC<{ data: PageData<BlogsPageData> & { blogs: { nodes: Array<{ bcms: { meta: { en: BlogEntryMeta; }; }; }>; }; }; }> = ({ data }) => { return ( ); };
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; }; }
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';
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; }; }; }; }
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';
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';
At this point, your code should look similar to this:
import React, { FC } from 'react'; import { BlogsPageData, PageData } from '../../../types'; import { graphql } from 'gatsby'; import { BlogEntryMeta } from '../../../bcms/types'; const BlogsPage: FC<{ data: PageData<BlogsPageData> & { blogs: { nodes: Array<{ bcms: { meta: { en: BlogEntryMeta; }; }; }>; }; }; }> = ({ data }) => { return <></>; };
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 } } } } } } } `;
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 in 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>
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> ); };
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'
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';
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; };
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, )} /> ); };
And after that, on your index.tsx you can then import this component for later use:
import {TopGradient} from '../../components/top-gradient'
Creating Blogs Card component
This component will handle the card that appears on the blog page.
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()}`; }, };
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; }
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> ); };
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 } } } } } `;
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
the page now, import the Blog Component:
import {BlogsCard} from '../../components/blogs/card'
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>
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.
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.
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>
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;
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';
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> ); };
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';
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]"
/>
Go back to your browser now and you should see the description beautifully displayed.
5. Recap
In this article, you've learned how to add data t o 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 Pro 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 soon.
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 to 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.
It takes a minute to start using BCMS
- What do you need to build a Gatsby blog
- Is Gatsby the best choice for blogging?
- BCMS: A Headless CMS suited for the Gatsby projects
- Building an SEO-friendly Gatsby blog with a headless CMS
- 2. Creating content structure in BCMS
- 3. Setting up Gatsby using BCMS CLI
- 4. Getting data from BCMS with Gatsby
- 5. Recap
- Next Steps
- Deploying Gatsby Websites
Other starters you may want to learn about
There are many example apps and starter projects to get you started.
Join our Newsletter
Get all the latest BCMS updates, news and events.
By submitting this form you consent to us emailing you occasionally about our products and services. You can unsubscribe from emails at any time, and we will never pass your email to third parties.