Vue Storefront is now Alokai! Learn More
Unified architecture

Unified architecture

There are many CMSs available on the market, each with its own method of structuring content. Often, the components used to display content in your Storefront are tightly coupled with the CMS you are using, making it challenging to switch between CMSs without rewriting your Storefront code.

In Alokai, CMS modules are designed with a unified architecture, meaning the same components are used across all our CMS modules. This makes it easier for you to switch between CMSs.

Communication overview

Every CMS integration features two communication layers working together:

  • API Client: A client which registers new endpoints in Alokai's Server Middleware and fetches content from the CMS API.
  • SDK Module: An Alokai SDK module used in the Storefront which communicates with the API Client and sets up the Live Preview feature.

API Client of every CMS integration exposes a unified getPage() API method. It allows you to fetch CMS page content for a specific path and locale. To call it from within the Storefront, you should use the corresponding getPage() method exposed by Alokai's SDK in a unifiedCms namespace.

import { getSdk } from '@/sdk/sdk.server';

const sdk = getSdk();

const cmsPage = await sdk.unifiedCms.getPage({
  path: '/about',
  locale: 'en',
});

In addition to the unified getPage() method, our CMS integrations provide access to raw methods from platform-specific SDKs, which you can access through the dedicated Alokai SDK namespace. They can be useful when you need to fetch individual components or use complex queries.

const { responses } = await sdk.amplience.fetchContentItems({
  requests: [
    { id: '09099a62-2416-4f6d-9072-48e9b33eced0' }
  ],
});

For a complete list of available raw methods, refer to Typescript interfaces of the selected CMS integration.

Normalization overview

After the API Client fetches CMS content for a page, it recursively processes the CMS content object and:

  • identifies CMS-specific structures,
  • converts the structures into a format compatible with Alokai's CMS components.

This is accomplished through a set of functions called normalizers, which are used internally by the API Client and can be customized through its configuration.

Normalizers in CMS integrations serve the same role as normalizers in eCommerce integrations. You can read more about the eCommerce normalizers here.

The content from the CMS is often structured in a way that is specific to the CMS. We can see this in the example below, where the same content, built on Editorial Content Type, is structured completely differently in Amplience and Contentful:

Amplience
{
  "_meta": {
    "deliveryId": "6dad3f8e-1d78-406b-ab3f-a127e187a298",
    "deliveryKey": "test-editorial",
    "name": "Editorial",
    "schema": "https://www.vuestorefront.io/editorial.json"
  },
  "component": "Editorial",
  "content": {
    "_meta": {
      "schema": "http://bigcontent.io/cms/schema/v1/core#/definitions/localized-value"
    },
    "values": [
      {
        "locale": "en-GB",
        "value": {
          "content": "## **Shop by category now dude!**"
        }
      },
      {
        "locale": "de-DE",
        "value": {
          "content": "## **Einkaufen nach Kategorie**"
        }
      }
    ]
  }
}

Amplience uses a simple JSON structure to store the content, and it returns rich text content as Markdown. Contentful, on the other hand, uses a more complex JSON structure to store the content, and it returns rich text content as a nested JSON object, which then can be rendered as HTML. The goal of the normalizers is to convert both of these structures into a common format that can be used by the Storefront Components. In this case, the normalized content for Editorial component would look like this:

{
  "component": "Editorial",
  "content": "<h2><b>Shop by category</b></h2><p></p>",
  "id": "63N1GV6YBIoCw9CCqbPMMW",
  "uniqueClass": "cms-component-63N1GV6YBIoCw9CCqbPMMW",
}

Then, the Storefront Components can use this normalized content to render the Editorial component in the Storefront.

Customizing normalizers

Sometimes,you might need to customize the normalizers to fit your specific use case. For example, you might want to change the way the Editorial component is rendered in the Storefront. You can do this by overriding the default normalizer in your integration's API Client configuration:

apps/storefront-middleware/sf-modules/cms-<cms-name>/config.ts
export const config = {
  location: "@vsf-enterprise/<cms-name>-api/server",
  configuration: {
    unified: {
      normalizers: {
        override: {
          normalizeMarkdown: (context, input) => {
            return {
              // your custom normalized object
            }
          },
        },
      },
    },
  },
};

Each normalizer function receives context and input as arguments. The context parameter gives you access to:

  • the normalizers property (i.e. all normalizers that you can use in your custom implementation),
  • various data-transforming utilities that you might find useful in your custom implementation.

Some normalizers need to call other normalizers to ensure the entire content object tree is fully traversed. For this purpose, we recommend using the universal normalizeContent normalizer. It accepts the input, identifies its type, and delegates the processing to the appropriate normalizer function.

apps/storefront-middleware/sf-modules/cms-<cms-name>/config.ts
export const config = {
  location: "@vsf-enterprise/<cms-name>-api/server",
  configuration: {
    unified: {
      normalizers: {
        override: {
          normalizeComponent: (context, input) => {
            const {
              id,
              component,
              ...remainingFields
            } = input;

            const normalizedFields = context.normalizers.normalizeContent(remainingFields);

            return {
              id,
              component,
              ...normalizedFields
            }
          },
        },
      },
    },
  },
};

Adding custom fields

If you want to add some custom field to the normalized content, you can use the addCustomField parameter in the configuration:

apps/storefront-middleware/sf-modules/cms-<cms-name>/config.ts
export const config = {
  location: "@vsf-enterprise/<cms-name>-api/server",
  configuration: {
    unified: {
      normalizers: {
        addCustomFields: [{
          normalizeMarkdown: (context, input) => {
            return {
              someCustomField: "someCustomValue"
            }
          },
        }],
      },
    },
  },
}

Similar to overridden normalizers, the callback receives context and input as arguments. It returns an object containing the custom fields, which is then added to the normalized object as the $custom property. This approach clearly distinguishes custom fields and prevents naming conflicts with other fields.

const normalizedObject = {
  someField: 'someValue',
  $custom: {
    someCustomField: 'someCustomValue'
  }
};