DEV Community

loading...
Cover image for Render Dynamic Pages in Gatsby Using File System Route API and YAML

Render Dynamic Pages in Gatsby Using File System Route API and YAML

meaganewaller profile image Meagan Waller Originally published at meaganwaller.com ・10 min read

I've been using Gatsby 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, 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.

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 to get your environment set up.)

npm install -g gatsby-cli
Enter fullscreen mode Exit fullscreen mode

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

npm init gatsby
Enter fullscreen mode Exit fullscreen mode

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.

cd dogwalker
Enter fullscreen mode Exit fullscreen mode

Get the site up locally with

npm run develop
Enter fullscreen mode Exit fullscreen mode

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.

import React from "react"

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

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

import React from 'react'

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

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.

import React from 'react'

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

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

If you're feeling lost, check out this branch 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.

import React from 'react'

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

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

import React from 'react'

export default () => (
  <>
  <h1>Tampa</h1>
  <p>We're also in Tampa.</p>
  </>
)
Enter fullscreen mode Exit fullscreen mode

src/pages/locations/orlando.js

import React from 'react'

export default () => (
  <>
  <h1>Orlando</h1>
  <p>We're also in Orlando.</p>
  </>
)
Enter fullscreen mode Exit fullscreen mode

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

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>
  </>
)
Enter fullscreen mode Exit fullscreen mode

(Read about Gatsby's Link component here)

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.

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.

- 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."
Enter fullscreen mode Exit fullscreen mode

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

npm install gatsby-transformer-yaml
Enter fullscreen mode Exit fullscreen mode

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.

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",
      },
  ],
}
Enter fullscreen mode Exit fullscreen mode

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. 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

query {
  allLocation {
    nodes {
      name
      description
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

You should see something like this:

{
  "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": {}
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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
    }
  }
}
`
Enter fullscreen mode Exit fullscreen mode

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.

query ($id: String!) {
  location(name: {eq: $id}) {
    name
    description
  }
}
Enter fullscreen mode Exit fullscreen mode

Then we can pass the parameter of

{ "id": "Jacksonville" }
Enter fullscreen mode Exit fullscreen mode

And receive this object back.

{
  "data": {
    "location": {
      "name": "Jacksonville",
      "description": "Yep, we started in Jacksonville, FL of all places."
    }
  },
  "extensions": {}
}
Enter fullscreen mode Exit fullscreen mode

Let's create our Location template page:

src/pages/locations/{Location.name}.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
  }
}
`
Enter fullscreen mode Exit fullscreen mode

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

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}")
    }
  }
}
`
Enter fullscreen mode Exit fullscreen mode

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

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

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>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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

import React from "react"
import Layout from '../components/Layout'

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

src/pages/about.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>
)
Enter fullscreen mode Exit fullscreen mode

src/pages/locations.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}")
    }
  }
}
`
Enter fullscreen mode Exit fullscreen mode

src/pages/locations/{Location.name}.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
  }
}
`
Enter fullscreen mode Exit fullscreen mode

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

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.


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

Discussion

pic
Editor guide