Create Complex URLs (The Route Resolver)

Beta

These docs rely on Slice Machine, which is in active development and subject to change. Content Relationships are not currently supported with Slices.

This article will teach you about how to create nested URLs for your Prismic documents using the Route Resolver.


⚠️ Before you can Build these Routes

To use this advanced route builder you will need to have Slice Machine enabled in your repository. To do this, either launch a new Slice Machine project or, if you want to use this with an existing repository, reach out to the team on the forum and we can enable it for you.

Route Resolver vs Link Resolver?

You may be asking what the difference between this new Route Resolver and the existing Link Resolver?

Good question. The most obvious difference is that the Route Resolver gets its information server-side which makes it possible to create nested routes to the Grandparent level. While the Link Resolver is limited to client-side singular document data, so you can only create routes with Parent data.

Which Resolver takes priority?

Your project should first try to find the route for the current document in the Link Resolver. If it returns null for that document then it calls the Route Resolver.

Custom Type examples

Below, we will walk through an example project with three custom types to explore how this works in more detail. First we'll have a look at the custom types that we want to link together to create our routes.

You can use these Custom Types to test the route resolver in your local project. You'll need to add these in the Custom Types section of your Prismic dashboard, you can set them all as repeatable.

1. Article.json

Below we have a Custom Type called 'Article', this is where we'll add our page content. To configure our 'Article' to have a parent category we need to add a Content Relationship field to link to a document that will contain the data for that category. The Content Relationship allows us to limit selections for this field to only one custom type.

2. Category.json

Now we have a category custom type where we can add the information for the parent category including the UID and display name. We also include another Content Relationship field where we link to the parent of this document and grandparent of the 'Article' custom type with the 'Section' custom type.

3. Section.json

Finally, we have the Section custom type which is very similar to the category type.

  • 1. Article.json
  • 2. Category.json
  • 3. Section.json
Copy
{
  "Main": {
    "title": {
      "type": "StructuredText",
      "config": {
        "single": "heading1, heading2, heading3, heading4, heading5, heading6",
        "label": "title"
      }
    },
    "content": {
      "type": "StructuredText",
      "config": {
        "multi": "paragraph, preformatted, heading1, heading2, heading3, heading4, heading5, heading6, strong, em, hyperlink, image, embed, list-item, o-list-item, o-list-item",
        "label": "Content"
      }
    }
  },
  "Meta": {
    "uid": {
      "type": "UID",
      "config": {
        "label": "slug"
      }
    },
    "category": {
      "type": "Link",
      "config": {
        "select": "document",
        "customtypes": [
          "category"
        ],
        "label": "category"
      }
    }
  }
}
Copy
{
  "Main": {
    "section": {
      "type": "Link",
      "config": {
        "select": "document",
        "customtypes": [
          "section"
        ],
        "label": "Section"
      }
    },
    "uid" : {
      "type" : "UID",
      "config" : {
        "label" : "category ID"
      }
    },
    "display_name": {
      "type": "StructuredText",
      "config": {
        "single": "heading1, heading2, heading3, heading4, heading5, heading6",
        "label": "Display Name"
      }
    },
    "summary": {
      "type": "StructuredText",
      "config": {
        "multi": "paragraph, preformatted, heading1, heading2, heading3, heading4, heading5, heading6, strong, em, hyperlink, image, embed, list-item, o-list-item, o-list-item",
        "label": "summary"
      }
    }
  }
}
Copy
{
  "Main" : {
    "uid" : {
      "type" : "UID",
      "config" : {
        "label" : "section ID"
      }
    },
    "display_name" : {
      "type" : "StructuredText",
      "config" : {
        "single" : "heading1, heading2, heading3, heading4, heading5, heading6",
        "label" : "Display Name"
      }
    }
  }
}

⚠️ Creating Content Relationships

Remember to constrain your content relationship to one custom type or you'll receive the following error "Invalid resolver parentCategory\nIt must be a content relationship linked to one and only one custom type"

Where to add your 'Routes' config?

For Next.js create a Router section in your prismic-configuartion.js config file or similar file like shown below. This filename is completely arbitrary and is just a file to contain configs that might be used every in your project, you can see an example in the tutorial series project. In this file, we can initialise our Client function for queries and pass it the Routes option so it is used everywhere, read more about queries here.

Copy
export const Router = {
  routes: [{"type":"page","path":"/:uid"}],
  href: (type) => {
    const route = Router.routes.find(r => r.type === type);
    return route && route.href;
  }
};

export const Client = (req = null, options = {}) => (
  Prismic.client(apiEndpoint, Object.assign({ routes: Router.routes }, options))
);

Route Resolver examples

Using your routes configuration, you will use the above custom type examples to create various combinations of routes. Each example below requires a different document structure in your Next.js project.

Depth Limit

The route resolver is limited to retrieving data from 3 levels deep, so /:grandparent/:parent/:uid-of-main-doc. Anything deeper than this is not possible.

1. Basic case

