HowTo Gatsby + NetlifyCMS static and dynamic pages
Tags: jamstack, gatsby, netlify, cms
Published: 2020-07-02 22:18:36

---
title: HowTo Gatsby + NetlifyCMS static and dynamic pages
published: true
description: Creating static and dynamic pages using Gatsby and Netlify CMS.
tags: jamstack, gatsby, netlify, cms
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/xyi0lhgx901hn99hvn42.png
---

Our Website and some other websites we've built in JAMstack has been with the stack of: GitLab, Netlify, Gatsby and Netlify CMS. This allow us to build a website with React Components with content sourced in markdown files, managed by Netlify CMS and autodeployed using the Netlify service.

## Our JAMstack

JAMstack stands for JavaScript, APIs, and Markup. The term was coined by Mathias Biilmann to describe a modern web development architecture based on client-side JavaScript, reusable APIs, and prebuilt Markup.

### Netlify CMS

Netlify CMS is an open source content management system for your Git workflow that enables you to provide editors with a friendly UI and intuitive workflows. You can use it with any static site generator to create faster, more flexible web projects. Content is stored in your Git repository alongside your code for easier versioning, multi-channel publishing, and the option to handle content updates directly in Git.

You may read more in the [official documentation](https://www.netlifycms.org/docs/intro/).

### Gatsby

Gatsby is a free and open source framework based on React that helps developers build blazing fast websites and apps. Forget complicated deploys with databases and servers and their expensive, time-consuming setup costs, maintenance, and scaling fears. Gatsby.js builds your site as “static” files which can be deployed easily on dozens of services.

You may read more in the [official documentation](https://www.gatsbyjs.org/docs/).

## Configuring Gatsby with Netlify CMS

### Add the dependencies

First we need install some dependencies to make them work together

```bash
npm install --save netlify-cms-app gatsby-plugin-netlify-cms netlify-cms-app
```

### Configuration for Netlify CMS

We need to create a file that tells to Netlify CMS which is our structure of data and where is stored, along with some other configuration. The file must be at `static/admin/config.yml`

```bash
├── static
│   ├── admin
│   │   ├── config.yml
```

```yaml
backend:
  name: gitlab
  repo: alfchee/alfchee-site
  branch: master

media_folder: "static/assets"
public_folder: assets
slug:
  encoding: "ascii"
  clean_accents: true
  sanitize_replacement: "_"

collections:
  - name: 'pages'
    label: 'Pages'
    folder: 'content/pages'
    create: true
    editor:
      preview: false
    fields:
      - { label: "Title", name: "title", widget: "string" }
      - { label: "Description", name: "description", widget: "string" }
      - {
          label: "Post Image",
          name: "featuredImage",
          widget: "image",
          allow_multiple: false,
        }
      - { label: "Template Key", name: "templateKey", widget: "string" }
      - { label: "Body", name: "body", widget: "markdown" }
```

With the previous we are configuring our backend to be GitLab and setting the repository name and the branch where Netlify CMS will be pushing changes.

Next we use `media_folder` and `public_folder` to tell where our assets will be stored when we upload files with the CMS, this come handy because we may specify the same directory used by Gatsby.

Then the Slug section allow us to create rules about how to create the slugs for the pages.

Lastly, in the section of `collections` we may create as many collections we whish, and define the structure of data for them, in this way the CMS will show many fields as we specify. In this example we are creating a collection of pages, where they'll be stored as markdown files in the directory `content/pages`, creating one file per page created. Then forl all the pages, the list of fields is declared. You may read more about the [definition of collections](https://www.netlifycms.org/docs/collection-types/) and the [available widgets](https://www.netlifycms.org/docs/widgets/) to set the fields.

### Accessing the content from Gatsby

Now that you have set your data and schema, you need to fetch it so can be rendereable, for so we need to make some configurations in Gatsby.

### Dependencies

Make sure you have the next dependencies installed, they are Gatsby Plugins

```bash
npm install --save gatsby-source-filesystem gatsby-transformer-remark gatsby-remark-images gatsby-remark-responsive-iframe gatsby-remark-prismjs gatsby-remark-copy-linked-files gatsby-remark-smartypants gatsby-transformer-sharp gatsby-plugin-sharp prismjs gatsby-image 
```

### Configuring Gatsby

The Configuration of Gatsby is in the file `gatsby-config.js`, and in that file we'll configure the source of data and also the plugins we've installed.

```javascript
module.exports = {
  /* Your site config here */
  plugins: [
    /* Pages */
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/pages`,
        name: `pages`,
      },
    },
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: 590,
            },
          },
          {
            resolve: `gatsby-remark-responsive-iframe`,
            options: {
              wrapperStyle: `margin-bottom: 1.0725rem`,
            },
          },
          `gatsby-remark-prismjs`,
          `gatsby-remark-copy-linked-files`,
          `gatsby-remark-smartypants`,
        ],
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-netlify-cms`,
      options: {
        enableIdentityWidget: false,
      },
    },
    "gatsby-plugin-netlify",
  ]
}
```

Each time you add a new directory with content that you need to be inspected by Gatsby then you'll need to add a block for `gatsby-source-filesystem` and setting the path. But the markdown files are just text files, in order they can be processed correctly you need the plugin `gatsby-transformer-remark`, that accepts some plugins to format the content, for so all the we've added in our configuration.

Then you can see that both plugins of Netlify has been added. The option `enableIdentityWidget` is required to set to `false` because we are using GitLab.

## Static Pages in Gatsby

