gatsby-source-prismic-graphql icon indicating copy to clipboard operation
gatsby-source-prismic-graphql copied to clipboard

Creating dynamic pages that have a parent relationship?

Open michaelpumo opened this issue 5 years ago • 11 comments

I have been reading through the docs on dynamic page generation and I have a good working solution. (Doc: https://github.com/birkir/gatsby-source-prismic-graphql#automatic-page-generation)

My current solution:

{
  resolve: "gatsby-source-prismic-graphql",
  options: {
    repositoryName: "example-repo-name",
    path: "/preview",
    previews: true,
    pages: [
      {
        type: "Page",
        match: "/:uid",
        path: "/page-preview",
        component: require.resolve("./src/templates/page.js"),
      },
    ],
  },
}

This works fine and creates pages like so (at the top level):

/about /contact /sales

However, in Prismic, some of these pages have a relationship field where the author can select a "parent" page.

Is there any possible way to generate dynamic pages with a URL pattern like:

/parent-page/sales

...where the parent relationship has its own uid prefixed before the page uid?

The query within the page.js template is as simple as:

query PageQuery($uid: String!) {
  prismic {
    allPages(uid: $uid) {
      edges {
        node {
          title
        }
      }
    }
  }
}

I guess what I'd like to know is if this is even possible at all with the current plugin?

A nudge in the right direction on how to achieve a nested URL structure/scheme would be greatly appreciated.

Thank you!

michaelpumo avatar Oct 14 '19 21:10 michaelpumo

I also would like to know if this is possible as it is a very common use case!

kamerondotcom avatar Nov 07 '19 08:11 kamerondotcom

Hi @kamerondotcom and @michaelpumo,

Currently it's impossible to inject params in the url that are not part of the medata coming from the main document that you're linking to. I'm currently working on an implementation that will allow you to setup any kind of custom param in the URL relying on custom resolvers. Basically it means that you'll manage to inject data coming from a document link like a parent category but also from any other field in your document like a text field. I'm still thinking of the best way to implement it but it will probably be something closer to this:

pages: [{
          type: 'Blog',          // Custom type of the document
          match: '/:lang/blog/:parentCategory?/:category?/:uid',   // Pages will be generated in this pattern
          path: '/blog', // Placeholder route for previews
          queryParams: {
            query: `
              category {
                ... on PRISMIC_Category {
                  _meta {
                    uid
                  }
                  parent_category {
                    ... on PRISMIC_Category {
                      _meta {
                        uid
                      }
                    }
                  }
                }
              }
            `,
            category: (metaNode) => metaNode.category._meta.uid,
            parentCategory: (metaNode) => {
              return metaNode.category.parent_category._meta.uid
            }
          }]

I still don't know about naming but basically the queryParams object ask for a piece of query to fetch the right data and then below you have the resolvers that will match the custom param names in the url that will take a node as parameter and you'll just need to specify the path to the data you need to fill the URL. Does it makes sense?

arnaudlewis avatar Nov 07 '19 16:11 arnaudlewis

That sounds great @arnaudlewis. I have a solution below for now but it would be great to get an official method for this.

What I did if anyone is interested (@kamerondotcom).

gatsby-config.js

const { apiEndpoint } = require("./prismic-config")
const repo = /([^\/]+)\.prismic\.io\/graphql/.exec(apiEndpoint)

module.exports = {
    {
      resolve: "gatsby-source-prismic-graphql",
      options: {
        repositoryName: repo[1],
        path: "/preview",
        previews: true,
        sharpKeys: [/image|photo|picture|logo/],
        // Do not use. We build pages manually in gatsby-node.js
        // pages: [
        //   {
        //     type: "Page",
        //     match: "/:uid",
        //     path: "/page-preview",
        //     component: require.resolve("./src/templates/page.js"),
        //   },
        // ],
      },
    },
  ],
}

gatsby-node.js

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

  const result = await graphql(`
    query PageQuery {
      prismic {
        allPages {
          edges {
            node {
              _meta {
                uid
              }
              parent {
                ... on PRISMIC_Page {
                  _meta {
                    id
                    uid
                  }
                }
              }
            }
          }
        }
      }
    }
  `)

  if (result.errors) {
    reporter.panicOnBuild(`🔥 Error while running GraphQL query on Prismic.`)
    return
  }

  const pageTemplate = require.resolve(`./src/templates/page.js`)

  console.log(`🙏🏼 Begin creating pages from Prismic...`)

  result.data.prismic.allPages.edges.forEach(({ node }) => {
    const parent = node.parent ? node.parent._meta.uid : ""
    const slug = node._meta.uid
    const url = parent ? `${parent}/${slug}` : `${slug}`
    const parentId = parent ? node.parent._meta.id : ""

    console.log(`✅ Page: ${url}`)

    createPage({
      path: url,
      component: pageTemplate,
      context: {
        parentId: parentId,
        uid: slug,
      },
    })
  })

  console.log(`👌🏼 Done creating pages from Prismic!`)
}

