Sourcing content in Gatsby

Sourcing content in Gatsby.js

What’s Gatsby?

Gatsby.js is a powerful static site generator (with dynamic capabilities) which can be used to build super performant web-sites. It has a very rich plug-in functionality and is perfect for your next personal blog, product landing, portfolio page or small e-commerce app.

Sourcing content

It’s fairly evident that when you build your web-site, apart from business logic, performance, security and stylings you care about actual content presented to an end user.

The case may be fairly simple: let’s say you have a product page with sections that need to be edited by marketing team which doesn’t want to edit those fancy h1’s and p’s in the code editor.

Another scenario may be personal-blog page which has lots of posts, with each post having its own title, content, and tons of other information that you might want to display.

Thanks to loads of plugins written by community and Gatsby maintainers, we are lucky to choose form range of options to get our content on the page. In this blog post I want to cover some of them and try to provide my opinion on easiness of use, developer experience and general pros and cons of using them.

Sourcing from project folder with gatsby-source-filesystem and gatsby-markdown-remark

One of the easiest ways to grab our content is to source it directly from our project folder. We can grab assets like images, and more complicated content types like blog posts written in markdown.

Scenario 1: Access images from assets folder to display them on the page

First we need to install gatsby-source-filesystem and set it up in gatsby-config.js.

npm install gatsby-source-filesystem

In gatsby-config.js:

gatsby-config.js
js
1...
Copy
Copy
2{
3 resolve: `gatsby-source-filesystem`,
4 options: {
5 path: `${__dirname}/content/images`,
6 name: `images`,
7 },
8 },
9 ...

With the lines above we are telling Gatsby that we want to allow GraphQL to query all the insides of assets folder of our project located at specified path

Now that plugin is prepared we can actually query our assets folder with the following graphQL query( sourceInstanceName is a filter parameter that corresponds to the name in the config above):

1const Query
Copy
=
Copy
graphql`
Copy
2 images: allFile(filter: { sourceInstanceName: { eq: "images" } }) {
3 edges {
4 node {
5 childImageSharp {
6 fixed(width: 50, height: 50) {
7 ...GatsbyImageSharpFixed
8 }
9 }
10 }
11 }
12 }
13 ...
14 }
15`

Note that to be able to use images returned by this query inside component rather than inside of page we need to use StaticQuery available from Gatsby.

staticquery.jsx
jsx
1<
Copy
StaticQuery
Copy
2 query={Query}
3 render={data => {
4 const images = data.images.edges.map(image => (
5 <Image
6 fixed={image.node.childImageSharp.fixed}
7 alt={author}
8 style={{
9 marginRight: rhythm(1 / 2),
10 marginBottom: 0,
11 minWidth: 50,
12 borderRadius: `100%`,
13 }}
14 />
15 ))
16 const { author, social } = data.site.siteMetadata
17 return (
18 <div
19 style={{
20 display: `flex`,
21 marginBottom: rhythm(2.5),
22 }}
23 >
24 {images}
25 <p>Queried from the inside of Bio.js component with StaticQuery</p>
26 </div>
27 )
28 }}
29/>

StaticQuery accepts query prop where we can use our GraphQL query from above and render prop which renders whatever we feed to it having an access to data which is nothing more than wrapper for our queried files.

If you are querying the same images but want to use them inside of one of your pages you can access it directly from props.data

Scenario 2: Access one particular image to display it on the page

To access one particular image by its name we have to adapt our GraphQL query a little bit, otherwise we can use it in the ways described above in first scenario by using StaticQuery in component and props.data in page.

Let’s specify absolute path to the file and use regex to cherry pick wanted image.

1const Query
Copy
=
Copy
graphql`
Copy
2 query {
3 avatar: file(absolutePath: { regex: "/profile-pic.jpg/" }) {
4 childImageSharp {
5 fluid(maxWidth: 400, maxHeight: 250) {
6 ...GatsbyImageSharpFluid
7 }
8 }
9 }
10 ...
11 }
12`

