Search code examples
graphqlgatsby

how to create multiple templates in Gatsby.js


I was trying to make two template for my projects details and blog post pages, but due to my lake of experience with Graphql & Gatsby I have managed to make it work for my projects, project details and blog posts but I have a bug which I couldn't find on my blog posts article template makes it not working??

here is my .md front matter

---
title: The Kimo-Coffee
stack: HTML & CSS
slug: the-kimo-coffee
date: 2022/01/03
key: projects
thumb: ../images/thumbs/coffee.png
featuredImg: ../images/featured/coffee-banner.png
---

and this is my blog page code (that's working fine)

import { graphql, Link } from "gatsby"
import React from "react"
import Layout from "../../components/Layout"
import * as styles from "../../styles/blog.module.css"
import { GatsbyImage, getImage } from "gatsby-plugin-image"

const posts = ({ data }) => {
  const posts = data.articles.nodes
  return (
    <Layout>
      <div className={styles.blog}>
        <h2>Blog Posts</h2>

        {posts.map(post => (
          <Link to={"/blog/" + post.frontmatter.slug} key={post.id}>
            <div className={styles.posts}>
              <GatsbyImage
                image={getImage(post.frontmatter.thumb)}
                alt={post.frontmatter.title}
              />
              <h3 className={styles.title}>{post.frontmatter.title}</h3>
              <p className={styles.date}>{post.frontmatter.date}</p>
              <p className={styles.stack}>{post.frontmatter.stack}</p>
              <p className={styles.description}>
                {post.frontmatter.description}
              </p>
            </div>
          </Link>
        ))}
      </div>
    </Layout>
  )
}

export default posts

export const query = graphql`
  query BlogPosts {
    articles: allMdx(
      filter: { frontmatter: { key: { eq: "article" } } }
      sort: { fields: frontmatter___date, order: DESC }
    ) {
      nodes {
        excerpt
        slug
        frontmatter {
          key
          date
          title
          stack
          description
          thumb {
            childImageSharp {
              gatsbyImageData(placeholder: BLURRED, layout: FULL_WIDTH)
            }
          }
        }
        id
      }
    }
  }
`

here is my blog post details (which is not working)

import React from "react"
import { graphql, Link } from "gatsby"
import { MDXRenderer } from "gatsby-plugin-mdx"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import Layout from "../components/Layout"
import * as styles from "../styles/blog-details.module.css"

const blogPosts = ({ data, pageContext }) => {
  const { body } = data.article
  const { title, date, stack, featuredImg } = data.article.frontmatter
  const { previous, next } = pageContext

  return (
    <Layout>
      <div className={styles.details}>
        <h2>{title}</h2>
        <p>{date}</p>
        <p>{stack}</p>
        <div className={styles.featured}>
          <GatsbyImage
            image={getImage(featuredImg.childImageSharp.gatsbyImageData)}
            alt={title}
          />
        </div>
        <article className={styles.body}>
          <MDXRenderer>{body}</MDXRenderer>
        </article>
        <React.Fragment>
          {previous && (
            <Link to={previous.frontmatter.slug}>
              <button>{previous.frontmatter.title}</button>
            </Link>
          )}
        </React.Fragment>
        <React.Fragment>
          {next && (
            <Link to={next.frontmatter.slug}>
              <button>{next.frontmatter.title}</button>
            </Link>
          )}
        </React.Fragment>
      </div>
    </Layout>
  )
}

export default blogPosts

export const query = graphql`
  query BlogDetails($slug: String) {
    article: mdx(frontmatter: { slug: { eq: $slug }, key: { eq: "article" } }) {
      body
      frontmatter {
        slug
        key
        title
        date
        stack
        featuredImg {
          childImageSharp {
            gatsbyImageData(placeholder: BLURRED, layout: FULL_WIDTH)
          }
        }
      }
    }
  }
`

and last this is my code for gatsby-node.js

const path = require("path")
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (node.internal.type === `Mdx`) {
    const slug = createFilePath({ node, getNode, basePath: `pages` })
    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })
  }
}

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

  const results1 = await graphql(`
    query content {
      allMdx(filter: { frontmatter: { key: { eq: "projects" } } }) {
        nodes {
          frontmatter {
            slug
            title
          }
        }
      }
    }
  `)

  const results2 = await graphql(`
    query content {
      allMdx(filter: { frontmatter: { key: { eq: "article" } } }) {
        nodes {
          frontmatter {
            slug
            title
          }
        }
      }
    }
  `)

  const projectTemplate = path.resolve(`src/templates/project-details.js`)
  const blogTemplate = path.resolve(`src/templates/blog-details.js`)
  if (results1.errors || results2.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }

  const posts = results2.data.allMdx.nodes

  posts.forEach((post, index) => {
    const previous = index === post.length - 1 ? null : post[index + 1]
    const next = index === 0 ? null : posts[index - 1]
    createPage({
      path: `/blog/${post.frontmatter.slug}`,
      component: blogTemplate,
      context: {
        slug: post.frontmatter.slug,
        previous,
        next,
      },
    })
  })

  results1.data.allMdx.nodes.forEach(node => {
    createPage({
      path: `/projects/${node.frontmatter.slug}`,
      component: projectTemplate,
      context: { slug: node.frontmatter.slug },
    })
  })
}

