Back Arrow
From the blog

Dynamic URL routing with Kontent.ai

We'll consider the top-to-bottom approach for modeling content relationships, as it is more user-friendly for content editors working in the Kontent.ai admin interface.

Headless CMS has been gaining popularity recently, and many common CMS platforms are developing their own headless versions. The Kentico platform is no exception. Its headless version is called Kontent AI. Interestingly, it has been around for quite some time, yet it's only recently that larger customers have started to take notice.

In traditional CMS platforms because they are website-focused, content is organized in a tree structure, and URLs are often automatically assigned by a CMS based on the content's position in a tree.

Since Kontent AI is a Headless CMS and designed to be multi-channel rather than website-centric, the concept of associating a URL with a content item is not implemented out of the box.  

Similar to many other Headless CMS platforms, developers should implement custom code creating and storing URLs for content items, and vice-versa locating specific content based on a given URL.  

There are two approaches for modeling content relationships to implement something like a traditional CMS "tree":

  1. Top-to-bottom, where the piece of content stores references to its child pages, however, child pages are not aware of who their parent is
  2. And bottom-to-top, where the content refers to its parent, but the parent may not know it's children =)

Both approaches have pros and cons, however the implementation in code will be very similar. We'll focus on the top-to-bottom approach, as it is more user-friendly for content editors working in the Kontent.ai admin interface. This approach allows viewing all child pages of a selected content and setting restrictions on the types of child pages.  

Content is stored in a flat repository, and connections between content items are configured in the properties of the content itself.

In addition to managing content connections for the tree, it's also necessary to link content with its generated URL. For large applications, storing this mapping in an index that's built and updated as new content is added makes sense. For smaller projects, storing the URL directly in the content is an option, which is also convenient for content editors.  

For automatic URL generation, Kontent AI offers the ability to create a Custom Element as a content type property and fill it in via an external service. You should also add a URL Slug property to the content, which automatically generates an SEO-friendly URL segment based on one of the content properties, typically, content's name.  

Essentially, you need to create:  

- A "Children" property (Linked items type)to store all child pages;
- A URL Slug (URL Slug type)for generating the last part of the content's URL;
- A "Tree URL" (Custom Element type)for the final URL generation.  

It's crucial to keep property names consistent across all content types since external code will rely on these property names. While using a Content-Type Snippet can unify common properties and expedite new type creation, it also limits the ability to customize these properties for different content types (to specify a different base property for URL Slug or set restrictions for Children). For example, to implement restrictions, such as allowing a child page titled "News Catalog" to only use the content of the "News" type.

A "Children" property
A URL Slug
A "Tree URL"

To generate the final URL, you need to create a code that accesses the Custom Element. The final URL will be constructed from the parent page's URL, combined with the current content's URL Slug. Since the URL Slug property isn't a system property, it must be passed as a parameter, i.e. to allow its reading (specify in Allow the custom element to read values of specific elements), the remaining necessary parameters are passed by default.

To locate the parent page, you need to request it from Kontent AI through the provided package Kontent.Ai.Delivery for .Net or kontent-ai/delivery-sdk for JS. These packages will allow you to create a Delivery Client with the specific ID and environment key.

Finding the parent page involves identifying content that includes the current page in its 'Children'. In a top-down tree, multiple pages might fit this criterion- in this case, you can select the first element from the list after sorting by name or ID. In a bottom-up tree, the parent page is identified by passing its ID or codename as a parameter. There may be no parent page if the current content is located at the root of the tree. In this case, the generated URL will be identical to the URL Slug of this page.

const KontentDelivery = require('@kontent-ai/delivery-sdk');

function (context, req) {

    let urlSlug = req.body.urlSlug;
    const language = req.body.language;
    const codename = req.body.codename;

    //Get a client for searching content in Kontent AI
    const deliveryClient = KontentDelivery.createDeliveryClient({
        environmentId: req.body.projectId,
        previewApiKey: <ApiKey>,
        defaultQueryConfig: {
            usePreviewMode: true
        }
    });    

    //Request content whose source page is a child page. In the case of a bottom-up tree, request the parent content by its ID
    const response = await deliveryClient.items().languageParameter(language).anyFilter('elements.children', [codename]).toPromise();

    let parentUrl = '/' + language;

    //Exclude the case where the page is a child of itself
    if (response.data.items.length) {
        response.data.items = response.data.items.filter(x => x.system.codename !== codename);
    }

    //Get the full parent URL and substitute the current language in case of a language mismatch
    if (response.data.items.length) {
        const item = response.data.items[0];

        if (item.elements.treeurl) {
            parentUrl = item.elements.treeurl.value;
        }

        if (item.system.language !== language) {
            const tokens = parentUrl.split('/');
            tokens[1] = language;
            parentUrl = tokens.join('/');
        }
    }

    //If URL Slug is not provided in the parameters, request it through the client; if unsuccessful, set URL Slug as codename
    if (!urlSlug || urlSlug === '') {
        const urlSlugResponse = await deliveryClient.item(codename).languageParameter(language).toPromise();
        urlSlug = urlSlugResponse.data.item.elements.url.value;
    }

    if (!urlSlug || urlSlug === '') {
        urlSlug = codename;
    }

    // Generate the final URL by concatenating it with the parent URL and verify its uniqueness
    let pageUrl = parentUrl + '/' + urlSlug;
    const uniqueResponse = await deliveryClient.items()
        .languageParameter(language)
        .equalsFilter('elements.treeurl', pageUrl)
        .notEqualsFilter('system.id', req.body.contentItemId)
        .toPromise();

    //If the generated URL is not unique, append the content ID to the end of the URL
    if (uniqueResponse.data.items.length) {
        pageUrl = pageUrl + '-' + req.body.contentItemId;
    }

    context.res = {
        headers: { "Content-Type": "application/json" },
        status: 200,
        body: {
            success: true,
            pageUrl: pageUrl
        }
    };
}

