© Joe Attardi 2020
J. AttardiUsing Gatsby and Netlify CMShttps://doi.org/10.1007/978-1-4842-6297-9_7

7. Dynamic Page Creation

Joe Attardi1 
(1)
Billerica, MA, USA
 

Our home page shows a listing of all the blog posts, which displays excerpts, but we still need a dedicated page for each post to show their full content. By hooking into Gatsby’s APIs, we can dynamically create pages based on the blog post data.

Gatsby Node APIs

First, create a new file in the root of the project called gatsby-node.js. This is a special file that is used to implement the Gatsby Node APIs. It allows you to manipulate and query the GraphQL data and create pages based on that data.

The various functions in this file are called at different times during the build process. Here are two of these functions that we will use in the example project.

onCreateNode

This function is called whenever a new node is created in the Gatsby data structure. For example, this function will be called once for each of our Markdown files since they are nodes created by the gatsby-source-filesystem plugin we configured earlier.

We will use onCreateNode to create slugs for our blog posts. As mentioned earlier, a slug is a human-readable, URL-friendly representation of the blog post title. The slug is used to register the blog post page URL and to create links to blog posts from the index page.

onCreateNode is called with an object containing the node being created and an actions object that contains several functions that can be called to perform different actions.

createPages

After data is sourced by source plugins and transformed by transformer plugins, this function is called to give us an opportunity to dynamically create pages. Because the data model has been built before this function is called, we can run GraphQL queries to look up the data we need to create our pages.

We will create a template page that will have data passed to it, which will result in a unique page for each blog post.

createPages is called with an object containing a graphql function for querying the data and an actions object similar to the one passed to onCreateNode.

Adding the slug to the blog post data

When new blog posts are added, we will get the slug for each post and add it as a new field on the blog post data. Later, we’ll use this slug field to create the URLs for the blog post pages. The slug is based on the filename of the Markdown file.

Open the newly created gatsby-node.js file and add the code in Listing 7-1. This code is taken from the code in the Gatsby tutorial, which can be found at www.gatsbyjs.org/tutorial/part-seven/#creating-slugs-for-pages.
const { createFilePath } = require('gatsby-source-filesystem');
exports.onCreateNode = function({ node, getNode, actions }) {
  const { createNodeField } = actions;
  if (node.internal.type === 'MarkdownRemark') {
    const slug = createFilePath({ node, getNode });
    createNodeField({
      node,
      name: 'slug',
      value: slug
    });
  }
};
Listing 7-1

Adding the slug

First, we require a helper function, createFilePath, from the gatsby-source-filesystem plugin. This function creates a URL from a file’s path.

Next, we implement the onCreateNode function. The argument to this function is destructured to get the node, a helper function called getNode, and an actions object containing some other actions that can be performed on the node.

We then destructure the actions object and pull out the createNodeField function, which is used to add additional fields to a node. These new fields are added under a field called fields.

This function gets called whenever a node of any type is created. We only want to perform this action when the node being created represents a Markdown file, so we check if the node’s type is MarkdownRemark. If it is, then we use createFilePath to generate the slug for the file.

Finally, we call createNodeField to add the slug field to the node.

We can now query for the slug field as shown in Listing 7-2.
{
  allMarkdownRemark {
    edges {
      node {
        fields {
          slug
        }
      }
    }
  }
}
Listing 7-2

Querying for the slug field

Dynamically creating the blog post pages

Next, we’ll tap into Gatsby’s createPages API to dynamically create blog post pages. First, we’ll create a template page that will be used as a base for the dynamically created pages. Then, we’ll perform a GraphQL query for the slugs of all the blog posts.

Each slug will be passed as a context parameter to the template page, which will be used in the template’s page query to query for the full blog post data and render it.

The process is outlined in Figure 7-1.
../images/502348_1_En_7_Chapter/502348_1_En_7_Fig1_HTML.jpg
Figure 7-1

Creating pages from a template

Creating the blog post template

Create a new directory src/templates and create a new file inside that directory called blog.js. Enter the contents of Listing 7-3.
import React from 'react';
import { graphql } from 'gatsby';
import Layout from '../components/Layout';
import styles from './blog.module.css';
export default function BlogTemplate({ data }) {
  return (
    <Layout>
      <div className={styles.blog}>
        <h1>{data.markdownRemark.frontmatter.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: data.markdownRemark.html }} />
      </div>
    </Layout>
  );
}
export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
      }
    }
  }