sorry if my code is ugly, I'm pretty new to programming and i started with Gatsby since it makes me feel optimistic after learning javascript and react :)


Solution

  • There are a few things weird in your code that shouldn't be working despite your said it does.

    In React, all components must be capitalized, otherwise, React will interpret them as HTML elements, and because normally a component name doesn't match an HTML tag, it will break because it won't exist.

    In your case, both templates must be renamed to:

    const Posts = ({ data }) => {}
    

    And:

    const BlogPosts = ({ data, pageContext }) => {}
    

    The prompted error:

    "There's not a page or function yet at /blog/undefined"

    This means that the template is not valid because there is not a valid function template. Once capitalized, the issue should be gone.

    In addition, double-check the markdowns to ensure that you have a slug field in all of them.

    You can also enter to localhost:8000/404 which in development, displays a list of all generated pages. You can check there if all the posts are being created correctly. If so, the issue is in your template. Otherwise, it's in your page generation.

    Your issue is in the GraphQL query of the BlogPosts, where the link to the article is not properly set. You have defined a query like:

      query BlogPosts {
        articles: allMdx(
          filter: { frontmatter: { key: { eq: "article" } } }
          sort: { fields: frontmatter___date, order: DESC }
        ) {
          nodes {
            excerpt
            slug # <-- here's the issue
            frontmatter {
              key
              date
              title
              stack
              description
              thumb {
                childImageSharp {
                  gatsbyImageData(placeholder: BLURRED, layout: FULL_WIDTH)
                }
              }
            }
            id
          }
        }
      }
    

    Where the slug is directly under nodes node. At the same time, you are linking the slug as it was a child of the frontmatter at:

    <Link to={"/blog/" + post.frontmatter.slug} key={post.id}>
    

    Which results in blog/undefined path. You can check the path just hovering the link and seeing where is it pointing.

    Ensure where it should be the slug field according to your data structure. I'd say that it should be inside the frontmatter:

            frontmatter {
              slug # <-- add it here
              key
              date
              title
              stack
              description
              thumb {
                childImageSharp {
                  gatsbyImageData(placeholder: BLURRED, layout: FULL_WIDTH)
                }
              }
            }
    

    Alternatively, just change the Link structure like:

    <Link to={"/blog/" + post.slug} key={post.id}>
    

    TL:DR

    Because of:

    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })
    

    You are creating a slug based on the filename, which is being inserted as a node child, that's why the query was working but the references to that wasn't.