Build dynamic blog with Gatsby

January 24 2018

GatsbyReactReduxGraphQLMaterialUIMarkdown

Gatsby is super good with netlify and contentful. With these tools, we could build a powerful CMS. When we say CMS, we usually mean traditional CMSs like WordPress or Drupal. But it's 2018 now and to work with modern tools like React, Gastby and GraphQL, we need a more powerful 'CMS' in the workflow. That's the combination of Contentful and Netlify.

Contentful is a content infrastructure, while Netlify is a platform for automating projects using modern techs. They together, become a much powerful management system.

Let me show you how to set them up and let the blog project goes alive.

Netlify

A workflow combines global deployment & CI & https. Whenever you commit the repository that Netlify bind with, or modify contents on contentful, it handles from there.

Netlify will automatically trigger the CI to build, test and publish. The process is pain-free and seems like heroku but for static site.

Netlify provide easy setup, free tier and powerful services, such as SSL, advanced CDN, load balancing, DDoS protection...

Install plugin

npm install --save gatsby-source-contentful
plugins: [
  // make sure to put last in the array
  {
    resolve: `gatsby-plugin-netlify`,
    options: {
      headers: {}, // option to add more headers. `Link` headers are transformed by the below criteria
      allPageHeaders: [], // option to add headers for all pages. `Link` headers are transformed by the below criteria
      mergeSecurityHeaders: true, // boolean to turn off the default security headers
      mergeLinkHeaders: true, // boolean to turn off the default gatsby js headers
      mergeCachingHeaders: true, // boolean to turn off the default caching headers
      transformHeaders: (headers, path) => headers, // optional transform for manipulating headers under each path (e.g.sorting), etc.
      generateMatchPathRewrites: true, // boolean to turn off automatic creation of redirect rules for client only paths
    },
  },
];

Link with repository

Once registered on Netlify, we are able to authorize Netlify to access our github.

Just choose the blog project repository, Netlify will detect and run build scripts from project root.

Build logs are under the deploys section for debug.

Domain

If successfully deployed, Netlify will provide a free sub-domain, which is long and ugly. You may use it for preview and debug, but I suggest you to buy a domain and bind it with your project.

For example, I have my domain cong-li.com hosted by google, and there are several sub-domains under it because some of my web apps are in google app engine and I use those sub-domains as entry-points.

So in my domain DNS configuration, I create a CNAME record like this

blog CNAMA 1h xxxx.netlify.com

Under netlify/settings/domain management, I add custom domain to my newly created subdomain log.cong-li.com.

Netlify also provides free HTTPS, please enable it, wait few minutes, go to your new blog site to check if it's updated.

Be aware

  1. I have a method that validate if a button's target path === current path, an error says 'window' is not defined while deploying. Reason is that when the server renders, it may be not in a browser so the window doesn't exist. One way to get it work is to put it inside ComponentDidMount. My situation is more complex, so I found another solution => Gatsby uses react-router that provides an injected property in route component (normally the layouts/index). And this object is never mutated, which means we could pass it to any child components and access it through this.props.location.

Contentful

The content handling I planned was just use markdown files and upload them to github manually, so that I don't need to worry if I need backup.

But after some reading, I found Contentful's content api and infrastructure are pretty awesome. So why not pick the best option and learn one more new skill?

Setup

after registered on contentful, add a new 'space', then go to space settings/API to create new Space ID & Content Delivery API. In Content Model, you could set up the data model for current space. This is the place we structure everything in the data sending to blog. So create your own model, and replace the frontmatter with them.

read more

Install plugins

npm install --save gatsby-source-contentful

Replace the value below with the id and token we got in setup.

// In your gatsby-config.js
plugins: [
  {
    resolve: `gatsby-source-contentful`,
    options: {
      spaceId: `your_space_id`,
      accessToken: `your_access_token`,
    },
  },
];

Refactor

To fetch data through Contentful's api is async, so we need to refactor the createPages method in gatsby-node.js using promise or async/await.

const path = require (`path`);
const {createPage} = boundActionCreators;
exports.createPages = ({boundActionCreators, graphql}) => {
	return new Promise ((resolve, reject) => {
		const postTemplate = path.resolve ('src/templates/postTemplate.js');
		resolve (
			graphql (`{
					allContentfulPost{
						edges{
							node{
								id
                slug
							}
						}
					}
				}`,
			).then ((result) => {
				if (result.errors) {
					console.log (result.errors);
					reject (result.errors);
				}
				result.data.allContentfulPost.edges.forEach ((edge) => {
					createPage ({
						path : edge.node.slug,
						component : postTemplate,
						context : {
							slug : edge.node.slug,
						},
					});
				});
				return;
			}),
		);
	});
};

Test in GraphQL debugger and make sure the data could be accessed by GraphQL query.

Gatsby's structure allows layouts/pages (templates) to query GraphQL, so go to each file and modify the query code to match the fields you set in Contentful. Especially map with values in frontmatter we set before. Because we no longer save Markdown file locally any more.

Credential

This is a common but necessory step in development, all the code for front-end is client-visiable, you don't want the API keys to be shown in your repository. So environtment variables and .gitignore file are needed to replace the hard-coded API info when install Contentful plugin. We need to import credentials based on current node environment. Use API keys directlly if environment is 'developemnt' and use environment variables that hold API keys if environment is 'production'. And add the file contains 'development' keys in .gitignore.

config/
└── credentials/
      ├── index.js
      ├── development
      └──  production
const devKey = require ('./development');
const prodKey = require ('./production');

if (process.env.NODE_ENV === 'production') {
	module.exports = prodKey;
} else {
	module.exports = devKey;
}
// development
module.exports = {
	CONTENTFUL_SPACE_ID : 'xxxxxxxx',
	CONTENTFUL_ACCESS_TOKEN : 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}

// production
module.exports = {
	CONTENTFUL_SPACE_ID : process.env.CONTENTFUL_SPACE_ID,
	CONTENTFUL_ACCESS_TOKEN : process.env.CONTENTFUL_ACCESS_TOKEN
}

Add 'config/credentials/development' in .gitignore so that it will not be commited to repository, and replace the value in 'gatsby-confit'.

{
			resolve : `gatsby-source-contentful`,
			options : {
				spaceId : require ('./config/credentials').CONTENTFUL_SPACE_ID,
				accessToken : require ('./config/credentials').CONTENTFUL_ACCESS_TOKEN,
				//host: `preview.contentful.com`,
			},
		},

In Netlify/Settings/Build&DEploy, add those variables manually. Now when others view your source code, they can only find 'CONTENTFULSPACEID' instead of its real value that saved as environment variables in Netlify server.

Webhook

Now everytime when you commit your project, Netlify will detect and run build script, during the deploy process it will fetch all the data from Contentful first so that GraphQL could structure the date schema so that React components are able to receive data through props.

Is it done? Well, not yet. The process sounds good enough, but it's not easy enough for people to use -> they have to commit repository everytime when there's a change in content from Contentful.

What if we automate this step, so that everytime when someone changes the content in Contentful, it will trigger Netlify to rebuild with the newest data fetched from Contentful? This is the purpose of webhook.

Webhook is a method with arguments that subscribed to certen events to alter your site with custom callbacks. We can use it to let Netlify and Contentful commicate with each other.

In Netlify/Settings/Build&Deploy, under the environment variables section, create a hook with the name you like, copy the generated url. Then in Contentful/space settings/webhooks, create a hook, past the url.

Now make some changes in Contentful, then monitor the deploy log in Netlify for few minutes to make sure they are connected through webhook.