Gatsby: Gatsby `createPage` is generating pages with the same data

Created on 2 Sep 2019  路  5Comments  路  Source: gatsbyjs/gatsby

gatsby-config.js is configured to source all markdown files from ./content directory.

ServicePost and BlogPost templates get data from ./src/content/services and
./src/content/blog directories.

gatsby-node.js sorts data from ./src/content/ based on templateKey set in
frontmatter of markdown files and passes it to createPage which generates pages
on /services/service-name and /blog/blog-article-name URLs.

The problem I have is that all /services/service-name and /blog/blog-article-name pages
get generated with the data from the first post from ./services/ and ./blog/ directories.

gatsby-config.js

const proxy = require('http-proxy-middleware')

module.exports = {
  siteMetadata: {
    title: 'Gatsby + Netlify CMS Starter',
    description:
      'This repo contains an example business website that is built with Gatsby, and Netlify CMS.It follows the JAMstack architecture by using Git as a single source of truth, and Netlify for continuous deployment, and CDN distribution.',
  },
  plugins: [
    'gatsby-plugin-react-helmet',
    'gatsby-plugin-sass',
    {
      // keep as first gatsby-source-filesystem plugin for gatsby image support
      resolve: 'gatsby-source-filesystem',
      options: {
        path: `${__dirname}/static/img`,
        name: 'uploads',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: `${__dirname}/src/content`,
        name: 'content',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: `${__dirname}/src/img`,
        name: 'images',
      },
    },
    'gatsby-plugin-sharp',
    'gatsby-transformer-sharp',
    {
      resolve: 'gatsby-transformer-remark',
      options: {
        plugins: [
          {
            resolve: 'gatsby-remark-relative-images',
            options: {
              name: 'uploads',
            },
          },
          {
            resolve: 'gatsby-remark-images',
            options: {
              // It's important to specify the maxWidth (in pixels) of
              // the content container as this plugin uses this as the
              // base for generating different widths of each image.
              maxWidth: 2048,
            },
          },
          {
            resolve: 'gatsby-remark-copy-linked-files',
            options: {
              destinationDir: 'static',
            },
          },
        ],
      },
    },
    {
      resolve: 'gatsby-plugin-netlify-cms',
      options: {
        modulePath: `${__dirname}/src/cms/cms.js`,
      },
    },
    {
      resolve: 'gatsby-plugin-purgecss', // purges all unused/unreferenced css rules
      options: {
        develop: true, // Activates purging in npm run develop
        purgeOnly: ['/all.sass'], // applies purging only on the bulma css file
      },
    }, // must be after other CSS plugins
    'gatsby-plugin-netlify', // make sure to keep it last in the array
  ],
  // for avoiding CORS while developing Netlify Functions locally
  // read more: https://www.gatsbyjs.org/docs/api-proxy/#advanced-proxying
  developMiddleware: app => {
    app.use(
      '/.netlify/functions/',
      proxy({
        target: 'http://localhost:9000',
        pathRewrite: {
          '/.netlify/functions/': '',
        },
      })
    )
  }
}

gatsby-node.js

const _ = require('lodash')
const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')
const { fmImagesToRelative } = require('gatsby-remark-relative-images')

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

  const blogs = await graphql(`
    {
      blogs: allMarkdownRemark(
        limit: 1000
        filter: { frontmatter: { templateKey: { eq: "blog-post" } } }
        sort: {
          fields: [frontmatter___date]
          order: [DESC]
        }
      ) {
        edges {
          node {
            id
            fields {
              slug
            }
            html
            frontmatter {
              title
              templateKey
              date
            }
          }
        }
      }
    }
  `)

  const BlogPostTemplate = path.resolve(`src/templates/BlogPost.js`)
  blogs.data.blogs.edges.forEach(({ node }) => {

    createPage({
      path: node.fields.slug,
      component: BlogPostTemplate,
      context: {},
    });
  });

  const services = await graphql(`
    {
      services: allMarkdownRemark(
        limit: 1000
        filter: { frontmatter: { templateKey: { eq: "service-post" } } }
      ) {
        edges {
          node {
            id
            fields {
              slug
            }
            html
            frontmatter {
              title
              templateKey
            }
          }
        }
      }
    }
  `)

  const ServicePostTemplate = path.resolve(`src/templates/ServicePost.js`)
  services.data.services.edges.forEach(({ node }) => {
    createPage({
      path: node.fields.slug,
      component: ServicePostTemplate,
    });
  });
}

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions
  fmImagesToRelative(node) // convert image paths for gatsby images

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

./src/templates/BlogPost.js

import React from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet'
import { graphql } from 'gatsby'
import Layout from '../components/Layout'
import Content, { HTMLContent } from '../components/Content'

export const BlogPostTemplate = ({
  content,
  contentComponent,
  description,
  title,
  helmet,
}) => {
  const PostContent = contentComponent || Content

  return (
    <section>
      {helmet || ''}
        <h1>
          {title}
        </h1>

        Blog post template

        <p>{description}</p>
        <PostContent content={content} />
    </section>
  )
}

BlogPostTemplate.propTypes = {
  content: PropTypes.node.isRequired,
  contentComponent: PropTypes.func,
  title: PropTypes.string,
  helmet: PropTypes.object,
}

const BlogPost = ({ data }) => {
  // console.log('Blog Post Data', JSON.stringify(data))

  const { markdownRemark: post } = data

  return (
    <Layout>
      <BlogPostTemplate
        content={post.html}
        contentComponent={HTMLContent}
        helmet={
          <Helmet titleTemplate="%s | Blog">
            <title>{`${post.frontmatter.title}`}</title>
          </Helmet>
        }
        title={post.frontmatter.title}
      />
    </Layout>
  )
}

BlogPost.propTypes = {
  data: PropTypes.shape({
    markdownRemark: PropTypes.object,
  }),
}

export default BlogPost

export const pageQuery = graphql`
  query BlogPostByID {
    markdownRemark(frontmatter: { templateKey: { eq: "blog-post" } }) {
      id
      html
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
        title
      }
    }
  }
`

./src/content/blog/blog-post-1.md

---
templateKey: blog-post
title: Blog post 1 title
date: 2014-12-17T15:04:10.000Z
---

Blog post 1 body
awaiting author response question or discussion

All 5 comments

@iamskok at first glance it looks like the issue resides on how you're creating the pages in gatsby-node.js and the the query you're using in the template, for instance BlogPostTemplate. If you don't mind waiting i'm cloning the repo and i'll go over it and post a detailed explanation. Sounds good?

@jonniebigodes perfect!

@iamskok i've gone over it and below are the steps i took to handle your issue.

  • Cloned the repo you supplied.
  • Installed the dependencies.
  • Looked over at your gatsby-node and part of the issue resides there. I'll go over it shortly, but first i'm going to post the changes i made.
const _ = require('lodash')
const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')
const { fmImagesToRelative } = require('gatsby-remark-relative-images')

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


  const allData=await graphql(`
  {
    blogs: allMarkdownRemark(limit: 1000, filter: {frontmatter: {templateKey: {eq: "blog-post"}}}, sort: {fields: [frontmatter___date], order: [DESC]}) {
      edges {
        node {
          id
          fields {
            slug
          }
          frontmatter {
            title
            templateKey
          }
        }
      }
    }
    services: allMarkdownRemark(limit: 1000, filter: {frontmatter: {templateKey: {eq: "service-post"}}}) {
      edges {
        node {
          id
          fields {
            slug
          }
          frontmatter {
            title
            templateKey
          }
        }
      }
    }
  }
  `)

  const {blogs,services}= allData.data

  blogs.edges.forEach(({ node }) => {

    createPage({
      path:node.fields.slug,
      component:require.resolve('./src/templates/BlogPost.js'),
      context:{
        slug:node.fields.slug, // will fetch the appropriate item based on the slug
      }
    })
  });
  services.edges.forEach(({node}) => {

    createPage({
      path:node.fields.slug,
      component:require.resolve('./src/templates/ServicePost.js'),
      context:{
        slug:node.fields.slug
      }
    })
  });


}

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions
  fmImagesToRelative(node) // convert image paths for gatsby images

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

Based on your repo code, instead going over two queries, one after another, instead i've changed it a bit and aliased it. Now in only one query you get both results that you need, and prevent code duplication.

Also and here's where the part of the issue resides. When you're creating the page, you're not injecting nothing in the gatsby context prop and when the template runs for each page it will fetch the first result it gets. With this change and the change to the template that i'm going to enumerate shortly your issue is solved.

  • Modified the ./src/templates/BlogPost.js to the following:
import React from "react";
import PropTypes from "prop-types";
import Helmet from "react-helmet";
import { graphql } from "gatsby";
import Layout from "../components/Layout";
import Content, { HTMLContent } from "../components/Content";

export const BlogPostTemplate = ({
  content,
  contentComponent,
  description,
  title,
  helmet
}) => {
  const PostContent = contentComponent || Content;

  return (
    <section>
      {helmet || ""}
      <h1>{title}</h1>
      Blog post template
      <p>{description}</p>
      <PostContent content={content} />
    </section>
  );
};

BlogPostTemplate.propTypes = {
  content: PropTypes.node.isRequired,
  contentComponent: PropTypes.func,
  title: PropTypes.string,
  helmet: PropTypes.object
};

const BlogPost = ({ data }) => {
  const { markdownRemark: post } = data;

  return (
    <Layout>
      <BlogPostTemplate
        content={post.html}
        contentComponent={HTMLContent}
        helmet={
          <Helmet titleTemplate="%s | Blog">
            <title>{`${post.frontmatter.title}`}</title>
          </Helmet>
        }
        title={post.frontmatter.title}
      />
    </Layout>
  );
};

BlogPost.propTypes = {
  data: PropTypes.shape({
    markdownRemark: PropTypes.object
  })
};

export default BlogPost;

export const pageQuery = graphql`
  query BlogPostByID($slug: String!) {
    markdownRemark(
      fields: { slug: { eq: $slug } }
      frontmatter: { templateKey: { eq: "blog-post" } }
    ) {
      id
      html
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
        title
      }
    }
  }
`;

With this small change to the query it will fetch the results based on the $slug variable that's injected via gatsby-node.js api call createPage and with this it will fetch each individual item, not the first one. You could also streamline the query by removing frontmatter: { templateKey: { eq: "blog-post" } } portion of the query. As technically each slug should be unique.

  • Modified ./src/templates/ServicePost.js to match the what is happening with the blog and now it contains the following:
  import React from "react";
  import PropTypes from "prop-types";
  import Helmet from "react-helmet";
  import { graphql } from "gatsby";
  import Layout from "../components/Layout";
  import Content, { HTMLContent } from "../components/Content";

  export const ServicePostTemplate = ({
    content,
    contentComponent,
    description,
    title,
    helmet
  }) => {
    const PostContent = contentComponent || Content;

    return (
      <section>
        {helmet || ""}
        <h1>{title}</h1>
        Service post template
        <p>{description}</p>
        <PostContent content={content} />
      </section>
    );
  };

  ServicePostTemplate.propTypes = {
    content: PropTypes.node.isRequired,
    contentComponent: PropTypes.func,
    description: PropTypes.string,
    title: PropTypes.string,
    helmet: PropTypes.object
  };

  const ServicePost = ({ data }) => {
    const { markdownRemark: post } = data;

    return (
      <Layout>
        <ServicePostTemplate
          content={post.html}
          contentComponent={HTMLContent}
          helmet={
            <Helmet titleTemplate="%s | Blog">
              <title>{`${post.frontmatter.title}`}</title>
            </Helmet>
          }
          title={post.frontmatter.title}
        />
      </Layout>
    );
  };

  ServicePost.propTypes = {
    data: PropTypes.shape({
      markdownRemark: PropTypes.object
    })
  };

  export default ServicePost;

  export const pageQuery = graphql`
    query ServicePostByID($slug: String!) {
      markdownRemark(
        fields: { slug: { eq: $slug } }
        frontmatter: { templateKey: { eq: "service-post" } }
      ) {
        id
        html
        frontmatter {
          title
        }
      }
    }
  `;

With this change it will work in similar fashion as the blog. Once again you could even remove the frontmatter: { templateKey: { eq: "service-post" } } portion for the same reason explained above for the blog.

  • Issued gatsby develop and opened http://localhost:8000 and i'm presented with the following:
    kaldi_1
  • Clicked http://localhost:8000/blog and i'm presented with the following:
    kaldi_2
  • Clicked http://localhost:8000/blog/blog-post-3/ and i'm presented with the following:
    kaldi_3

  • Clicked http://localhost:8000/blog/blog-post-2/ and i'm presented with the following:
    kaldi_blog_2

As you can see, the data is now displayed correctly.

Same as in the services.

Feel free to provide feedback so that we can close this issue or continue to work on it untill we find a suitable solution.

@jonniebigodes Thanks a lot for the detailed explanation!

Everything is working as expected website.
I also got rid of frontmatter: { templateKey: { eq: "service-post" } } lines of code.

@iamskok no need to thank, glad that i was able to help.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

KyleAMathews picture KyleAMathews  路  3Comments

dustinhorton picture dustinhorton  路  3Comments

Oppenheimer1 picture Oppenheimer1  路  3Comments

kalinchernev picture kalinchernev  路  3Comments

signalwerk picture signalwerk  路  3Comments