This works and builds pages with parents correctly. However, it's not recursive and only goes one level deep. You could go deeper if you wanted to but it will always be a fixed level unless I can think of a clever solution to it.

Also, I think this breaks the preview mechanism now. Not sure how to get around that?

Thanks

michaelpumo avatar Nov 07 '19 19:11 michaelpumo

Yes but links between pages and preview rely on the link resolver and so for this, you need all the metadata from your doc links to be able to properly generate it. I’m my implementation, I’ll generate the link resolver from your gatsby config so you won’t even have to care about it ;)

arnaudlewis avatar Nov 07 '19 19:11 arnaudlewis

Heya @arnaudlewis - you're absolutely right. I had to do some trickery to get links working too. It's limited to known links...if they add an adhoc link via richtext then I guess it would break. Darn! Need to error-proof this for the meantime.

michaelpumo avatar Nov 07 '19 19:11 michaelpumo

Hi @arnaudlewis, we're desperate for nested path support but don't want to go down the gastsby-node.js route as we rely very heavily on previews. Do you have a view on timescales for nested paths support? Many thanks.

PS Awesome project btw :-)

Jrousell avatar Jan 16 '20 16:01 Jrousell

Hi @arnaudlewis, we need this same capability. Is this on the roadmap?

javamate avatar Mar 11 '20 21:03 javamate

Hi guys, actually, the feature is developed and ready for any Prismic user and we'll be adapted to Gatsby so It needs some tests on our end but I can work on a small sample to showcase how it will work. Basically, it will look almost like I described before but the resolvers will be more declarative than functions and as soon as you have this kind of routing model defined, It will automatically add a url field on each document queried from the writing room. It means that the link resolver won't be necessary anymore to resolve a URL on your website ;)

arnaudlewis avatar Mar 12 '20 10:03 arnaudlewis

Hi @arnaudlewis. Do you have an ETA when this will be implemented for Gatsby? Do you have that small sample to showcase how it will work? Thanks!

javamate avatar Apr 01 '20 20:04 javamate

@arnaudlewis our use case is even a bit simpler. We has a slug_prefix field on all our pages that allows content editors to put whatever they want (ie /products/) then it appends the uid to the end. As @Jrousell said we ended up having to do this with a custom page resolver in gatsby-node.js but this has broken previews:

    pages.edges.forEach(entry => {
      const { node: { _meta: {uid}, slug_prefix } } = entry
      let prefix = slug_prefix || ''
      
      // lets trim the start and end / just to make things consistent
      prefix = prefix.replace(/^\/+|\/+$/g, '') + '/'
    
      // If we come across a page with the slug 'home' we'll make this the homepage
      const slug = uid === 'home' ? '/' : prefix + uid
      
      createPage({
        context: {
          uid,
        },
        path: slug,
        component: path.resolve("src/templates/page.js"),
      })
    })

Is there a way we can just write a custom resolver. You mentioned something was implemented. Is there any documentation you can point us to or provide an example here?

greatwitenorth avatar Apr 29 '20 15:04 greatwitenorth

@arnaudlewis Any news on this one? Is this feature still under development by the Prismic team?

DanielJohnsson87 avatar Jul 10 '20 12:07 DanielJohnsson87