Render Dynamic Pages in Gatsby Using File System Route API and YAML
Tags: tutorial, gatsby, react, javascript
Published: 2021-01-12 18:09:12

I've been using [Gatsby](https://www.gatsbyjs.com) a bunch lately. I think it's an enjoyable framework and a great way to ramp up on React. This year, one of my goals is to get more comfortable in the frontend stack and become more proficient with JavaScript.
Gatsby has been a fantastic tool for helping me work towards that goal.

While doing the [#100DaysOfGatsby challenege](/100-days-of-gatsby), I had to create dynamic pages. I decided to use a YAML file as my source of truth for the dynamic generation. Let's build a simple Gatsby app that generates some dynamic pages from a YAML file.

## Okay, but why would I want to do that?
Before diving into the _how_ of this tutorial, I'd like to discuss the motivations behind it; meet DOGWALKER. DOGWALKER is the newest application for finding someone to walk your dog. There need to be three pages for the application's initial release: Home, About, and Locations. There needs to be a list of all the locations that DOGWALKER is currently available on the locations page, and each location in that list will link to their own individual locations page.

<!--more-->
For example, DOGWALKER is available in Jacksonville, Tampa, and Orlando. The locations page would have a list containing those three cities. Each city name links to its page. Nothing except the city name and description will be on each page, and this is an elementary proof of concept.

Theoretically, dynamic pages aren't needed. One could update the locations page and create a page for each new location. Manually adding pages is very tedious. It means that anytime the location template might change, say when the application moves from proof of concept into its first alpha release, the developer would need to change every single location page.

 A developer could make their life a lot easier by having all the data stored in one place, in this case, it's a YAML file, and then have reusable templates. Each time the requirements for a location page change, the only update that needs to happen is in the location page template.

Have I convinced you that dynamic routing is worth learning? Let's jump in.

## Creating a Gatsby Project
First things first, get Gatsby installed on your machine (you'll need node, npm, and git installed already if you're not sure how [follow this guide](https://www.gatsbyjs.com/docs/tutorial/part-zero/) to get your environment set up.)

```bash
npm install -g gatsby-cli
```

Now the `gatsby` command will be available to use, and this is how to generate your gatsby projects.

```bash
npm init gatsby
```
Follow the prompts. We won't need a CMS, any styling, or any additional plugins.

After the Gatsby site is created, change into the directory, mine is named `dogwalker`.

```bash
cd dogwalker
```

Get the site up locally with

```bash
npm run develop
```

In your browser, go to `localhost:8000` to see the Gatsby congratulations page.

## Making the DOGWALKER Proof of Concept

The proof of concept for DOGWALKER is three pages. Home, About, and Locations. Let's go ahead and remove the code from the index file so we can start fresh. Inside of `src/pages/index.js`, Replace everything with the code below.

```js
import React from "react"

export default () => {
  return (
    <>
    <h2>DOGWALKER</h2>
    <p>This is the dogwalker homepage.</p>
    </>
  )
}
```

Let's add a link to our About page. We'll call it `about.js`.

```js
import React from 'react'

export default () => (
  <>
  <h1>About DOGWALKER</h1>
  <p>We're somehow different than all the other dogwalker applications.</p>
  </>
)
```

Now, in your browser, go to `localhost:8000/about`, and you will see the about page we just created. We haven't linked it from the homepage, but we will get to that shortly.

Let's create our locations page now. The actual page that will be the list of our locations can also live inside `src/pages`, we will call it `locations.js`. There will be no dynamic content. We will include a header that says Location for the time being.

```js
import React from 'react'

export default () => (
  <>
  <h1>Locations</h1>
  <p>Check out where you can use DOGWALKER</p>
  </>
)
```

Go to `localhost:8000/locations` to see the locations page.

If you're feeling lost, check out [this branch](https://github.com/meaganewaller/dogwalker/tree/initial-static-pages) to see the changes just for the above static pages.

## Adding locations statically

Let's add the locations statically; this will help us determine what we can dynamically generate. Creating static pages first will also drive home how good dynamic rendering is for this type of content.

The first location DOGWALKER was available in was Jacksonville, so let's create the Jacksonville page. I'm going to put mine at `src/pages/locations/jacksonville.js`.

```js
import React from 'react'

export default () => (
  <>
  <h1>Jacksonville</h1>
  <p>Yep, we started in Jacksonville, FL of all places.</p>
  </>
)
```

Visit this page at `localhost:8000/locations/jacksonville` to view the page we just created.

Repeat the process for Tampa and Orlando.

`src/pages/locations/tampa.js`
```js
import React from 'react'

export default () => (
  <>
  <h1>Tampa</h1>
  <p>We're also in Tampa.</p>
  </>
)
```

`src/pages/locations/orlando.js`
```js
import React from 'react'

export default () => (
  <>
  <h1>Orlando</h1>
  <p>We're also in Orlando.</p>
  </>
)
```
Once again, visit these pages at `localhost:8000/locations/tampa`, and `localhost:8000/locations/orlando`.

Now we can add our locations to our static locations page.

`src/pages/locations.js`
```js
import React from 'react'
import { Link } from 'gatsby'

export default () => (
  <>
  <h1>Locations</h1>
  <p>Check out where you can use DOGWALKER</p>

  <ul>
    <li>
      <Link to="/locations/jacksonville">Jacksonville</Link>
    </li>
    <li>
      <Link to="/locations/tampa">Tampa</Link>
    </li>
    <li>
      <Link to="/locations/orlando">Orlando</Link>
    </li>
  </ul>
  </>
)
```
(Read about [Gatsby's Link component here](https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-link/))

Okay, so this works. All our locations are linked; they live under the locations URL. What more could you want? Imagine six months down the line when DOGWALKER blows up and is available in 120 cities. Do you want to create 120 pages that all look the same except for the title and the city's description? I didn't think so. Let's generate these pages dynamically.

[View the code at this point here.](https://github.com/meaganewaller/dogwalker/tree/create-static-locations)

## Create & Query Locations

Let's create those dynamic pages. First, we need to create a YAML file to store all of our locations. YAML isn't the only option for storing our source of truth for the dynamic pages, but it's just the format I've chosen to work in for this example.

Each location page has a city name and a description. My YAML file is going to live at `locations/locations.yml.`

```yaml
- name: "Jacksonville"
  description: "Yep, we started in Jacksonville, FL of all places."
- name: "Tampa"
  description: "We're also in Tampa."
- name: "Orlando"
  description: "We're also in Orlando."
```

Next, we need to tell gatsby about our YAML file. We need to install the `gatsby-transformer-yaml` package.

```bash
npm install gatsby-transformer-yaml
```



Now, inside of `gatsby-config.js` we can configure it, and we can also use `gatsby-source-filesystem` to tell Gatsby where the YAML file lives.

```js
module.exports = {
  plugins: [
      {
        resolve: "gatsby-transformer-yaml",
        options: {
            typeName: ({node}) => {
            const name = node.sourceInstanceName
            if (name === `locations`) {
              return `Location`
            }
            return name
          },
        },
      },
      {
        resolve: "gatsby-source-filesystem",
        options: {
            path: "./locations",
            name: "locations",
        },
        __key: "locations",
      },
  ],
}
```

Let's open up our GraphQL playground and make sure things are working as expected. I'm unfortunately not going to go into GraphQL on this blog post. Gatsby docs once again are a great
place to [learn more](https://www.gatsbyjs.com/docs/reference/graphql-data-layer/). A super quick overview of GraphQL is that GraphQL is Gatsby's Data Layer providing an API to query your data.

Go to `localhost:8000/__graphql`.

Paste this query into the query panel

```graphql
query {
  allLocation {
    nodes {
      name
      description
    }
  }
}
```

You should see something like this:

```json
{
  "data": {
    "allLocation": {
      "nodes": [
        {
          "name": "Jacksonville",
          "description": "Yep, we started in Jacksonville, FL of all places."
        },
        {
          "name": "Tampa",
          "description": "We're also in Tampa."
        },
        {
          "name": "Orlando",
          "description": "We're also in Orlando."
        }
      ]
    }
  },
  "extensions": {}
}
```

Great! Gatsby is aware of the YAML file, and we can query it using GraphQL. We can use this query inside of our application to get a hold of that data.

[Take a look at the code at this point in the project here](https://github.com/meaganewaller/dogwalker/tree/create-and-query-locations).

## Creating Dynamic Locations Pages
Alright! It's time to create the dynamic location pages.

Let's first update our `src/pages/locations.js` file. We will change it from displaying a list of links to displaying a list of names, and then we will figure out how to add the links to it later.

`src/pages/locations.js`
```js
import React from 'react'
import { Link, graphql } from 'gatsby'

export default ({data}) => (
  <>
  <h1>Locations</h1>
  <p>Check out where you can use DOGWALKER</p>

  <ul>
    {data.locations.nodes.map(location => (
      <li key={location.id}>
        {location.name}
      </li>
    ))}
  </ul>
  </>
)

export const query = graphql`
{
  locations: allLocation {
    nodes {
      name
    }
  }
}
`
```

The code above will give us an unordered list of the city names from the YAML file.

We want to link them; they should live at the same URL `locations/city-name`. We're going to replace our `locations/tampa.js`, `locations/orlando.js`, and `locations/jacksonville.js` with one file: `locations/{Location.name}.js`. Gatsby uses the curly braces in filenames to denote a dynamic URL segment. So, `locations/{Location.name}.js` will create a route like: `locations/jacksonville`, as we had before, except now we don't need separate files for every location.

Before we create that page, though, let me show you how we will query it using GraphQL. In GraphQL, we have query params, so we can pass a param and search by it. Let's give the name to the query and see if we can just get back that one individual location record.

```graphql
query ($id: String!) {
  location(name: {eq: $id}) {
    name
    description
  }
}
```

Then we can pass the parameter of

```graphql
{ "id": "Jacksonville" }
```

And receive this object back.

```json
{
  "data": {
    "location": {
      "name": "Jacksonville",
      "description": "Yep, we started in Jacksonville, FL of all places."
    }
  },
  "extensions": {}
}
```

Let's create our Location template page:

`src/pages/locations/{Location.name}.js`
```js
import React from 'react'
import { graphql } from 'gatsby'

export default (props) => {
  const { location } = props.data
  return (
    <>
    <h1>{location.name}</h1>
    <p>{location.description}</p>
    </>
  )
}

export const query = graphql`
query($id: String!) {
  location(id: { eq: $id }) {
    name
    description
  }
}
`
```

The `id` inside of this query is a unique identifier that Gatsby provides to simplify page queries.

Now, we can link to our location pages.

`src/pages/locations.js`

```js
import React from 'react'
import { graphql, Link } from 'gatsby'

export default ({data}) => (

  <>
  <h1>Locations</h1>
  <p>Check out where you can use DOGWALKER</p>

  <ul>
    {data.locations.nodes.map(location => (
      <li key={location.id}>
        <Link to={location.nameSlug}>{location.name}</Link>
      </li>
    ))}
  </ul>
  </>
)

export const query = graphql`
{
  locations: allLocation {
    nodes {
      name
      nameSlug: gatsbyPath(filePath: "/locations/{Location.name}")
    }
  }
}
`
```

`nameSlug` here is just what I've decided to call this property. You could call it `locationPath`, or anything else you want. Gatsby slugifies every route and includes a `gatsbyPath` field, which takes an argument of the `filePath` it is trying to resolve.[^1]
Now when you go to `localhost:8000/locations`, you will see a list of links to all of the location pages, and they're all rendering via the Location template! How cool is that?

[View the code at this point in the project](https://github.com/meaganewaller/dogwalker/tree/dynamic-location-pages)


## Finishing Touches
Let's go ahead and do one more thing, not entirely necessary, but let's add our links to the index page to click them instead of typing them into the browser whenever we want to visit them.

`src/pages/index.js`

```js
import React from "react"
import { Link } from 'gatsby'

export default () => {
  return (
    <>
    <ul>
      <li>
        <Link to="/">Home</Link>
      </li>
      <li>
        <Link to="/about">About</Link>
      </li>
      <li>
        <Link to="/locations">Locations</Link>
      </li>
    </ul>
    <h2>DOGWALKER</h2>
    <p>This is the dogwalker homepage.</p>
    </>
  )
}
```

Fantastic! It's not pretty or styled, but it's functional. Except when you click on About or Location and then don't have a link back to the home page 🤔. Let's take it up a notch and create a Layout
that we can reuse on the pages that include our little menu.

I put my layout inside of the `components` directory.

`src/components/Layout.js`

```js
import React from 'react'
import { Link } from 'gatsby'

const Layout = ({children}) => {
  return (
    <>
    <header>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/locations">Locations</Link>
      </nav>
    </header>

    <main>
      {children}
    </main>
    </>
  )
}

export default Layout
```

We need to add the Layout to every file that we want to render inside the layout. Those will be the children, and the files will now render inside of the `<main></main>` tags.

`src/pages/index.js`
```js
import React from "react"
import Layout from '../components/Layout'

export default () => {
  return (
    <Layout>
    <h2>DOGWALKER</h2>
    <p>This is the dogwalker homepage.</p>
    </Layout>
  )
}
```

`src/pages/about.js`
```js
import React from 'react'
import Layout from '../components/Layout'

export default () => (
  <Layout>
  <h1>About DOGWALKER</h1>
  <p>We're somehow different than all the other dogwalker applications.</p>
  </Layout>
)
```

`src/pages/locations.js`
```js
import React from 'react'
import { graphql, Link } from 'gatsby'
import Layout from '../components/Layout'

export default ({data}) => (

  <Layout>
  <h1>Locations</h1>
  <p>Check out where you can use DOGWALKER</p>

  <ul>
    {data.locations.nodes.map(location => (
      <li key={location.id}>
        <Link to={location.nameSlug}>{location.name}</Link>
      </li>
    ))}
  </ul>
  </Layout>
)

export const query = graphql`
{
  locations: allLocation {
    nodes {
      name
      nameSlug: gatsbyPath(filePath: "/locations/{Location.name}")
    }
  }
}
`
```

`src/pages/locations/{Location.name}.js`
```js
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../../components/Layout'

export default (props) => {
  const { location } = props.data
  return (
    <Layout>
    <h1>{location.name}</h1>
    <p>{location.description}</p>
    </Layout>
  )
}

export const query = graphql`
query($id: String!) {
  location(id: { eq: $id }) {
    name
    description
  }
}
`
```

See, not entirely necessary, but it's nice to know how to do this and makes clicking around possible.

[View the code at this point in the project](https://github.com/meaganewaller/dogwalker/tree/add-layout)

## Wrap Up
Gatsby makes creating simple applications super quick, but it's still powerful enough to be interesting. I'm excited to dig more into Gatsby and hopefully write more posts like this. I hope you enjoyed this tutorial. I'd love to start
writing more content like this and maybe expanding some tutorials into a series or to create an accompanying video. Let me know what you think on [Twitter](https://twitter.com/meaganewaller).



[^1]: https://www.gatsbyjs.com/docs/reference/routing/file-system-route-api/#gatsbypath-example