DEV Community

Cover image for Content Management with Gatsby, Netlify and Contentful
Babs Craig
Babs Craig

Posted on • Edited on

Content Management with Gatsby, Netlify and Contentful

Gatsby, Netlify and Contentful - The Triple Tag Team For Content Management Success

I've been using Gatsby for the better part of 6 months now. And it's quickly become my go-to for building static sites. The advantages are huge. You get:

  • A lot of configuration and boilerplate done out of the box.
  • Speed, SEO and performance optimizations.
  • A great community, great docs, a growing plugin ecosystem.
  • And my personal favourite - getting to write all the React & GraphQL code I want.

It's been a great developer experience, to say the least. However, when building static sites, one of the major concerns in choosing a tool is how content gets updated on the site. Lots of older platforms have solved this one way or another, Wordpress being the most popular of them, but using the triple threat of Gatsby, Netlify, and Contentful, we can build a pretty good alternative to the traditional CMS tools out there while retaining our SEO compatibility.

This article will show you how you can set up a system for managing content on any page of your Gatsby site. In our case, we'll be using Gatsby's powerful gatsby-node API to pull in content from Contentful and to dynamically generate pages. You can also use Contentful data on any existing page via the provided graphql queries.

Let's begin.

You'll need the gatsby-cli tool to get started. Run npm i -g gatsby in your terminal and once that has run, create a new project with

$ gatsby new gatsby-contentul-blog
Enter fullscreen mode Exit fullscreen mode

This will create a new Gatsby project in a folder called gatsby-contentful-blog. cd into the new project and run gatsby develop. Now you have the default Gatsby starter homepage:

Open up the project in your favourite text editor and navigate to the pages folder. Let's tweak some of the content in the index.js: (You can just copy and paste this in)

    import React from "react";
    import { Link } from "gatsby";

    import Layout from "../components/layout";
    import Image from "../components/image";
    import SEO from "../components/seo";
    import "./index.css";

    const IndexPage = () => (
      <Layout>
        <SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
        <div className="home">
          <h1>Hello There</h1>
          <p>Welcome my awesome blog</p>
          <div>
            <div
              style={{
                maxWidth: `300px`,
                margin: "0 auto 1.45rem"
              }}
            >
              <Image />
            </div>
          </div>
          <Link to="/blogposts/">View all posts</Link>
        </div>
      </Layout>
    );

    export default IndexPage;
Enter fullscreen mode Exit fullscreen mode

Next, find page-2.js and change the filename to blogposts.js. Gatsby uses the name of any file in the pages folder as a route name and will make the exported React component available on said route. This means we now have a /blogposts route. We'll come back to this file later but in the meantime, let's also change a few values in the gatsby-config.js file. This file sits in the project root.

siteMetadata: {
        title: `My Awesome Blog`,
        description: `An awesome blog displaying my awesome posts.`,
        author: `YOUR_NAME`,
    },
Enter fullscreen mode Exit fullscreen mode

Great! We now have our basic site set up. So we'll go to the Contentful website and create a new account. It's pretty painless and you should be set up in no time. By default they provide an example space but let's create a fresh one for the project.

Open up the sidebar and click on Create Space. Choose the free option and give your space any name. I'll call mine gatsby-blog. Select the empty space option, click Proceed to confirmation, and confirm your options.

After confirming, on the dashboard, click either the "Create Content Type" button or the "Content Model" button in the header and fill in the form that appears. Let's call the content type Blog Post and leave the API Identifier as is. Enter any description you'd like.

