Vue Storefront is now Alokai! Learn More
Multistore configuration

Multistore configuration

Multistore allows different store configurations to coexist within a single Storefront and Server Middleware instance.

How it works?

Multi-store is an agnostic extension for Middleware that adjusts configuration depending on the incoming request's address.

The storefront is using @vue-storefront/multistore package to handle the multistore configuration and adjust the configuration based on the incoming request's address.

To learn more about the details of this solution, see the Multistore documentation.

Configuration

The configuration of the multistore is split between the Storefront and the Middleware.

Middleware

In the Alokai Storefront, the multistore configuration is located in the apps/storefront-middleware/integrations/<integration>/extensions/multistore.ts file.

It may look like this:

/integrations/<integration>/extensions/multistore.ts
import { createMultistoreExtension } from "@vue-storefront/multistore";
import NodeCache from "node-cache";

const { MULTISTORE1_CHANNEL_ID, MULTISTORE2_CHANNEL_ID } = process.env;

/**
 * This is an example of a multistore configuration.
 * Read more here: https://docs.vuestorefront.io/storefront/features/advanced/multi-store-configuration
 */
export const multistoreExtension = createMultistoreExtension({
  fetchConfiguration: () => ({
    "dev.vsf.local": {
      origin: MULTISTORE1_ORIGIN,
      siteId: MULTISTORE1_SITE_ID,
    },
    "dev.client.local": {
      origin: MULTISTORE2_ORIGIN,
      siteId: MULTISTORE2_SITE_ID,
    },
  }),
  mergeConfigurations({ baseConfig, storeConfig }) {
    return Object.assign(baseConfig, storeConfig);
  },
  cacheManagerFactory() {
    const client = new NodeCache({
      stdTTL: 10,
    });

    return {
      get(key) {
        return client.get(key);
      },
      set(key, value) {
        return client.set(key, value);
      },
    };
  },
});

Let's assume that you have a different store for specific countries where you sell your products. Each store has its domain:

  • pl.mycommerce.com
  • de.mycommerce.com

First, you need to list all the domains that are used in the multistore setup as storeDomainsList in the apps/storefront-middleware/middleware.config.ts file.

/middleware.config.ts
/**
 * List of domains that are used in the multistore setup.
 */
export const listOfStoresDomains = ["pl.mycommerce.com", "de.mycommerce.com"];

Then, in the same file, you need to define what configuration should be fetched for each domain:

/integrations/<integration>/extensions/multistore.ts
const { MULTISTORE1_ORIGIN, MULTISTORE1_SITE_ID, MULTISTORE2_ORIGIN, MULTISTORE2_SITE_ID } =
  process.env;

/**
 * This is an example of a multistore configuration.
 * Read more here: https://docs.vuestorefront.io/storefront/features/advanced/multi-store-configuration
 */