Copy
{
  type: 'article',
  path: '/:uid'
}

1.1 Document structure

~/pages/[uid].js

1.2 Static paths

next-slicezone and its associated lifecycle hook useGetStaticPaths, queries the documents for you and passes the data to formatPath field like so:

next-slicezone version

The following example uses the latest version of the next-slicezone package (0.1.0-alpha.0), which changes how values from the parameters are called. Learn more about next-slicezone's Lifecycle hooks.

To install this run npm install next-slicezone@0.1.0-alpha.0

Copy
export const getStaticPaths = useGetStaticPaths({
  client: Client(),
  type: "blog_post",
  formatPath: (prismicDocument) => {
    return {
      params: {
        uid: prismicDocument.uid,
      },
    }
  },
});

2. Multilang

To add multi-language routes, declare the lang and uid values to the routes.

Copy
{
  type: 'article',
  path: '/:lang/:uid'
}

2.1 Document structure

~/pages/[lang]/[uid].js

2.2 Static paths

next-slicezone and its associated lifecycle hook useGetStaticPaths, queries the documents for you and passes the data to the formatPath field like so:

next-slicezone version

The following example uses the latest version of the next-slicezone package (0.1.0-alpha.0), which changes how values from the parameters are called. Learn more about next-slicezone's Lifecycle hooks.

To install this run npm install next-slicezone@0.1.0-alpha.0

Copy
export const getStaticPaths = useGetStaticPaths({
  client: Client(),
  type: "blog_post",
  formatPath: (prismicDocument) => {
    return {
      params: {
        uid: prismicDocument.uid,
        lang: prismicDocument.lang,
      },
    }
  },
});

3. With a parent

This example requires a resolver field to match the Content Relationship in the custom type (It will automatically match the UID of the related doc). In the example below, the category resolver requires that relationship to be filled; otherwise, it will trigger an error.

Copy
{
  type: 'article',
  path: '/:category/:uid',
  resolvers: {
    category: 'category' // id of the content relationship in the article mask
  }
}

3.1. Document structure

~/pages/[category]/[uid].js

3.2. Static paths

Matching the paths for this one is a little different because category isn't a keyword, so we have to get this from the document. Thanks to the route resolver's work client-side this information is provided in the url field. So if you have a document with the UID: shoes and the Category: clothes, the URL will be returned as /clothes/shoes.

We then have to break this URL up into separate values, which getStaticPaths expects, using split().

next-slicezone version

The following example uses the latest version of the next-slicezone package (0.1.0-alpha.0), which changes how values from the parameters are called. Learn more about next-slicezone's Lifecycle hooks.

To install this run npm install next-slicezone@0.1.0-alpha.0

Copy
export const getStaticPaths = useGetStaticPaths({
  client: Client(),
  formatPath( doc ) {
    const [category, uid] = doc.url.split('/')
    return {
      params: {
        category,
        uid
      }
    }
  }
});

4. With an optional parent

In the example below, the Route Resolver will check the content relationship to create a URL like: /my-category/my-uid. However, if the category doesn't exist, the URL will only be /my-uid. This is because the question mark indicates that the category URL segment is optional, and the resolver won't create an error if the category field is empty.

Document Structure: ~/pages/[category]/[uid].js

or

Document Structure: ~/pages/[uid].js

Copy
{
  type: 'article',
  path: '/:category?/:uid',
  resolvers: {
    category: 'category' // id of the content relationship in the article mask
  }
}

Pages Directory

Remember you must also have files in your Pages directory that can handle both of these options so that your project doesn't break.

5. With multiple parents (Grandparents)

This is the most complex of our examples. Here we have the page UID, the parent category, the grandparent section, and the lang. We again have to include the resolver for the parent category, and for the section, or grandparent. To specify the grandparent, show the content relationship with the dot notation, like so: category.section. Again, if these fields aren't defined, the resolver will throw an error.

Document Structure: ~/pages/[section]/[category]/[uid].js

Copy
{
  type: 'article',
  path: '/:section/:category?/:uid',
  resolvers: {
    category: 'category', // API ID of the content relationship in the Custom Type
    section: 'category.section'
  }
}

5.1. Document structure

~/pages/[section]/[category]/[uid].js

5.2. Static paths

Again section and category isn't a keyword, so we have to get this from the the url field as described above.

next-slicezone version

The following example uses the latest version of the next-slicezone package (0.1.0-alpha.0), which changes how values from the parameters are called. Learn more about next-slicezone's Lifecycle hooks.

To install this run npm install next-slicezone@0.1.0-alpha.0

Copy
export const getStaticPaths = useGetStaticPaths({
  client: Client(),
  fallback: process.env.NODE_ENV === "development",
  formatPath( doc ) {
    const [section, category, uid] = doc.url.split('/')
    return {
      params: {
        section,
        category,
        uid
      }
    }
  }
});

Related Articles


Was this article helpful?
Not really
Yes, Thanks

Can't find what you're looking for? Get in touch with us on our Community Forum.