Scenario 3: Access blog post written in markdown together with its frontmatter

As Gatsby is often used as a blog template it offers a very convenient way of working with blogposts written in markdown. To access markdown posts we first need to tweak our config a little bit so that Gatsby knows where our markdown files live.

We use gatsby-source-filesystem to achieve that:

gatsby-config.js
js
1{
Copy
Copy
2 resolve: `gatsby-source-filesystem`,
3 options: {
4 path: `${__dirname}/content/blog`,
5 name: `blog`,
6 },
7 },
8 ...

To be able to work with markdown files in a really convenient way we also need to set up gatsby-transformer-remark plugin. Note how we add other plugins inside of gatsby-transformer-remark like gatsby-remark-images or gatsby-remark-prismjs. Those are here so we are able to directly embed images into our markdown and highlight code chunks with prismjs respectively.

gatsby-config.js
js
1{
Copy
Copy
2 resolve: `gatsby-transformer-remark`,
3 options: {
4 plugins: [
5 {
6 resolve: `gatsby-remark-images`,
7 options: {
8 maxWidth: 590,
9 },
10 },
11 {
12 resolve: `gatsby-remark-responsive-iframe`,
13 options: {
14 wrapperStyle: `margin-bottom: 1.0725rem`,
15 },
16 },
17 `gatsby-remark-prismjs`,
18 ],
19 },
20 },
21 ...

With all of the above set up we can now query our markdown posts with query (we can conveniently use sort to get our blog posts in the chronological order and filter to be sure that we query only those markdowns which are located in blog folder of our project):

1export const pageQuery
Copy
=
Copy
graphql`
Copy
2 query {
3 ...
4 allMarkdownRemark(
5 filter: { fileAbsolutePath: { regex: "/blog/" } }
6 sort: { fields: [frontmatter___date], order: DESC }
7 ) {
8 edges {
9 node {
10 excerpt
11 id
12 html
13 fields {
14 slug
15 }
16 frontmatter {
17 date(formatString: "MMMM DD, YYYY")
18 title
19 }
20 }
21 }
22 }
23 }
24`

We already know that we can now access our markdown blog posts in any page just via this.props.data.allMarkdownRemark.edges, map trough them and display all the necessary data generated for us by plugin.

For example we have access to frontmatter which is nothing more than JSON-like structure that we include in our markdown.

Here is a quick example:

1---
Copy
2title: Hello World
3date: '2015-05-01T22:12:03.284Z'
4---
5
6This is the first paragraph of our blog written in markdown and above is frontmatter wich can include any data you pass there

We have included title and date, but you can feel free to add any other parameter that you want to be accessible from the query(like tags in a form of array)

1---
Copy
2title: Hello World
3date: '2015-05-01T22:12:03.284Z'
4tags: ["svg", "css", "react"]
5---

Headless CMSs

Sometimes its not really convenient to change all the content types like images or blog posts in code editor. Moreover you final user may not be aware of how to navigate trough code and may require a more straightforward solution.

This is where headless CMS comes into play. Imagine a scenario where you make a static product page with Gatsby and pass it to the marketing department that is responsible for copywriting and images on the page. You built it with code - they interact with a user-friendly UI that makes it easy to change any content. Awesome!

Let’s explore how we would do it with Gatsby.js!

Sourcing from Contentful

To be able to source something from Contentful you will need an account at https://www.contentful.com/. After registration you will get a simple example project that we will use for learning purposes.

For now lets start with installing gatsby-source-contentful and adding it to our config.

npm install --save gatsby-source-contentful

In gatsby-config.js we need to add the plugin and provide our spaceId and accessToken which both can be found in the settings -> API keys of our project dashboard:

An image

gatsby-config.js
js
1{
Copy
Copy
2 resolve: `gatsby-source-contentful`,
3 options: {
4 spaceId: `your_space_id`,
5 accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
6 },
7 },