export const multistoreExtension = createMultistoreExtension({
  fetchConfiguration: () => ({
    "pl.mycommerce.com": {
      origin: MULTISTORE1_ORIGIN,
      siteId: MULTISTORE1_SITE_ID,
    },
    "de.mycommerce.com": {
      origin: MULTISTORE2_ORIGIN,
      siteId: MULTISTORE2_SITE_ID,
    },
  }),
  // ...
};

Before, you had some default store configuration in apps/storefront-middleware/integrations/<integration>/config.ts.

The fetchConfiguration method will fetch the configuration for the specific domain and merge it with the default configuration.

Now, origin and siteId for pl.mycommerce.com will be fetched from the environment variables MULTISTORE1_ORIGIN and MULTISTORE1_SITE_ID and for de.mycommerce.com from MULTISTORE2_ORIGIN and MULTISTORE2_SITE_ID.

E-commerce specific configurations

Any value of the config could be customized based on the domain. In this example, we are using origin and siteId which would work for SFCC integration. For other platforms, you may need to adjust the configuration keys. You can see more commerce-specific multistore configurations in the E-commerce platforms section.

As a next step, you need to enable the multistore by setting the IS_MULTISTORE_ENABLED=true in the .env file.

/.env
IS_MULTISTORE_ENABLED=true

# ...

The Middleware will now adjust the configuration based on the incoming request's address.

Storefront

You need to enable the multistore in the Storefront as well.

First, you need to configure the SDK to communicate with the Server Middleware properly. You need to adjust the apiUrl and ssrApiUrl configured in the Alokai Storefront.

Why?

Modern browsers mandate the use of secure cookies with the SameSite attribute set to Strict, preventing their transmission with cross-site requests. This presents a challenge for multistore setups, where Storefront and Server exist on separate domains. Yet, our infrastructure is designed to overcome this, serving the Middleware as <domain>/api across different domains. The solution requires redirecting all client-side requests to /api rather than directly to the Middleware, with the HTTP Client using the Origin header to identify the correct domain. During SSR, cookie and cross-site request restrictions do not apply, enabling direct calls to the Middleware.

Next.js
// apps/storefront-unified-nextjs/sdk/sdk.config.ts

const options: CreateSdkOptions = {
  middleware: {
-    apiUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
+    apiUrl: '/api',
+    ssrApiUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
  },
  multistore: {
    enabled: process.env.NEXT_PUBLIC_MULTISTORE_ENABLED === 'true',
  },
};

//...

Finally, you need to enable the multistore by setting the proper environment variable in the .env file.

Next.js
+NEXT_PUBLIC_MULTISTORE_ENABLED=true

Now, both the Storefront and the Server Middleware are configured to work with the multistore.

Production deployment

When deploying the Storefront to Alokai Cloud, set up support for multi-store in the frontend deployment configuration. The configuration is typically located in the .github/workflows/continuous-delivery.yml file. Find the section responsible for building the frontend and in the with part, add: multistore_enabled: true.

Next.js
  build-frontend:
    name: Build Frontend
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Build
        uses: vuestorefront/storefront-deployment/build-frontend@v3
        with:
          frontend: "next"
          project_name: ${{ secrets.PROJECT_NAME }}
          cloud_username: ${{ secrets.CLOUD_USERNAME }}
          cloud_password: ${{ secrets.CLOUD_PASSWORD }}
          cloud_region: ${{ secrets.CLOUD_REGION }}
          npm_email: ${{ secrets.NPM_EMAIL }}
          npm_user: ${{ secrets.NPM_USER }}
          npm_pass: ${{ secrets.NPM_PASS }}
+          multistore_enabled: true

If you have already deployed Middleware to Alokai Cloud, go to the Console and set the IS_MULTISTORE_ENABLED environment variable in Middleware to true.

Last, but not least, we highly recommend using the http://<pod-name>:<port> as the base URL for the API in the storefront configuration. This way, the communication done during SSR will be done using the internal network, which is faster and more secure. Typically, it would be http://additional-app-middleware:4000.

Local development

Local development of the multistore setup requires some additional steps to be taken. We will use the nginx server to handle the domains and the mkcert program to generate SSL certificates.

Adding domains to the /etc/hosts file

For the browser to find the entered domain addresses we use, a DNS server is needed. We can use the built-in server in most operating systems. Its configuration is done by editing the /etc/hosts file. In the Windows system, it is located in the System32 directory. In most cases, editing it requires administrator privileges.

In our example, we will use MacOS and the command-line editor nano.

sudo nano /etc/hosts

Then edit the contents of the file by adding two entries for our domains.

##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1       localhost
+127.0.0.1       de.mycommerce.com
+127.0.0.1       pl.mycommerce.com

Installing nginx and mkcert

As mentioned earlier, we need the nginx server. Additionally, for each domain we will use, we need an SSL certificate. We will use the mkcert program to generate them. So let's install these two programs now. In our example, we will use the Homebrew package manager.

brew install nginx mkcert

Running nginx

To start nginx and generate SSL certificates, a lot of work is needed. We simplified this process by preparing a special script.

Go to the Middleware directory and run:

Next.js
FRONTEND_PORT=3000 ts-node-dev ./dev/multistore.ts
./dev/temp/multistore.sh

That's it! The nginx server is running and the certificates have been generated. All you have to do is trust the generated certificates in your system, but don't worry, it's just one command:

mkcert -install

Running Middleware and Storefront

Run yarn dev:nuxt:multistore for Nuxt, or yarn dev:next:multistore for Next from the root directory and then type in the browser its url with https:// protocol. i.e. https://de.mycommerce.com.

Troubleshooting

The browser cannot connect to the store.

  1. Ensure that you did update the hosts file.
  2. Browse the logs from the yarn dev:nuxt:multistore/yarn dev:next:multistore command in search for errors.
  3. Sometimes the DNS information is getting cached in the browser. Open the store in incognito. You may also need to wait a bit and re-open the incognito mode if this doesn't work at first. I am getting below error in the console
Uncaught (in promise) DOMException: Failed to construct 'WebSocket': The URL 'wss://localhost:undefined/_nuxt/' is invalid.
    at setupWebSocket (https://your-store-url/_nuxt/@vite/client:268:20)
    at fallback (https://your-store-url/_nuxt/@vite/client:247:22)
    at WebSocket.<anonymous> (https://your-store-url/_nuxt/@vite/client:283:13)

Unfortunately Nuxt3' hot reload is not available in the multistore mode.

E-commerce platforms

SAP

SAP requires several configuration keys per store. To handle multiple stores in SAP, you need to configure the configuration.api for each domain.

Example:

We have a default configuration for SAPCC that offers apparel products.

/integrations/sapcc/config.ts
export const sapccConfig = {
  location: "@vsf-enterprise/sapcc-api/server",
  configuration: {
    OAuth: {
      uri: SAPCC_OAUTH_URI,
      clientId: SAPCC_OAUTH_CLIENT_ID,
      clientSecret: SAPCC_OAUTH_CLIENT_SECRET,
      tokenEndpoint: SAPCC_OAUTH_TOKEN_ENDPOINT,
      tokenRevokeEndpoint: SAPCC_OAUTH_TOKEN_REVOKE_ENDPOINT,
      cookieOptions: {
        "vsf-sap-token": { secure: NODE_ENV !== "development" },
      },
    },
    api: {
      uri: SAPCC_API_URI,
      baseSiteId: "apparel-uk",
      catalogId: "apparelProductCatalog",
      catalogVersion: "Online",
      defaultLanguage: "en",
      defaultCurrency: "USD",
    },
  },
  extensions: (extensions: ApiClientExtension[]) => [
    ...extensions,
    ...(IS_ALGOLIA_ENABLED ? [unifiedApiExtensionWithAlgolia] : [unifiedApiExtension]),
    ...(IS_MULTISTORE_ENABLED ? [multistoreExtension] : []),
  ],
} satisfies Integration<MiddlewareConfig>;

We want to add a separate store for electronics products.

/integrations/sapcc/extensions/mutlistore.ts
import { createMultistoreExtension } from "@vue-storefront/multistore";
import NodeCache from "node-cache";

const {
  MULTISTORE1_BASE_SITE_ID,
  MULTISTORE1_CATALOG_ID,
  MULTISTORE2_BASE_SITE_ID,
  MULTISTORE2_CATALOG_ID,
} = process.env;

export const multistoreExtension = createMultistoreExtension({
  fetchConfiguration: () => ({
    "dev.vsf.local": {
      baseSiteId: MULTISTORE1_BASE_SITE_ID,
      catalogId: MULTISTORE1_CATALOG_ID,
    },
    "dev.client.local": {
      baseSiteId: MULTISTORE2_BASE_SITE_ID,
      catalogId: MULTISTORE2_CATALOG_ID,
    },
  }),
  mergeConfigurations({ baseConfig, storeConfig }) {
    // Note: the multistore config should be applied to the `api` object
    Object.assign(baseConfig.api, storeConfig);
    return baseConfig;
  },
  cacheManagerFactory() {
    const client = new NodeCache({
      stdTTL: 10,
    });

    return {
      get(key) {
        return client.get(key);
      },
      set(key, value) {
        return client.set(key, value);
      },
    };
  },
});

Now, the pl.mycommerce.com domain will use the apparel-uk configuration and the de.mycommerce.com domain will use the electronics configuration.

SFCC

To handle multiple stores in SFCC, you need to configure the origin and siteId for each domain, as was described in this guide.

/integrations/sfcc/extensions/mutlistore.ts
import { createMultistoreExtension } from "@vue-storefront/multistore";
import NodeCache from "node-cache";

const { MULTISTORE1_ORIGIN, MULTISTORE1_SITE_ID, MULTISTORE2_ORIGIN, MULTISTORE2_SITE_ID } =
  process.env;

export const multistoreExtension = createMultistoreExtension({
  fetchConfiguration: () => ({
    "dev.vsf.local": {
      origin: MULTISTORE1_ORIGIN,
      siteId: MULTISTORE1_SITE_ID,
    },
    "dev.client.local": {
      origin: MULTISTORE2_ORIGIN,
      siteId: MULTISTORE2_SITE_ID,
    },
  }),
  mergeConfigurations({ baseConfig, storeConfig }) {
    return Object.assign(baseConfig, storeConfig);
  },
  cacheManagerFactory() {
    const client = new NodeCache({
      stdTTL: 10,
    });

    return {
      get(key) {
        return client.get(key);
      },
      set(key, value) {
        return client.set(key, value);
      },
    };
  },
});

BigCommerce

BigCommerce offers a multistorefront feature, which requires to configure the channelId for each domain.

In case, you'd need to handle multiple separate BigCommerce stores, you need to configure whole integration for each store separately.

/integrations/bigcommerce/extensions/mutlistore.ts
import { createMultistoreExtension } from "@vue-storefront/multistore";
import NodeCache from "node-cache";

const { MULTISTORE1_CHANNEL_ID, MULTISTORE2_CHANNEL_ID } = process.env;

export const multistoreExtension = createMultistoreExtension({
  fetchConfiguration: () => ({
    "dev.vsf.local": {
      channelId: MULTISTORE1_CHANNEL_ID,
    },
    "dev.client.local": {
      channelId: MULTISTORE2_CHANNEL_ID,
    },
  }),
  mergeConfigurations({ baseConfig, storeConfig }) {
    return Object.assign(baseConfig, storeConfig);
  },
  cacheManagerFactory() {
    const client = new NodeCache({
      stdTTL: 10,
    });

    return {
      get(key) {
        return client.get(key);
      },
      set(key, value) {
        return client.set(key, value);
      },
    };
  },
});

Commercetools

In Commercetools, you may need to configure the channel for each domain.

/integrations/commercetools/extensions/mutlistore.ts
import { createMultistoreExtension } from "@vue-storefront/multistore";
import NodeCache from "node-cache";

const { MULTISTORE1_CHANNEL, MULTISTORE2_CHANNEL } = process.env;

export const multistoreExtension = createMultistoreExtension({
  fetchConfiguration: () => ({
    "dev.vsf.local": {
      channel: MULTISTORE1_CHANNEL,
    },
    "dev.client.local": {
      channel: MULTISTORE2_CHANNEL,
    },
  }),
  mergeConfigurations({ baseConfig, storeConfig }) {
    return Object.assign(baseConfig, storeConfig);
  },
  cacheManagerFactory() {
    const client = new NodeCache({
      stdTTL: 10,
    });

    return {
      get(key) {
        return client.get(key);
      },
      set(key, value) {
        return client.set(key, value);
      },
    };
  },
});