Search code examples
javascriptreactjsgatsbywordpress-rest-api

Gatsbyjs Page generation | Best practices


I'm working on Gatsbyjs web with headless WordPress as data source. I don't want to generate all pages statically in /pages, but instead in im gatsby-node.js mapping through allPages / allPosts queries and sending data to page/post template using createPage API.

But my pages are kinda complex, they seem to need very different queries ( acf.. )

What would be the best practice here? Should I create a template for each and every page and map data directly into those?


Solution

  • Yes, you hit the nail. You have to generate templates/pages for each type of page you want to generate.

    TL;DR

    You only need to create different createPage actions and point them in different templates/pages. For example:

    createPage({
          path: node.fields.slug,
          component: path.resolve(`./src/templates/blog-post.js`),
          context: {
            slug: node.fields.slug,
          },
        })
    

    and

     createPage({
          path: node.fields.slug,
          component: path.resolve(`./src/templates/tags.js`),
          context: {
            slug: node.fields.slug,
          },
        })
    

    Long version

    Standard use-case

    exports.createPages = async ({ 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-post.js`),
          context: {
            slug: node.fields.slug,
          },
        })
      })
    }
    

    component will define where the data will be available to use and which template/page/component will use.

    If you want to use a different template rather than /blog-post you need to create another createPage action. Something like this:

    exports.createPages = ({ actions, graphql }) => {
      const { createPage } = actions
    
      return graphql(`
        {
          allMarkdownRemark(limit: 1000) {
            edges {
              node {
                id
                fields {
                  slug
                }
                frontmatter {
                  tags
                  templateKey
                }
              }
            }
          }
        }
      `).then(result => {
        if (result.errors) {
          result.errors.forEach(e => console.error(e.toString()))
          return Promise.reject(result.errors)
        }
    
        const posts = result.data.allMarkdownRemark.edges
    
        posts.forEach(edge => {
          const id = edge.node.id
          createPage({
            path: edge.node.fields.slug,
            tags: edge.node.frontmatter.tags,
            component: path.resolve(
              `src/templates/blog-post.js`
            ),
            // additional data can be passed via context
            context: {
              id,
            },
          })
        })
    
        // Tag pages:
        let tags = []
        // Iterate through each post, putting all found tags into `tags`
        posts.forEach(edge => {
          if (_.get(edge, `node.frontmatter.tags`)) {
            tags = tags.concat(edge.node.frontmatter.tags)
          }
        })
        // Eliminate duplicate tags
        tags = _.uniq(tags)
    
        // Make tag pages
        tags.forEach(tag => {
          const tagPath = `/tags/${_.kebabCase(tag)}/`
    
          createPage({
            path: tagPath,
            component: path.resolve(`src/templates/tags.js`),
            context: {
              tag,
            },
          })
        })
      })
    }
    

    Without going into details of what it does or how (if you need I can detail the answer), the important thing is that you can use createPage action to define how many pages, data, and components you need. In this case, blog-post.js and tags.js which will be found under /blog-post/postSlug and in /tag/tagPath.

    Promise use-case

    If you have a small website or project, the previous case may work, but if your project grows, it becomes hell to find information among so many lines. So I use to create promises to store that information. In my gatsby-node:

    const postsBuilder = require("./src/build/postsBuilder");
    const tagsBuilder = require("./src/build/tagsBuilder");
    
    exports.createPages = async ({graphql, actions}) => {
      await Promise.all(
        [
          postBuilder(graphql, actions),
          tagsBuilder(graphql, actions)
        ]
      );
    };
    

    Then, in one of those builders:

    const path = require('path')
    
    async function postsBuilder(graphql, actions) {
      const {createPage} = actions;
    
      const postsQuery= await graphql(`
         {
          allMarkdownRemark(limit: 1000) {
            edges {
              node {
                id
                fields {
                  slug
                }
                frontmatter {
                  tags
                  templateKey
                }
              }
            }
          }
        }`);
    
      const resultForms = postsQuery.data.allMarkdownRemark.edges;
    
      resultForms.map(node => {
          createPage({
            path: node.node.url + '/',
            component: whateverYouNeed,
            context: {
              name: node.node.name,
              url: node.node.url
            },
          })
      });
    }
    
    module.exports = postsBuilder;
    

    Note that code could be refactored in many ways, is just to show another approach of what you are able to do.

    I think the promise way is much more semantic and clean but it's up to you to use whatever you need in each case.

    References:

    1. First query
    2. Second query