After creating the content type, we'll start to add some fields to it A field is a building block for our content. If you have a blog post for instance, a few fields could be the title, the body, the tags and an image. This will generate a form that you'll fill later on when we start to create actual blog posts. Follow the next steps to create a title field.

  • Click on the Add Field button to the right of the dashboard.
  • Select Text as the type of field you want.

  • Add another field. Select Media as the type instead of Text and call it image.
  • Add a tags field by selecting Text as the type. Give it tags as a name, and then select the list option on the screen below since we will be storing a list of tags in this field.

  • Lastly, create a slug field. Start by selecting Text as the type and call it slug. This time, instead of clicking Create as above, click on Create and Configure. On the next screen go to the Appearance tab and select slug as the way the field should be displayed. Also, in the Validations tab select unique field to be sure that no two blog posts have the same slugs

Your content model should now look like this:

A content model is like a schema that our actual content will follow. You can create all types of models such as case studies, blog posts, product data, page content and so on.

Save your changes and click on the Content button at the top of the page and select Add Blog Post. I'm going to add three posts with placeholder data, feel free to add as many as you'd like. For images, you can grab some free, open license ones from unsplash.com. Notice how the slug field gets auto-generated as you enter the title? This will come in handy later.

Awesome! That was a lot but we're halfway there...

At this point we have our first couple of blog posts and it's time to bring them into our Gatsby site. For this, we'll depend on Gatsby's awesome GraphQL API for pulling in the data. Let's work on that next.

Go to your settings in Contentful and click on the API Keys option in the dropdown menu. Create a new API Key and keep the details close by.

Back in your terminal, install the Gatsby plugin we need to start pulling in our Contentful data.

$ yarn add gatsby-source-contentful
Enter fullscreen mode Exit fullscreen mode

We'll be using Contentful's Content Delivery API since we want to retrieve published data only, so be sure to grab the Content Delivery API key and not the Content Preview API key.

In your gatsby-config.js file, add the configuration object to the plugins array:

plugins: [
        ...
    {
      resolve: `gatsby-source-contentful`,
      options: {
        spaceId: `YOUR_SPACE_ID`,
        accessToken: `YOUR_CONTENT_DELIVERY_API_KEY`
      }
    }
],
Enter fullscreen mode Exit fullscreen mode

You should restart your development server again at this point for the new configs to kick in. When the server restarts, gatsby-source-contentful's GraphQL queries will be available to use.

We can easily test if everything is working by using the GraphiQL playground that Gatsby provides for us. Open http://localhost:8000/___graphql in your browser and run the query below by pasting it into the left window on the page. The query name is allContentfulBlogPost because our content model is called Blog Post. If we had called it Product or Case Study, then the query made available to us would have been allContentfulProduct or allContentfulCaseStudy.

