In a traditional CMS, we might be used to structuring our site by nesting pages underneath each other. In this scenario, the CMS often handles the creation of the URL for us. This works a bit differently in a headless CMS, where the content is usually handled as just a piece of content and isn't tied up to any specific view or structure. But what if we want to give our editors more control over the URL structure, other than placing all new articles under "/articles/:articleSlug"?
Updating the content type in Contentful
One way of solving this could be to add a parent field in your content type.
By including a reference field to another page (which again could have another reference to another page) we can build up the structure as we'd like. For the example shown in the image, the path would be "/om-oss/partnere".
Building the slugs in Nextjs
When fetching our data, we also need to generate the correct slug on our website. By using the Contentful JS client, we can fetch the entries and dereference the parent(s) content for the current page to access the slugs. The include
parameter decides how many levels we can do this.
contentfulClient
.getEntries({
content_type: `${contentType}`,
include: 3,
})
.then(function (entry) {
return entry.items
})
After we get the entry or entries - we need a way to get all slugs and concatenate them, as long as the page has a parent. I solved this using a recursive method.
function buildSlugFromParent(page) {
const parent = page.parent?.fields
return parent ? `${buildSlugFromParent(parent)}/${page.slug}` : page.slug
}
In the method above, I'm sending in the entry's fields and checking if a parent exists. If it does, rerun the method, while building up the URL segments.
Now we can set up the static paths for Nextjs to build up our site.
export const getStaticPaths = async () => {
var result = await getAllEntries('article')
const slugs = result.map((x) => ({ slug: buildSlugFromParent(x.fields, x.fields.slug) }))
const paths = slugs.map((x) => {
return {
params: {
slug: x.slug.split('/'),
},
}
})
return {
paths,
fallback: 'blocking',
}
}
Conclusion
Although this seems to work fine, we are fetching the whole parent objects, when we only need the slug. This might be possible using GraphQL, but I haven't had time to explore that option yet. Another thought was creating a custom component in Contentful, that would generate a read-only slug field using the parent(s), so we could fetch that directly.
This is probably just one or many ways of solving this problem, and I'll be exploring more solutions to this later on.