birkir / gatsby-source-prismic-graphql

Gatsby source plugin for Prismic GraphQL
MIT License
137 stars 75 forks source link

Creating dynamic pages that have a parent relationship? #88

Open michaelpumo opened 4 years ago

michaelpumo commented 4 years ago

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!

kamerondotcom commented 4 years ago

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

arnaudlewis commented 4 years ago

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?

michaelpumo commented 4 years ago

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

arnaudlewis commented 4 years ago

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 ;)

michaelpumo commented 4 years ago

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.

Jrousell commented 4 years ago

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 :-)

javamate commented 4 years ago

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

arnaudlewis commented 4 years ago

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 ;)

javamate commented 4 years ago

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!

greatwitenorth commented 4 years ago

@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?

DanielJohnsson87 commented 4 years ago

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