Using Wildcard Items in Sitecore XM Cloud

The simplest way to use wildcard items in XM Cloud with a headless NextJs front-end.

February 20, 2023

By David Austin

Wildcard items have been used for a fair number of Siteco. For the most part, they’ve all had the same purpose. Load an item, of a particular template, from another location within the Sitecore CMS.

A typical scenario is when authors utilize buckets, and the URL might be https://abccompany.com/insights/2023/10/23/a-simple-story by using a * (aka wildcard) as an item name underneath insights. We can then transform that URL into https://abccompany.com/insights/a-simple-story. But it could also be used to load a-simple-story from another location entirely, outside of the site structure.

This was often accomplished using a content search based on template, filters, etc. to identify the correct item, get its information, put it into the view model, and then display that data based on the Presentation Details associated with the wildcard item accordingly.

The Problem

When it comes to using a wildcard item in XM Cloud, you don’t have the ability to perform that content search in the traditional sense. You can’t use a content resolver as you aren’t hitting the back-end, not to mention a traditional content resolver doesn’t gain access to the HTTP request. All you have is the ability to use GraphQL queries against Experience Edge. So how do you do it? Well, let’s explore the simplest solution.

The Solution

I will acknowledge that this solution came to pass with assistance from Richard Seal - Sitecore Lead Partner Technical Advocate. As it turned out, both of us were trying to solve the problem at the same time, and throughout our discussion, we explored a few different options, but in the end, this ended up being the simplest. I’m certain other developers will extend this or develop their own method.

Ok, let’s get into it.

The [[...path]].tsx file

The secret to making wildcards work in a Headless NextJs front-end application lies in the [[...path]].tsx file located in the src/pages folder of your rendering. If we use the above example here, we need to create an insights folder inside src/pages and then duplicate the [[...path]].tsx file into that new folder. Then rename it to the [...path].tsx as we only want it scoped under the /insights folder. We need to make some modifications inside of said duplicate file, but we’ll get to that.

As the default version will attempt to load pages in this path, depending on the number of pages here, we don’t want it to load all of these pages during build. This can cause errors as the build doesn’t get those items as part of the sitemap build (depending on your environment) but also because the paths for said items will differ.

Modifying getStaticPaths

We will shrink the size of getStaticPaths by removing the sitemapFetcher to prevent loading all the pages during build. Sometimes, you might not want this to be here because you’re loading the pages from another source entirely.

export const getStaticPaths: GetStaticPaths = async (context) => {
  let paths: StaticPath[] = [];
  let fallback: boolean | 'blocking' = 'blocking';
  paths = [];
  fallback = 'blocking';
  return {
    paths,
    fallback,
  };
};

By using fallback set to ‘blocking’ we ensure that these pages aren’t loaded during build.

Modifying getStaticProps

Next up, we need to modify the getStaticProps function to load the layout of our wildcard item. Now, this wildcard item doesn’t need to be in the /insights/* path, but it’s handy if it is.

The first thing to do is store the path the user is navigating into a new parameter in the context. In our case, we store this in context.params.requestPath. Then we can set the path of the context to our wildcard item.

Once that’s done, then we can use the sitecorePagePropsFactory to generate the appropriate layout information from that wildcard item.

export const getStaticProps: GetStaticProps = async (context) => {
  if (context.params) {
    context.params.requestPath = context.params.path;
    context.params.path = [`insights/,-w-,`];
  }
  const props = await sitecorePagePropsFactory.create(context);

  return {
    props,
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 5 seconds
    revalidate: 5, // In seconds
    notFound: props.notFound, // Returns custom 404 page with a status code of 404 when true
  };
};

Creating a Component to Display Wildcard Data

With the layout data from the wildcard item being processed, we now need to display the contents of the insights page that the user accessed. Our simplest way to do this is to assign a JSON Rendering to the Presentation Details of the wildcard item. In our case, we called said component, InsightsPage.

Within the getStaticProps of this new component, we will now pull the data from the item the user was trying to access.

Using GraphQL, we can query the item in question against Experience Edge (or even a local dev environment) and retrieve any required fields. We then pass this data into the component, and in our case, we simply then pass this data into sub-components that make up the page. It’s often handy, though not required when all of the wildcard pages use the same layout.

export const getStaticProps: GetStaticComponentProps = async (
  rendering: any,
  layoutData: any,
  context: any
) => {
  const graphQLClient = new GraphQLClient(config.graphQLEndpoint);
  graphQLClient.setHeader('sc_apikey', config.sitecoreApiKey);
  const insightPath = context?.params?.requestPath;
  const basePath = '/sitecore/unstructured data/insights/';
  const itemPath =
    basePath + insightPath.pop().replaceAll('-', ' '); // Adjust depending on naming convention
  const query = gql`
    query {
      item(path: "${itemPath}", language: "en") {
        id
        name
        ... on InsightPage {
          pageTitle {
            value
          }
          body {
            value
          }
        }
      }
    }
  `;
  const data = await graphQLClient.request(query);
  return {
    context: layoutData?.sitecore?.route,
    insightData: data,
  };
};

And Voila

You can obviously extend the above to meet your needs. Still, the beauty here is that the entire wildcard item loading is all done in the front-end without the need for any complex content resolvers, controllers, content searches, etc. This solution effectively works not only in XM Cloud but also if you have access to a CM/CD environment. So you’ve effectively future-proofed your solution if your client decides to move into XM Cloud in the future if they aren’t already.

Image of Fishtank employee David Austin

David Austin

Development Team Lead | Sitecore Technology MVP x 3

David is a decorated Development Team Lead with Sitecore Technology MVP and Coveo MVP awards, as well as Sitecore CDP & Personalize Certified. He's worked in IT for 25 years; everything ranging from Developer to Business Analyst to Group Lead helping manage everything from Intranet and Internet sites to facility management and application support. David is a dedicated family man who loves to spend time with his girls. He's also an avid photographer and loves to explore new places.