Rewriting A Static Website Using Gatsby and GraphQL - Part 1

Laurie - Feb 21 '19 - - Dev Community

Originally posted on Ten Mile Square's blog.

My personal website is implemented using Jekyll, which I liked, but it has an unfortunate initial load behavior that just does not look good or provide the user experience I want. So, I recently decided to reimplement the site, and learn GraphQL along the way.

At around the same time a friend recommended GatsbyJS. Since Gatsby is meant to work with GraphQL out of the box it was a perfect choice. The goal was that it would be easier to learn both technologies if I was working towards a final product I was already familiar with.

NextGen Generation

Jekyll generates a static site using a mixture of Markdown (or in my case, Yaml), Html/CSS and the Liquid templating engine. In my site, I use Liquid templating to create static pages that can loop through and display the content I’ve defined in my Yaml files. That content can be blog posts (like this one), details of speaking engagements, etc. I can also define a template for a section of a page, like the header, and use Yaml to define the content of that section for each page. All of this gets built using `Jekyll build` and deployed using Firebase, though that’s a topic for another post.

On the other hand, Gatsby is based on React, so it’s mostly Javascript. It seems like quite the departure, and there are some significant changes, but GraphQL and Gatsby plugins make the changes simpler. To start, Gatsby comes with some starter templates. I originally began with just the default `new Gatsby <project-name>`. However, when I discovered a pre-styled HTML5UP starter package I made a version with that instead. For me, it was beneficial to see an existing pre-styled page and start to learn from those patterns. As a general rule, I like to start with something that works and iterate to the functionality or look and feel I want.

Digging In

Part of what makes Gatsby so feature rich is the ability to add plugins. While most frameworks allow for plugins/libraries, those that exist for Gatsby are specific to that framework and decently fine-grained.

My first goal is to limit the changes I need to make to my site. Specifically, I want to keep the data defined as static yaml so that I don't have to reformat all of it. I can do that by adding a couple of plugins. The first is

{    
  resolve: `gatsby-source-filesystem`,
  options: {  
      path: `./src/data/`,
  } 
}
Enter fullscreen mode Exit fullscreen mode

This allows my code to look at files within the `src/data` directory. Now, all I have to do is take my files from the `_data` folder in the Jekyll project and drop them into the `src/data` folder in the Gatsby project.

At this point I'm able to find the files themselves, but I can't dig into the content inside them. To do that, I need to add the plugin `gatsby-transformer-yaml`. With that, my GraphQL queries can look inside the content of any yaml file in my project to query for results. I should note here that a similar plugin exists for markdown files if you would prefer that syntax.

Now that I can access the data, I need to create a query to pull the information I want from it. A typical GraphQL query looks something like this:

type Query {
     me: User
}
Enter fullscreen mode Exit fullscreen mode

In my case, using the yaml plugin, the query I need looks like this:

{
    allSpeakingYaml {
        edges {
            node {
                conference
                year
                url
                date
                location
                image
                talks {
                    title 
                    video
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this case the query starts with `allSpeakingYaml` to direct GraphQL to look for this data in the speaking.yaml file. If I wanted to query blog.yaml file, for example, I'd start the query with allBlogYaml.

Hooking It Up

One of the gotchas I discovered was the error `Unknown field allSpeakingYaml on type Query`. I saw this error a number of different times and the type of code that triggered it always seemed to be different. It took me a bit to determine what it was trying to convey. What the error really boils down to is that GraphQL is unable to find a matching result. This can mean it couldn't find a file that matched the first line, as I stumbled across when I my file was speaking.yml, no .yaml file meant the search failed. It can also be triggered by finding the file but not the associated content structure the query asks for. I again stumbled upon the error when I had forgotten to change allSpeakingYaml to allBlogYaml but had already changed the structure and keywords of the data I was looking for. Yet another way to find this error is by leaving out the system-file plugin; without it, the folder that holds my data is not visible to GraphQL and it will throw the same error.

Since I'm now error free it's time to look at the response of the query. One of the great things about Gatsby is that it comes with a browser view to test your GraphQL queries against, similar to Postman. I can use this endpoint to test out my queries and examine the structure of the response. This is important because in order to use JSX in React and display data on the web, it needs to be referenced appropriately. I'll start with querying my blog posts since it's the simplest structure. This is the response:

{ 
 "data": { 
     "allPostsYaml": { 
         "edges": [ 
           { 
             "node": { 
                 "title": "Optimistic UI vs Intuitive UX", 
                 "url": "https://tenmilesquare.com/optimistic-intuitive/" 
                }
           }, 
           { 
             "node": { 
                 "title": "Technology for the Non-Technical", 
                 "url": "https://tenmilesquare.com/for-the-non-technical/" 
                 } 
           }
        ] 
     } 
  }
}
Enter fullscreen mode Exit fullscreen mode

Mapping Array Objects

I want to put all the titles of my posts and their url links on my blog post page. In previous projects in Angular I've temporarily referenced the entire messy object and had it render with brackets and all just to make sure everything was working. Interestingly, even if I wanted to, I can't do that here, JSX doesn’t allow it and gives the error `Objects are not valid as a React child`. So that means I need to be referencing the individual string literals that are available within the response.

Looking at the response object above, the first thing to notice is that data and allPostsYaml are objects. However, within those objects are edges, which refers to an array. That means JSX needs to use map.

{data.allPostsYaml.edges.map(({ node }, index) => ({node.title}))}
Enter fullscreen mode Exit fullscreen mode

Within the map I can reference node.title, as in the example above, or node.url. That means my site will go through all of the entries in the array and display the blog post titles which is exactly what I want.

But not all of my yaml files have such simple structures. This is part of the response to my speaking object query:

{
 "data": { 
     "allSpeakingYaml": { 
         "edges": [ 
           {
             "node": { 
                "conference": "Indy Code",
                "url": "https://indycode.amegala.com/",              
                "date": "April 24-26, 2019",            
                "image": "headshot.jpg",
                "talks": [
                   {                
                      "title": "How to Talk Like an Engineer",
                      "video": null
                   }, 
                   { 
                      "title": "A Software Engineer's Guide to DevOps", 
                      "video": null
                    } 
                 ]     
             }         
         }    
     }
}
Enter fullscreen mode Exit fullscreen mode

At the top level the response looks the same as the one for blog posts. However, within the response, the key `talks` has an array value. My initial instinct was to add a map function inside the other map function and be done with it. Something like this:

{data.allSpeakingYaml.edges.map(({ node }, index) =>
    ({node.talks.map(({ talk }) =>( {talk.title} )) 
))}
Enter fullscreen mode Exit fullscreen mode

But that didn’t work. It kept saying that talk wasn’t valid. Why? Well, I had to look very closely at the response object. In our previous example `edges` is a key that references an array object, just like `talks` is in this example. In both cases, within the arrays, there are objects that don't have a key reference. However, in the `edges` example, those objects have another object inside, `node`. So we're referencing an object and looking at its attributes. In the case of `talks`, there are only keyed attributes, so we are able to reference them directly, like this:

{data.allSpeakingYaml.edges.map(({ node }, index) =>
    ({node.talks.map(({ title, video }) => ( {title} )) 
))}
Enter fullscreen mode Exit fullscreen mode

I'll admit that I still expected to assign a reference key to each object in `talks` and access the `title` and `video` data as attributes. However, we didn't do that to the `edges` array, we went straight to referencing `node`. So this is the same.

More To Come

At this point I have moved over my site data and made it accessible and viewable on the site's pages. That’s a good first step, but there is still lots to do. In my next post I’ll discuss removing some of the liquid templating instructions left over from my Jekyll implementation.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .