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
@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.
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.
./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.
./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.
gatsby develop and opened http://localhost:8000 and i'm presented with the following:
http://localhost:8000/blog and i'm presented with the following:
Clicked http://localhost:8000/blog/blog-post-3/ and i'm presented with the following:

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

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.