As you know, if you add files to `src/pages` directory, Gatsby will try to render them as pages and create routes. For so `src/pages/index.jsx` will be treated as the home page, and in the same way you can continue adding more static pages with static routes.

From a static page if you want to get information of your markdown files, you'll use GraphQL to query the data you need. For so you'll be using some objects provided by Gatsby. Example:

```javascript
import { graphql } from "gatsby"
import React from "react"

class MyComponent extends React.Component {
  render() {
    // obtaining from props the data queried with GraphQL
    const page = this.props.data.markdownRemark
  }
}

export const query = graphql`
  query MyPage{
    markdownRemark({ WRITE YOUR QUERY }) {
      id
      html
      frontmatter {
        ANY OTHER FIELDS DEFINED
      }
    }
  }
`

export default IndexPage
```

### Dynamic Pages with Gatsby

Using the previous method we need to create  new JS or JSX file for each page, and make the the same things once and another time. To avoid that we are going to dynamically create the pages, and to be able to do that we'll be using:
* Layout, defining one or more layouts that will define shared behavior for all the site
* Templates, defining shared behaviors or elements to be displayed in dependence of the page
* Edit `gatsby-node.js`, in this file we can use some hooks that allow us to create the pages dynamically

#### Install dependencies

```bash
npm install esm
```

### Content for gatsby-node.js

If this file doesn't exist, create it or edit the content

```javascript
require = require("esm")(module)
module.exports = require("./gatsby-node.esm.js")
```

Now we need to create the file `gatsby-node.esm.js` and add the content

```javascript
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `MarkdownRemark`) {
    const value = createFilePath({ node, getNode })
    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

const buildPages = (edges, createPage) => {
  const pages = edges.filter((edge) => {
    if ("templateKey" in edge.node.frontmatter) {
      return edge
    }
  })

  // create pages with the filtered edges
  pages.map((page) => {
    console.log(`Creating page ${JSON.stringify(page.node.frontmatter)}`)

    createPage({
      path: page.node.fields.slug,
      // getting the component to render the page using the templateKey property
      component: path.resolve(
        `src/templates/${String(page.node.frontmatter.templateKey)}.js`
      ),
      context: {
        id: page.node.id,
        slug: page.node.fields.slug,
      },
    })
  })
}

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions

  // graphql query for pages only
  const result = await graphql(
    `
      {
        allMarkdownRemark(
          limit: 1000
          filter: { fileAbsolutePath: { regex: "/(pages|blog)/i" } }
        ) {
          edges {
            node {
              id
              fields {
                slug
              }
              frontmatter {
                title
                templateKey
              }
            }
          }
        }
      }
    `
  )
  // if errors then throw
  if (result.errors) {
    throw result.errors
  }
  console.log("Creating pages ---->>>>")
  // Create pages
  buildPages(result.data.allMarkdownRemark.edges, createPage)
}
```

The exposed function `createPages` will create dynaic pages according our needs. Using GraphQL we are querying files that has the word `page` or `blog` in the path (not file name), and we are interested in the values `id`, `slug`, `title`, and `templateKey`.

We defined the logic in the function `buildPages` to be able to handle more easy, we may create a function `buildBlog` to create only blog post pages with a different route. The interesting here is that `createPage` function needs the slug, which is the route for the URL and the component used to render this page, and is that the reason of the `templateKey` field, that we are using it to determine the file to use as template. So for a `default` `templateKey`, the file to use as template is `src/templates/default.js`.

The context is also important, so we make sure to pass the `id` and `slug` as context, because we can use them to query the content of the page we want to render.

### Creating Templates

We may create many templates as we need to display or hide components or data in the dynamic pages, and we are going to define a default template. Create the file `src/templates/default.js` with the content

```javascript
import React from "react"
import { graphql } from "gatsby"
import { Container } from "react-bootstrap"

import Layout from "../components/Layout"
import NavBar from "../components/NavBar"
import Footer from "../components/Footer"
import styles from "./default.module.css"

class PageDefaultTemplate extends React.Component {
  render() {
    const page = this.props.data.markdownRemark
    const meta = {
      pageTitle: page.frontmatter.title,
    }

    // TODO: Get the navbar items

    return (
      <Layout siteMeta={meta}>
        {/* Page content */}
        <section className={styles.sectionTerms}>
          {/* Navbar */}
          <NavBar />
          {/* end of Navbar */}

          <Container className={styles.contentData}>
            <header>
              <h1 className={styles.title}>{page.frontmatter.title}</h1>
            </header>

            <article
              className={styles.dataInfo}
              dangerouslySetInnerHTML={{ __html: page.html }}
            />
          </Container>

          {/* Footer */}
          <Footer />
        </section>
        {/* end of Page content */}
      </Layout>
    )
  }
}

export default PageDefaultTemplate

export const pageQuery = graphql`
  query PageBySlug($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      id
      html
      frontmatter {
        description
        featuredImage
        title
        templateKey
      }
    }
  }
`
```

As seeing, the `slug` passed as context is used here to make a query in the `markdownRemark` collection to get the content of the page in which we are navigating, and then we can use it as we whish.

### Layout component

Is good to have a layout component that handles SEO among other stuffs, but remember to have this kind of content in your layout

```javascript
const Layout = ({ children, siteMeta }) => {
  const { pageTitle } = siteMeta

  return (
    <>
      <Seo pageTitle={pageTitle} />

      {/* main container for all the pages content */}
      <div className="main-container">{children}</div>
    </>
  )
}

Layout.propTypes = {
  children: PropTypes.node.isRequired,
  siteMeta: PropTypes.object,
}
```