`;
Listing 7-3

The blog post page template

The file’s default export, as usual, is the React component for the template page. There’s also a named export called query. This is the page query. The exported query will be executed by Gatsby when building the site. Note that the query takes a $slug parameter. This will receive the slug context parameter in the createPages function we’ll write next.

The slug is passed as an argument to the markdownRemark query to look up the specific blog post. We’re querying for the rendered HTML and the title from the front matter.

The page component receives the result of the GraphQL query as the data prop. We then use the values in the data prop to populate the blog post page. In particular, we use React’s dangerouslySetInnerHTML to set the rendered HTML from Remark as the inner HTML of the element.

Next, let’s create a quick CSS module for the template page. Create a file src/templates/blog.module.css and add the code from Listing 7-4.
.blog {
  padding: 1rem;
}
.blog h1 {
  margin: 0;
}
Listing 7-4

Simple CSS module for the blog template page

Creating the pages

Now that we’ve created the template, we’ll use the createPages API to generate the blog post pages. Open the file gatsby-node.js and add the createPages function as shown in Listing 7-5. This code is taken from the code in the Gatsby tutorial, which can be found at www.gatsbyjs.org/tutorial/part-seven/#creating-pages.
const path = require('path');
const { createFilePath } = require('gatsby-source-filesystem');
exports.onCreateNode = function({ node, getNode, actions }) {
  const { createNodeField } = actions;
  if (node.internal.type === 'MarkdownRemark') {
    const slug = createFilePath({ node, getNode });
    createNodeField({
      node,
      name: 'slug',
      value: slug
    });
  }
};
exports.createPages = async function({ graphql, actions }) {
  const { createPage } = actions;
  const result = await graphql(`
    query {
      allMarkdownRemark {
        edges {
          node {
            fields {
              slug
            }
          }
        }
      }
    }
  `);
  result.data.allMarkdownRemark.edges
    .forEach(({ node }) => {
      createPage({
        path: node.fields.slug,
        component: path
          .resolve('./src/templates/blog.js'),
        context: {
          slug: node.fields.slug
        }
      });
    });
};
Listing 7-5

The updated gatsby-node.js

We’ve added the Gatsby createPages API to this file. As with onCreateNode, there is an actions property that contains various helper functions. We use destructuring to access the createPage action. Next, we perform a GraphQL query to find the slugs of all of the blog posts.

Note that this GraphQL query looks a little different than other queries we’ve seen so far. Earlier, we saw queries defined using the graphql tag on a template string. Here, graphql is a function we call to execute the query, which is supplied as a string argument.

The graphql function returns a Promise. To simplify the code a bit, we use the async/await syntax instead. This allows us to have asynchronous code written in a synchronous style.

Once we have the slugs, we iterate over the results and call createPage for each. The path is the URL of the page, the component references our template page, and the context contains a slug property. This slug value is what is passed as an argument to the page query in our blog template page, which will be used to query for the full data for each blog post.

Linking to the dynamically generated pages

The last step in the process is to update our index page so that the blog post titles link to the full post pages that we dynamically created.

Open the file src/components/BlogList.js. In this component, we have a GraphQL query for the blog posts. We need to add the slug field to this query to get the URL of each blog post page, then we need to add links to those URLs.

Change the file to match the code in Listing 7-6.
import React from 'react';
import { graphql, useStaticQuery } from 'gatsby';
import BlogPost from './BlogPost';
export default function BlogList() {
  const data = useStaticQuery(graphql`
    {
      allMarkdownRemark(sort: { fields: frontmatter___date, order: DESC }) {
        edges {
          node {
            id
            frontmatter {
              title
              date(formatString: "MMMM D, YYYY")
            }
            fields {
              slug
            }
            excerpt
          }
        }
      }
    }
  `);
  return (
    <div>
      {data.allMarkdownRemark.edges.map(edge => (
        <BlogPost
          key={edge.node.id}
          slug={edge.node.fields.slug}
          title={edge.node.frontmatter.title}
          date={edge.node.frontmatter.date}
          excerpt={edge.node.excerpt}
        />
      ))}
    </div>
  );
}
Listing 7-6

The updated BlogList component

We are taking the slug from the query results and passing it as a prop to the BlogPost component, where it will render a link. Listing 7-7 shows the updated BlogPost component in src/components/BlogPost.js.
import React from 'react';
import { Link } from 'gatsby';
import styles from './BlogPost.module.css';
export default function BlogPost({ title, date, excerpt, slug }) {
  return (
    <article className={styles.blog}>
      <h2><Link to={slug}>{title}</Link></h2>
      <h3>{date}</h3>
      <p>{excerpt}</p>
    </article>
  );
}
Listing 7-7

The updated BlogPost component

The Gatsby Link component

Notice that we didn’t add a standard HTML link via an anchor tag. Instead, we used Gatsby’s built-in Link component. This is a special component meant to be used for navigation within a Gatsby site, which makes some optimizations and preloads some resources from the linked resources.

The optimization is very interesting. When the mouse is hovered over the link, it makes an asynchronous request for some metadata about the page being linked to.

If we open the browser’s developer tools and monitor the network requests, we can see two requests are made when we hover over one of the links. One of these is page-data.json, which contains metadata specific to the page being linked to by that Link component. Listing 7-8 shows an example of a response from this call.
{
  "componentChunkName": "component---src-templates-blog-js",
  "path": "/2020-06-21-welcome-to-the-coffee-blog/",
  "result": {
    "data": {
      "markdownRemark": {
        "html": "...",
        "frontmatter": { "title": "Welcome to the Coffee Blog" }
      }
    },
    "pageContext": { "slug": "/2020-06-21-welcome-to-the-coffee-blog/" }
  }
}
Listing 7-8

The contents of page-data.json

This file contains a lot of information, including
  • componentChunkName: The filename of the Webpack chunk containing this page

  • path: The URL, relative to the site root

  • result
    • data: The result of the GraphQL query that was executed at build time

    • pageContext: The data that was passed to the page component’s context

We’ve made several changes. Let’s see if everything is working. Save everything and run gatsby develop. Open the development site. The index page should now have links to each blog post, as shown in Figure 7-2.
../images/502348_1_En_7_Chapter/502348_1_En_7_Fig2_HTML.jpg
Figure 7-2

The post titles are now links

If we click one of the links, it should take us to a page for the full blog post. This is the page that we dynamically created using the Node APIs.
../images/502348_1_En_7_Chapter/502348_1_En_7_Fig3_HTML.jpg
Figure 7-3

The full blog post page

One last tweak

Now that clicking a blog post title links us to the full blog post page, there is no way to get back to the index page (i.e., other than using the browser’s back button). Let’s make the header text a link back to the index page so that we have better navigation.

Open the file src/components/Layout.js, and add the code shown in Listing 7-9.
import React from 'react';
import { Link } from 'gatsby';
import styles from './Layout.module.css';
export default function Layout({ children }) {
  return (
    <div>
      <header id={styles.header}>
        <div id={styles.inner}>
          <h1><Link to="/">Joe's Coffee Shop</Link></h1>
        </div>
      </header>
      <main id={styles.main}>
        {children}
      </main>
    </div>
  );
}
Listing 7-9

Adding a link to the index page

This will result in a decidedly ugly link.
../images/502348_1_En_7_Chapter/502348_1_En_7_Fig4_HTML.jpg
Figure 7-4

The link has the default styling

Let’s make this look a little nicer. Open the CSS module in src/components/Layout.module.css and update it as shown in Listing 7-10.
#header {
  font-family: 'Oswald', sans-serif;
  background: url('/coffee.jpg');
  background-size: cover;
  color: #FFFFFF;
}
#header #inner {
  background: rgba(119, 79, 56, 0.85);
  padding: 1rem;
}
#header h1 {
  margin: 0;
}
#header h1 a {
  color: #FFFFFF;
  text-decoration: none;
}
Listing 7-10

The updated CSS module for the Layout component

The new header link is shown in Figure 7-5. It looks much better now.
../images/502348_1_En_7_Chapter/502348_1_En_7_Fig5_HTML.jpg
Figure 7-5

The restyled link

Finally, to finish things up, let’s commit and push to GitHub.
git add .
git commit -m "Add blog post pages"
git push origin master

Summary

In this chapter, we
  • Created a template page to use for blog posts

  • Used this template with Gatsby’s Node APIs to dynamically create pages for blog posts

  • Updated the index page to link to the blog posts

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.214.141