Multistore

Since version 2.0.0, Vue Storefront's integration for SAP Commerce Cloud allows you to work with multiple Base Sites and Base Stores within a single application instance.

Good to know

Before you follow this guide, we strongly recommend you read the official SAP Commerce Cloud documentation on Base Sites (opens new window) and Base Stores (opens new window).

Assumptions

We have created our multistore implementation based on the standard Base Site & Base Store setup provided by SAP Comerce Cloud:

  • the electronics Base Site featuring the electronics Base Store and the electronicsProductCatalog
  • the apparel-uk Base Site featuring the apparel-uk Base Store and the apparelProductCatalog

Configuration

Multistore configuration is pretty much the same as the standard one described in the Configuration section of our docs. There are, however, a few minor differences to keep in mind.

Good to know

Whenever you change some store settings (e.g. defaultCurrency) in your SAP Backoffice, you will need to re-build and re-deploy your application.

Middleware configuration

To support two or more SAP stores, you should use the VSF multi-store extension.

  1. First, add a multi-store extension to your middleware.config.js file
# middleware.config.js
require('dotenv').config();
+ const { multistoreExtension } = require('@vsf-enterprise/multistore');
module.exports = {
  integrations: {
    sapcc: {
      location: '@vsf-enterprise/sapcc-api/server',
+     extensions: (extensions) => [
+       ...extensions,
+       multistoreExtension
+     ],
      configuration: {
        OAuth: {
          uri: process.env.SAPCC_OAUTH_URI,
          clientId: process.env.SAPCC_OAUTH_CLIENT_ID,
          clientSecret: process.env.SAPCC_OAUTH_CLIENT_SECRET,
          tokenEndpoint: process.env.SAPCC_OAUTH_TOKEN_ENDPOINT,
          tokenRevokeEndpoint: process.env.SAPCC_OAUTH_TOKEN_REVOKE_ENDPOINT,
          cookieOptions: {
            'vsf-sap-token': { secure: process.env.NODE_ENV !== 'development' }
          }
        },
        api: {
          uri: process.env.SAPCC_API_URI,
          baseSiteId: 'electronics',
          catalogId: 'electronicsProductCatalog',
          catalogVersion: 'Online',
          defaultLanguage: 'en',
          defaultCurrency: 'USD'
        }
      }
    }
  }
};

At this point, configuration.api contains the default store configuration.

  1. Create a multistore.config.js file with the following methods:
  • fetchConfiguration({ domain }): Record<string, StoreConfig> - fetches store configuration. The method accepts the domain as an argument and returns with the store-specific configuration based on the domains where the domain is a key and the configuration is a value.
  • mergeConfigurations({ baseConfig, storeConfig }): StoreConfig - overwrites base configuration with store-specific config.
  • cacheManagerFactory(): { get: (key: string) => StoreConfig, set(key: string, value: StoreConfig)} - creates cache manager with get and set methods.

This file could look like the example below. It uses node-cache (opens new window) package as a cache manager.

// multistore.config.js

const NodeCache = require('node-cache');

module.exports = {
  fetchConfiguration({ domain }) {
    return {
      'apparel.mystore.io': {
        baseSiteId: 'apparel-uk',
        catalogId: 'apparelProductCatalog',
        catalogVersion: 'Online',
        defaultLanguage: 'en',
        defaultCurrency: 'GBP'
      },
      'electronics.mystore.io': {
        baseSiteId: 'electronics',
        catalogId: 'electronicsProductCatalog',
        catalogVersion: 'Online',
        defaultLanguage: 'en',
        defaultCurrency: 'USD'
      },
      'sap.localhost.apparel:3000': {
        baseSiteId: 'apparel-uk',
        catalogId: 'apparelProductCatalog',
        catalogVersion: 'Online',
        defaultLanguage: 'en',
        defaultCurrency: 'GBP'
      },
      'localhost:3000': {
        baseSiteId: 'electronics',
        catalogId: 'electronicsProductCatalog',
        catalogVersion: 'Online',
        defaultLanguage: 'en',
        defaultCurrency: 'USD'
      }
    };
  },
  mergeConfigurations({ baseConfig, storeConfig }) {
    return {
      ...baseConfig,
      api: {
        ...baseConfig.api,
        ...storeConfig
      }
    };
  },
  cacheManagerFactory() {
    const client = new NodeCache({
      stdTTL: 10
    });

    return {
      get(key) {
        return client.get(key);
      },
      set(key, value) {
        return client.set(key, value);
      }
    };
  }
};
  1. Add multi-store configuration to the middleware.config.js file.
