Modelling re-usable page/slice with content relationship & next js

Hello!

So I have been trying to set up a content relationship system with Next JS, so that I can re-use a piece of content within another page.

As an illustration:

In a custom type pillar_page "jobs in Canada", I want to be able to pull a page of type country_faq, specifically the page "Canada" which contains let's say a FAQ about finding jobs in Canada.

I want to be able to retrieve the data of the country_faq type "Canada" to display its content within my pillage_page "Jobs in Canada".

What is the easiest way to do that with Next JS?

Option A :

  1. Set the content directly within the custom type "country_faq" without creating any slice within this custom type
  2. Pull the "Canada" country_faq page through a dummy slice content relationship in the "jobs in canada" pillar_page

Issue: how to configure a "custom_type" that will not have a real route in my application? Instead this custom_type will only act as a slice, as it will live only when call through a slice through content relationship?

Option 2 : a bit more hacky

  1. Add a "CountryFaq" Slice in the country_faq "Canada" page, that will have the whole content
  2. Pull the "Canada" country_faq page through a dummy slice with content relationship in the "jobs in canada" pillar_page:

Issue ---> Instead of getting the content in the "Canada" country_faq page (basically it's CountryFaq slice), I am getting the content of the dummy slice containing the content relationship. Namely, nothing.

Hope this is clear :sweat_smile:
Thanks in advance for your help!

1 Like

Hi @Marving,

This is a good question and is a use-case that I have frequently in my own projects. I think the best approach is to use Option A that you've outlined above. There shouldn't be any issue with having a Custom Type that doesn't correspond to any route in your application. There's no issue leaving the country_faq type out of your routes configuration.

When you query the pillar_page type you can also retrieve the content from the linked country_faq document. Here you have a few options:

  1. FetchLinks - a query option that allows you to retrieve simple content from a linked document. It's easy to add to your query but is limited in what type of content it can retrieve. Take a look at the linked documentation to see if it will work in this case for you.
  2. GraphQuery - if FetchLinks isn't able to work in your case, GraphQuery is a more powerful version of retrieving content from a Content Relationship. You will be able to retrieve anything you need from the linked document, but it can be more complex to configure especially for a Content Relationship in a Slice.
  3. Query the linked page separately - this is my recommended method especially if your Next.js site is static. After you query the pillar_page, filter the slices for slice that contains the Content Relationship. Then map that to create an array of ID for any linked country_faq document. Then run a second query using the getAllByIDs method. This will return all the linked country_faq documents and their content that you can then use to fill in that section.

Let me know if you have any questions about any of this.

Thanks for the reply @Levi!

Is there any Prismic/Next js boilerplate/Repository available with thise use-case?

I will dive in your indications this week-end, thank you!

To be honest, I am still not sure how to proceed.

Apologies, still a junior dev here!

I am not sure to fully understand where & how I should do such a filtering system? In the getStaticProps of the main page (pillar_page) where the content will be displayed?

That is what I have started to do:

context:
custom type where I want to display the data: pillar_page
Custom type where I am taking the data through content relationship: topic_info
slice I am invoking in the pillar_page to do the content relationship: reusable_topic_info

export async function getStaticProps({ params, previewData }) {
  const client = createClient({ previewData })
  const page = await client.getByUID('pillar_page', params.uid)
  const similarPages = await getSimilarPages({ client, page })

  console.log(page.data.slices)
  // where page.data.slices --> where  slice_type = "reusable_topic_info"
  const contentRelationshipSlice = page.data.slices.find(
    (slice) => slice.slice_type === 'reusable_topic_info'
  )
  console.log(
    'the one slice with content relationship:',
    contentRelationshipSlice
  )
  // const contentRelationshipSlice = page.body.find(
  //   (slice) => slice.slice_type === 'content_relationship'
  // )

  // const linkedTopicInfoIds = contentRelationshipSlice.items.map(
  //   (item) => item.topic_info.id
  // )

  // const linkedTopicInfoDocs = await client.getAllByIDs(linkedTopicInfoIds)

  // const canadaTopicInfo = linkedTopicInfoDocs.find(
  //   (doc) => doc.data.title === 'Canada'
  // )
  // const canadaContent = canadaTopicInfo.data.content

  // console.log(canadaContent)
  // Use the canadaContent to fill in the section where you want to display the linked topic_info content

  return {
    props: {
      metaTitle: page.data.meta_title,
      metaDescription: page.data.meta_description,
      ogImage: page.data.og_image.url,
      page: page,
      similarPages,
      title: page.data.title,
      upperTitle: page.data.uppertitle
    }
  }
}

export async function getStaticPaths() {
  const client = createClient()

  const pages = await client.getAllByType('pillar_page')

  return {
    paths: pages.map((page) => prismicH.asLink(page)),
    fallback: false
  }
}

my console.log of contentRelationshipSlice is:

the one slice with content relationship: {
  variation: 'default',
  version: 'sktwi1xtmkfgx8626',
  items: [ {} ],
  primary: {
    reusabletopic: {
      id: 'ZBdcuxAAAB0AkkGv',
      type: 'topic_info',
      tags: [],
      lang: 'en-us',
      slug: 'discover-the-benefits-of-working-in-canadas-thriving-data-and-ai-ecosystem',
      first_publication_date: '2023-03-19T19:04:35+0000',
      last_publication_date: '2023-03-25T20:58:41+0000',
      link_type: 'Document',
      isBroken: false
    }
  },
  id: 'reusable_topic_info$8a96e7cb-dddc-481f-b1f4-7375345be343',
  slice_type: 'reusable_topic_info',
  slice_label: null
}

But I do not understand how am I retrieving each piece of content of the custom type topic_info instead of getting the content of the slice type "reusable_topic_info"?

Thanks in advanced!!

I have found a way, thanks!

1 Like

Well now you have to share it!

1 Like

Of course :slight_smile:

export async function getStaticProps({ params, previewData }) {
  const client = createClient({ previewData })


  const page = await client.getByUID('pillar_page', params.uid)
  const similarPages = await getSimilarPages({ client, page })

// The magic is operating here:
  page.data.slices = await Promise.all(
    page.data.slices.map(async (slice) => {
      if (
        slice.slice_type === 'reusable_topic_info' &&
        slice.primary.reusabletopic.id
      ) {
        const topic = await client.getByID(slice.primary.reusabletopic.id)
        slice.primary.reusabletopic = topic
        return slice
      }

      return slice
    })
  )

  return {
    props: {
      metaTitle: page.data.meta_title,
      metaDescription: page.data.meta_description,
      ogImage: page.data.og_image.url,
      page: page,
      similarPages,
      title: page.data.title,
      upperTitle: page.data.uppertitle
    }
  }
}

Shoutout to Angelo Ashmore, a Prismic contributor who unblocked me on that on!!
Basically the idea is of the snippet code above (FAQ document = topic info):

  1. Add a field to your Page CT that lets you select your FAQ document.
  2. In your page’s getStaticProps, you would query first query for your page’s document. Then, you would make another query for your FAQ document using the page’s content relationship’s ID property.
  3. Return the FAQ document in your props and use it in your page.
2 Likes