Note that it’s not a good idea to expose your accessToken to the config file directly so it can be visible by everyone on GitHub. For the training purposes I will include it in the code for this tutorial but try to use environment variables to protect your keys as it can be seen in the example above. If it’s your first time seeing environment variable term don’t worry, you can grasp the concept from this post .

Before we move any further I want to show you, how we can resolve a little conflict coming from the fact that some Contentful files are treated as markdown by Gatsby. Our gatsby-node.js is responsible for programmatically creating pages from our markdown posts which are situated in blog folder and by default it uses allMarkdownRemark query, which would source also Contentful markdown which we don’t need. Let’s adapt our query to source only those files which are located in our project folder:

In gatsby-node.js we have added filter and set it to /blog/:

gatsby-node.js
js
1...
Copy
Copy
2
3exports.createPages = ({ graphql, actions }) => {
4 const { createPage } = actions
5
6 const blogPost = path.resolve(`./src/templates/blog-post.js`)
7 return graphql(
8 `
9 {
10 allMarkdownRemark(
11 filter: { fileAbsolutePath: { regex: "/blog/" } }
12 sort: { fields: [frontmatter___date], order: DESC }
13 limit: 1000
14 ) {
15 edges {
16 node {
17 fields {
18 slug
19 }
20 frontmatter {
21 title
22 }
23 }
24 }
25 }
26 }
27 `
28
29...

Now we are ready to source our content from Contentful. In a new page named contentful.js we first want to query our assets that Contentful created for us. At the time being we have one particular interesting content type called Course which has all the necessary items for us to train.

An image

It is straightforward to query Contentful assets with GraphQL and all we need to do to get all the entries that are of type Course is to run allContentfulCourse query, you may already guess that we can query yourCustomType of content with allContentfulYourCustomType(note how we filter our courses on language basis, otherwise we would get duplicates of every course in language specified in Contentful - it’s quite specific to this case, because every course has translation):

1export const pageQuery
Copy
=
Copy
graphql`
Copy
2 query {
3 allContentfulCourse(filter: { node_locale: { eq: "en-US" } }) {
4 edges {
5 node {
6 title
7 duration
8 shortDescription
9 image {
10 sizes(maxWidth: 1280) {
11 ...GatsbyContentfulSizes
12 }
13 id
14 }
15 }
16 }
17 }
18 }
19`

From exploring our content on Contentful we can see that each Course has a title, duration, short description and image. We have included those in our query and now can access them in our component via this.props.data.

An image

contentful.jsx
jsx
1class
Copy
Copy
ContentfulPage
Copy
Copy
extends
Copy
Copy
React
Copy
.
Copy
Component
Copy
Copy
{
Copy
Copy
2 render() {
3 const courses = this.props.data.allContentfulCourse.edges
4 return (
5 <Layout location={this.props.location}>
6 <Link to="/">Back</Link>
7 {courses.map(course => {
8 return (
9 <React.Fragment>
10 <h3>{course.node.title}</h3>
11 <Image sizes={course.node.image.sizes} />
12 <div>{course.node.duration}</div>
13 <p>{course.node.shortDescription}</p>
14 </React.Fragment>
15 )
16 })}
17 </Layout>
18 )
19 }
20}

Recap

In this small tutorial you have learned several ways of sourcing different content types in Gatsby and combine them in a single project avoiding possible sourcing conflicts by precisely specifying what we want to query from what source.


Thanks for reading! I hope you’ve enjoyed reading this post as much as I’ve enjoyed writing it! If you have any questions or want to bring up a discussion don’t hesitate to reach out to me on twitter. I would be happy if you hit that follow button not to miss any posts that I will be releasing in the future 😄

As always you can find the code for tutorial here on github


Comments section

Dimitri Ivashchuk

Written by Dimitri who lives and works in Vienna building useful things. He is passionate about modern web technologies and enjoys learning something new everyday as well as sharing his knowledge with others.

Front-end engineer @CubeTech
Instructor @egghead.io