# middleware.config.js

require('dotenv').config();
+ const multistore = require('./multistore.config');
const { multistoreExtension } = require('@vsf-enterprise/multistore');

module.exports = {
  integrations: {
    sapcc: {
      location: '@vsf-enterprise/sapcc-api/server',
      extensions: (extensions) => [
        ...extensions,
        multistoreExtension
      ],
      configuration: {
        OAuth: {
          uri: process.env.SAPCC_OAUTH_URI,
          clientId: process.env.SAPCC_OAUTH_CLIENT_ID,
          clientSecret: process.env.SAPCC_OAUTH_CLIENT_SECRET,
          tokenEndpoint: process.env.SAPCC_OAUTH_TOKEN_ENDPOINT,
          tokenRevokeEndpoint: process.env.SAPCC_OAUTH_TOKEN_REVOKE_ENDPOINT,
          cookieOptions: {
            'vsf-sap-token': { secure: process.env.NODE_ENV !== 'development' }
          }
        },
        api: {
          uri: process.env.SAPCC_API_URI,
          baseSiteId: 'electronics',
          catalogId: 'electronicsProductCatalog',
          catalogVersion: 'Online',
          defaultLanguage: 'en',
          defaultCurrency: 'USD'
        },
+       multistore
      },
    }
  }
};
  1. That's all. Now, based on the domain, the middleware will know how to overwrite the config to use a store-specific one to perform proper SAPCC API requests.

Nuxt configuration

A single SAP Store can have multiple languages assigned to it. To handle such a setup properly, Vue Storefront should:

  • know about all languages it needs to support
  • know which language should be treated as the default one
  • have translation files for all supported languages in the /lang directory

That information is required by the i18n module to generate a proper routing (opens new window) structure for multiple languages. For example, if our apparel-uk and electronics stores supported a total of three languages (English as default, German and Japanese), our i18n module configuration should look like this:

i18n: {
  defaultLocale: 'en',
  locales: [
    { code: 'en', label: 'English', file: 'en.js', iso: 'en' },
    { code: 'de', label: 'German', file: 'de.js', iso: 'de' },
    { code: 'ja', label: '日本語', file: 'ja.js', iso: 'ja' }
  ]
}

Assuming we are using the prefix_except_default (opens new window) strategy, the following routing structure would be generated:

[
  {
    path: "/",
    name: "index___en"
  },
  {
    path: "/de",
    name: "index___de"
  },
  {
    path: "/ja",
    name: "about___ja"
  }
]

Good to know

Currently Vue Storefront does not support unprefixed routes (opens new window) for non-default locales.

Local development

The simplest way to work with a multistore setup locally is by adding new domains to the /etc/hosts file:

127.0.0.1       localhost
127.0.0.1       sap.localhost.apparel
127.0.0.1       sap.localhost.electronics

This way you will be able to access your application - running by default on localhost:3000 - with different domains (e.g. sap.localhost.apparel:3000). Remember to add store-specific config to your multistore.config.js.

Flow breakdown

As mentioned in the Configuration section, Vue Storefront uses a simple domain detection mechanism to load the desired store configuration and serve store-specific data to the users. The diagram below presents a sequence of events happening on the initial application load before the first Vue component is mounted.

As you can see, the flow features two main actors: LoadStore plugin on the front end and the Store Service in the API Middleware. The former is executed only once during the first application load. The latter is used for every request to ensure they are sent with the right baseSiteId. This - combined with locale and currency cookies coming from the frontend - allows Vue Storefront to serve store-specific data to the users in the right language and with the right currencies.