{
  allContentfulBlogPost {
    edges {
      node {
        id
    slug
        title
        tags
        image {
          file {
            url
          }         
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The gatsby-source-contentful plugin handles all the behind the scenes fetching from the Contentful API using the keys we provided in the gatsby-config file. It then makes a semantically named GraphQL query available to us.

If it all works, you should see the content you added in the results window to the right of the GraphiQL window in JSON format.

Now that we have connected our Gatsby blog with our Contentful data, we can start building the pages for the blog. Gatsby provides us with a file called gatsby-node.js. This file can be used to dynamically add pages to your site. When Gatsby runs, it will look at the code here and create any pages you tell it to. In the gatsby-node.js file, let's place the following code:

const path = require(`path`);
const slash = require(`slash`);

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions;
  // we use the provided allContentfulBlogPost query to fetch the data from Contentful
  return graphql(
    `
      {
        allContentfulBlogPost {
          edges {
            node {
              id
              slug
            }
          }
        }
      }
    `
  ).then(result => {
      if (result.errors) {
        console.log("Error retrieving contentful data", result.errors);
      }

      // Resolve the paths to our template
      const blogPostTemplate = path.resolve("./src/templates/blogpost.js");

      // Then for each result we create a page.
      result.data.allContentfulBlogPost.edges.forEach(edge => {
        createPage({
          path: `/blogpost/${edge.node.slug}/`,
          component: slash(blogPostTemplate),
          context: {
                        slug: edge.node.slug,
            id: edge.node.id
          }
        });
      });
    })
    .catch(error => {
      console.log("Error retrieving contentful data", error);
    });
};
Enter fullscreen mode Exit fullscreen mode

This module exports a function called createPages. This function has two parameters, graphql and an actions object. We extract the createPage action then call the same Graphql query we ran in the GraphiQL playground earlier. We take this result and for each result (each blog post) we call the createPage function. This function accepts a config object which Gatsby reads when rendering the page. We set the path equal to the concatenated string "/blogpost" plus the slug. Notice that we also reference a template file at ./src/templates/blogpost.js, not to worry, we'll create that file soon.

At this point, kill your server and start it up again. If you enter a dud route like http://localhost:8000/some-non-existent-route/ you'll see Gatsby's development 404 page. This page has a list of all the routes and as you can see the newly created pages have been set up.

See why we chose to have a unique slug field? Each post has to have a unique route and using slugs looks so much nicer than using nonsensical ID strings in the URL. Also since Gatsby generates a static site which can have a sitemap, it's much better for SEO to have your route names match the kind of content you want to rank for.

Now we can concentrate on building out the actual pages.

Create a templates folder inside your src folder and add a file called blogpost.js. This will be our template component which will be used every time Gatsby calls the createPage function in the gatsby-node.js file.

NOTE: Be sure to restart your server at this point if you get any errors. We're doing a lot of config stuff and Gatsby may need a restart in order to run everything properly.

import React from "react";
import { Link, graphql } from "gatsby";
import Layout from "../components/layout";
import SEO from "../components/seo";

const BlogPost = ({ data }) => {
  const { title, body, image, tags } = data.contentfulBlogPost;
  return (
    <Layout>
      <SEO title={title} />
      <div className="blogpost">
        <h1>{title}</h1>
        <img alt={title} src={image.file.url} />
        <div className="tags">
          {tags.map(tag => (
            <span className="tag" key={tag}>
              {tag}
            </span>
          ))}
        </div>
        <p className="body-text">{body.body}</p>
        <Link to="/blogposts">View more posts</Link>
        <Link to="/">Back to Home</Link>
      </div>
    </Layout>
  );
};

export default BlogPost;

export const pageQuery = graphql`
  query($slug: String!) {
    contentfulBlogPost(slug: { eq: $slug }) {
      title
      slug
      body {
        body
      }
      image {
        file {
          url
        }
      }
      tags
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

At the bottom of the page, we export a Graphql query. Gatsby will run this query at runtime and will pass a data prop to BlogPost containing the Contentful data. Note that in this case we are querying a single post and passing the slug along as a filter parameter. We're basically asking for the post which matches the passed in slug (contentfulBlogPost(slug: { eq: $slug })). This slug is made available to us because we passed it in as a page context in our gatsby-config.js.

The rest is straightforward React. We create a component and using the data prop, we populate the page content. We have no styling yet but we'll get to that in a bit.

What we need now is a page to list all the available blog post pages. We cannot rely on going to the 404 page every time we need to read a blog post!

Let's head back to the blogposts.js file in the pages folder that we created at the beginning of this project and tweak it.

import React from "react";
import { Link, graphql } from "gatsby";

import Layout from "../components/layout";
import SEO from "../components/seo";

const BlogPosts = ({ data }) => {
  const blogPosts = data.allContentfulBlogPost.edges;
  return (
    <Layout>
      <SEO title="Blog posts" />
            <h1>{"Here's a list of all blogposts!"}</h1>
      <div className="blogposts">
        {blogPosts.map(({ node: post }) => (
          <div key={post.id}>
            <Link to={`/blogpost/${post.slug}`}>{post.title}</Link>
          </div>
        ))}
        <span className="mgBtm__24" />
        <Link to="/">Go back to the homepage</Link>
      </div>
    </Layout>
  );
};

export default BlogPosts;

export const query = graphql`
  query BlogPostsPageQuery {
    allContentfulBlogPost(limit: 1000) {
      edges {
        node {
          id
          title
          slug
          body {
            body
          }
          image {
            file {
              url
            }
          }
          tags
        }
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

The pattern should be familiar now.

At the bottom of the page, we export a GraphQL query. The query is the same as the one we used in gatsby-nod.js to generate the blogpost pages. It pulls all the Contentful data of the BlogPost type. Gatsby makes the result of the query available to us in the data object just as with the individual blogpost page. For this page though, we only need the id, title, slug and tags fields.

At this point, let's add some very basic styling. For the sake of this example, we'll just add a few lines to the end of the layout.css file, but in a real-world project you'd probably not want to do this. Go with whatever method you are comfortable with.

/* Add these lines to the end of the layout.css file */
@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,600");
html {
  font-family: "Open Sans";
}

header {
  /* We use !important here to override
  the inline styles just for this example.
  in production code, avoid using it where
  possible*/
  background-color: cadetblue !important;
}

header div {
  text-align: center;
}

header div h1 {
  font-weight: 600;
}

.home {
  text-align: center;
}

.home img {
  margin: auto;
}

.blogpost {
  font-size: 18px;
  width: 35em;
}

h1 {
  font-weight: 400;
  margin-top: 48px;
  font-family: "Open Sans";
}

img {
  margin-bottom: 8px;
}

.tags {
  margin-bottom: 24px;
}

.tags span.tag {
  font-weight: bold;
  margin-right: 8px;
  background: cadetblue;
  padding: 2px 12px;
  border-radius: 4px;
  color: white;
  font-size: 12px;
}

.blogpost p.body-text {
  margin-bottom: 32px;
}

p {
  line-height: 1.8;
  color: #929191;
  font-weight: 300;
}

.blogpost a {
  display: block;
  margin-bottom: 8px;
}

.blogposts a {
  display: block;
  margin-bottom: 8px;
}

footer {
  margin-top: 120px;
}

.mgBtm__24 {
  display: inline-block;
  margin-bottom: 24px;
}
Enter fullscreen mode Exit fullscreen mode

Now we have our blog, the next step is to deploy it and make it available for all the world to see. With Netlify this is super easy. Netlify integrates really well with GitHub. In your terminal, run:

    $ git init
Enter fullscreen mode Exit fullscreen mode

Go to your GitHub and create a new repo called gatsby-contentful-blog-starter, then follow the commands for pushing to an existing repository.

    $ git add .
    $ git commit -m 'initial commit'
    $ git remote add origin git@github.com:YOUR_GITUHB_USERNAME/gatsby-contentful-blog-starter.git
    $ git push -u origin master
Enter fullscreen mode Exit fullscreen mode

With your code pushed to GitHub, head over to Netlify and create an account. In your dashboard click on'New site from Git, select GitHub as a provider and go through the authorization process selecting whatever options you feel comfortable with.

Next, select a repo from the list provided. if you can't find the repo we just created, select Configure the Netlify app on GitHub. This will open a popup allowing you to choose the repo you want to authorize for use with Netlify. Follow the prompts till you find the repo. After selecting our blog project, you'll be redirected to the Netlify deploy screen and you should now be able to select the gatsby-contentful-blog-starter repo. As you can see, Netlify knows how to build Gatsby sites so you can just click the Deploy Site button at the end of the page.

Netlify runs Gatsby sites really easy with minimal configuration. Your new site should be ready in a few minutes.

Remember how we had to kill the server and restart it to fetch new data? Well we don't want to have to trigger a redeploy manually every time someone adds or changes content in Contenful. The solution to that is to use Contentful's hooks to trigger an automatic Netlify redeploy of our site (Yup, this triple tag-team thought of everything).

This means that new pages will get added to your blog automatically for each new blogpost you add. Also, if you're using the Gatsby sitemap plugin, the new pages will be included in the sitemap when it get regenerated on deployment, making it much easier to rank for keywords and help your SEO efforts with minimal fuss.

On Netify click on Site Settings and then in the left menu, select Build & Deploy. Look for the Add build hook button and click on it, giving the build hook a name (I am using "contentful") and then click on Save.

Now copy the buildhook url and go back to your Contentful dashboard. Hit the settings dropwdown and select Webhooks. The Webhooks screen already has a template for Netlify in the bottom right. Click on this.

In the form that appears, add the Netlify build hook url and click Create Webhook.

Now go back to the Content page and add a new blog post. As soon as you hit publish, Contentful will make an API call to the build hook you provided. This will in turn, cause Netlify to redeploy your site. Gatsby will pull in the Contentful data all over again, which now includes the new post you added, and create a new page based on the new blog post.

And that's it! It's been quite a journey, but now we have a working blog using three awesome tools that work so well together. From here you can add more content types and pages, expand the site or start a new project from scratch. Happy hacking!

P.S: I know this was quite a long one, and I'm glad to answer any questions you may have if you get stuck. If you do, leave me a line in the comments below or hit me up on Twitter at @thebabscraig, I'm always happy to learn together. I'm also looking to connect with other developers on Instagram, so also hit me up at @thebabscraig there too!

Top comments (12)

Collapse
 
megazear7 profile image
megazear7

Great post! I'll definitely have to run through the steps in detail as time allows and set up an example site. I did something similar with a different set of technologies but I like the auto build web hooks and such. I might try to do something similar but swap out react for the pwa-starter-kit from the polymer team and see how that goes.

Collapse
 
thebabscraig profile image
Babs Craig

Yep. Gatsby, Contenful or Netlify taken individually, can work with other technologies too. I'd love to hear how that goes. Do let me know!

Collapse
 
megazear7 profile image
megazear7

So I went all in on this. I ended up building out a static site generator called Orison on top of lit-html, which is the new Polymer templating library. It is pretty awesome to create a static site with lit-html templating, as it is so close to bare bones JavaScript, making the entire developer experience built on native web technologies even at the site generation step. I am in the process of using this approach to create multiple ecommerce sites for my clients. They are not quite ready to share but over all this JAMstack / Contentful + Netlify combo with is a great way to build websites.

Thread Thread
 
thebabscraig profile image
Babs Craig

Glad the article was able to help. Sounds like an interesting project you're working on.

Collapse
 
uptheirons78 profile image
Mauro Bono

Friend, you are great and really SAVED my day with the "webhook" explanation! I was getting crazy on how to set a proper cache plan to avoid triggering a .cache clear any time I was adding a post or modify an old one in Contentful. Thanks a lot!

Collapse
 
arberbr profile image
Arber Braja

Thank you for the post. Im currently using a setup almost the as this one for my website.

Gatsby and Netlify but the content is not pulled from Contentful or any external headless CMS. Im using local files instead in markdown as content source.

Collapse
 
theodesp profile image
Theofanis Despoudis

Ouch 39$ / month for Contentful is quite pricey. That's the cost of a Safari books Subscription or something similar. Have you considered netlifycms.org/ ?

Collapse
 
thebabscraig profile image
Babs Craig

Contentful has a pretty expansive free plan. A lot of small businesses and projects can get by on the free version for quite a while. I'd have to take a look at Netlify CMS too. Thanks!

Collapse
 
jdozierezell profile image
Jonathan

DatoCMS is also has an easy integration with Gatsby and Netlify. A little more affordable at their first paid tier as well. One of my favorite features of Contentful and Dato is that they both work with gatsby-image, which is possibly my favorite part of Gatsby.

Collapse
 
jaffparker profile image
Jaff Parker

This is great, thanks for this tutorial! I only knew about Gatsby before, but this combo is a game changer

Collapse
 
thebabscraig profile image
Babs Craig

I'm glad you found it useful. We're really lucky to have with such great tools to build with.

Collapse
 
yaron profile image
Yaron Levi

thank you! nice article