To display a Custom Element, you need an HTML markup that will set the value for this element upon initialization, using the generated code. To initialize a Custom Element and access its parameters, you need to import the JS script from Kontent AI and call the init function, which takes as parameters the element itself and the context containing data about the content and project.  

For more details on working with Custom Elements, refer to the documentation.

This approach replicates the URL creation process similar to traditional tree structures. The final step is to develop a mechanism to retrieve content based on a specific URL. Since the content already stores its URL, you can use the Delivery Client (JS or .net) to match the requested URL with the TreeUrl of the page.  

In summary, while Headless CMS platforms are gaining popularity and have many advantages, they sometimes require additional development effort for features that were previously standard. However, they encourage a shift away from tree structures towards shorter, more flexible URLs. This is particularly useful for applications with uniform content types, such as news feeds, or when content is categorized by various criteria without a single "correct" parent.

Conclusion

It's easy to start working with us. Just fill the brief or call us.

Find out more
White Arrow
From the blog
Related articles

Mastering advanced tracking with Kentico Xperience

Dmitry Bastron

We will take you on a journey through a real-life scenario of implementing advanced tracking and analytics using Kentico Xperience 13 DXP.

Kentico
Devtools

Sitecore integration with Azure Active Directory B2C

Dmitry Bastron

We would like to share our experience of integrating Sitecore 9.3 with the Azure AD B2C (Azure Active Directory Business to Consumer) user management system.

Sitecore
Azure

Activity logging with Xperience by Kentico

Dmitry Bastron

We'll dive into practical implementation in your Xperience by Kentico project. We'll guide you through setting up a custom activity type and show you how to log visitor activities effectively.

Kentico

Why is Kentico of such significance to us?

Anastasia Medvedeva

Kentico stands as one of our principal development tools, we believe it would be fitting to address why we opt to work with Kentico and why we allocate substantial time to cultivating our experts in this DXP.

Kentico

Interesting features of devtools for QA

Chrome DevTools serves as a developer console, offering an array of in-browser tools for constructing and debugging websites and applications.

Devtools
QA

Where to start learning Sitecore - An interview with Sitecore MVP Anna Gevel

Anna Gevel

As a software development company, we at Byteminds truly believe that learning and sharing knowledge is one of the best ways of growing technical expertise.

Sitecore

Sitecore replatforming and upgrades

Anastasia Medvedeva

Our expertise spans full-scale builds and support to upgrades and replatforming.

Sitecore

Kentico replatforming and upgrades

Anastasia Medvedeva

Since 2015, we've been harnessing Kentico's capabilities well beyond its core CMS functions.

Kentico

Umbraco replatforming and upgrades

Anastasia Medvedeva

Our team boasts several developers experienced in working with Umbraco, specialising in development, upgrading, and replatforming from other CMS to Umbraco.

Umbraco

How we improved page load speed for Next.js ecommerce website by 50%

Sergei Pestov

How to stop declining of the performance indicators of your ecommerce website and perform optimising page load performance.

Next.js

Sitecore Personalize: tips & tricks for decision models and programmable nodes

Anna Gevel

We've collected various findings around decision models and programmable nodes working with Sitecore Personalize.

Sitecore

Fixed Price, Time & Materials, and Retainer: How to Choose the Right Agreement for Your Project with Us

We will explain how these agreements differ from one another and what projects they are suitable for.

Customer success

Enterprise projects: what does a developer need to know?

Let's talk about what enterprise development is, what nuance enterprise projects may have, and which skills you need to acquire to successfully work within the .NET stack.

Development

Headless CMS. Identifying Ideal Use Cases and Speeding Up Time-to-Market

Andrey Stepanov

All you need to know about Headless CMS. We also share the knowledge about benefits of Headless CMS, its pros and cons.

Headless CMS

Troubleshooting tracking and personalisation in Sitecore XM Cloud

Anna Gevel

One of the first things I tested in Sitecore XM Cloud was embedded tracking and personalisation capabilities. It has been really interesting to see what is available out-of-the-box, how much flexibility XM Cloud offers to marketing teams and what is required from developers to set it up.

Sitecore
This website uses cookies. View Privacy Policy.