Skip to content
Advertisement

Programatically generating Gatsby pages without a slug from Graphql

I have set up an ACF options page in WordPress called Projects

Inside the Projects options page there is an ACF repeater allowing the user to add multiple Projects.

In Gatsby, I’m using Graphql to query the data for my Projects in two files:

  • Inside a custom hook, allowing access to the data globally in my Gatsby site

  • Inside a gatsby-node.js file in order to generate a slug for my template page called project-details.js

Obviously there is no slug in Graphql for this repeater field in the ACF options page. Instead, I’m generating a slug based on a nested Title text field that’s found inside each Project repeater field.

I’m using both the replaceAll() and toLowerCase() methods to create the slug and then making it available as part of my data.

Here’s my custom hook:

export const useProjectsQueryAlt = () => {

  const data = useStaticQuery(graphql`
    query ProjectsQueryAlt {
      wp {
        projects {
          projects {
            allprojects {
              projectContent
              projectTitle
              featuredImage {
                mediaItemUrl
                id
              }
              projectGallery {
                caption
                id
                mediaItemUrl
              }
            }
          }
        }
      }
    }
    `)

    const project = data.wp.projects.projects.allprojects.map(node => {
      const { projectContent, projectTitle, featuredImage, projectGallery } = node;
        
        const title = node.projectTitle;
        const spacesToHyphen = title.replaceAll(' ', '-');
        const slugFromTitle = spacesToHyphen.toLowerCase()

        return {
          projectContent, 
          projectTitle,
          slug: slugFromTitle,
          featuredImage,
        
          projectGallery: projectGallery.map(node => {
            const { caption, id, mediaItemUrl } = node;
            return {
              caption, 
              id, 
              mediaItemUrl
            }
          })

      }
    })

    return { project }
}

Here’s my gatsby-node file:

const path = require('path')

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

    const { data } = await graphql(`
    query Projects {
        wp {
            projects {
                projects {
                    allprojects {
                        projectTitle
                    }
                }
            }
        }
    }
    `) 

    data.wp.projects.projects.allprojects.forEach(node => {

        const title = node.projectTitle;

        const spacesToHyphen = title.replaceAll(' ', '-');
        const slugFromTitle = spacesToHyphen.toLowerCase()

        actions.createPage({
            path: '/projects/' + slugFromTitle,
            component: path.resolve('./src/templates/project-details.js'),
            context: { slug: slugFromTitle },
        })
    })

}

Here’s my template file project-details.js

import React from 'react'

function ProjectDetails() {

  return (
    <div>
      ...my page template content
    </div>
  )
}

export default ProjectDetails

I now need to find a way to check that the two appended slugs match in my ‘project-details.js’ template file in order to display the relevant project data to the corresponding URL.

Seeing as I’ve generated my slugs on the front end, following the Gatsby Docs for setting up dynamically generate pages doesn’t align with my use case. I was hoping somebody has had experience with this use case and can point me in the right direction.

Advertisement

Answer

The problem in your approach is that you are generating a “fake” slug based on the title of the project so you can’t use that field to filter any GraphQL node because the field is not present in the project fields. Your best option is using the title itself or using any autogenerated identifier (id, if it’s present as a field).

actions.createPage({
    path: '/projects/' + slugFromTitle,
    component: path.resolve('./src/templates/project-details.js'),
    context: { title },
})

Note: you can omit { title: title }

You can still use the path of your generated slug, this is a valid approach.

I’m assuming that if the title is a unique field, the slug must be too, hence you will be a valid filter.

Now in the project-details.js:

import React from 'react'

function ProjectDetails({ data }) {
  console.log("my data is", data);

  return (
    <div>
      ...my page template content
    </div>
  )
}

export const query = graphql`
  query($title: String!) {
    yourACFNode(title: { eq: $title} ) {
      # your fields
    }
  }
`

export default ProjectDetails

Of course, tweak the query above to match your ACF node but get the approach.

Advertisement