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/config/multistore.config.ts
file.
It may look like this:
import { MultistoreExtensionMethods } from "@vue-storefront/multistore";
import NodeCache from "node-cache";
/**
* List of domains that are used in the multistore setup.
*/
export const listOfStoresDomains = ["dev.vsf.local", "dev.client.local"];
/**
* This is an example of a multistore configuration.
* Read more here: https://docs.alokai.com/storefront/features/advanced/multistore-configuration
*/
export function getMultistoreConfig(): MultistoreExtensionMethods {
const { ORIGIN_1, SITE_ID_1, ORIGIN_2, SITE_ID_2 } = process.env;
return {
fetchConfiguration: () => ({
"dev.vsf.local": {
origin: ORIGIN_1,
siteId: SITE_ID_1,
},
"dev.client.local": {
origin: ORIGIN_2,
siteId: SITE_ID_2,
},
}),
mergeConfigurations({ baseConfig, storeConfig }) {
return {
...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/config/multistore.config.ts
file.
export const storeDomainsList = ["pl.mycommerce.com", "de.mycommerce.com"];
Then, in the same file, you need to define what configuration should be fetched for each domain:
export function getMultistoreConfig(): MultistoreExtensionMethods {
const { PL_ORIGIN, PL_SITE_ID, DE_ORIGIN, DE_SITE_ID } = process.env;
return {
fetchConfiguration: () => ({
"pl.mycommerce.com": {
origin: PL_ORIGIN,
siteId: PL_SITE_ID,
},
"de.mycommerce.com": {
origin: DE_ORIGIN,
siteId: DE_SITE_ID,
},
}),
// ...
};
}
Before, you had some default store configuration in apps/storefront-middleware/config/{commerceName}.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 PL_ORIGIN
and PL_SITE_ID
and for de.mycommerce.com
from DE_ORIGIN
and DE_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.
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.
// 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_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
.
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:
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.
- Ensure that you did update the
hosts
file. - Browse the logs from the
yarn dev:nuxt:multistore
/yarn dev:next:multistore
command in search for errors. - 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.
// sapcc.config.ts
export function getSapccConfig() {
// ...
return {
location: "@vsf-enterprise/sapcc-api/server",
configuration: {
// ...
api: {
uri: SAPCC_API_URI,
baseSiteId: "apparel-uk",
catalogId: "apparelProductCatalog",
catalogVersion: "Online",
defaultLanguage: "en",
defaultCurrency: "USD",
},
},
} satisfies Integration<MiddlewareConfig>;
}
We want to add a separate store for electronics products.
// multistore.config.ts
export function getMultistoreConfig(): MultistoreExtensionMethods {
const { PL_BASE_SITE_ID, PL_CATALOG_ID, DE_BASE_SITE_ID, DE_CATALOG_ID } = process.env;
return {
fetchConfiguration: () => ({
"pl.mycommerce.com": {
baseSiteId: PL_BASE_SITE_ID, // "apparel-uk"
catalogId: PL_CATALOG_ID, // "apparelProductCatalog"
},
"de.mycommerce.com": {
baseSiteId: DE_BASE_SITE_ID, // "electronics"
catalogId: DE_CATALOG_ID, // "electronicsProductCatalog"
},
}),
mergeConfigurations({ baseConfig, storeConfig }) {
return {
...baseConfig,
api: {
...baseConfig.api, // Note: this is a nested object, so we need to merge it separately
...storeConfig,
},
};
},
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.
// multisite.config.ts
export function getMultisiteConfig(): MultisiteExtensionMethods {
const { PL_ORIGIN, PL_SITE_ID, DE_ORIGIN, DE_SITE_ID } = process.env;
return {
fetchConfiguration: () => ({
"pl.mycommerce.com": {
origin: PL_ORIGIN,
siteId: PL_SITE_ID,
},
"de.mycommerce.com": {
origin: DE_ORIGIN,
siteId: DE_SITE_ID,
},
}),
mergeConfigurations({ baseConfig, storeConfig }) {
return {
...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.
// multistore.config.ts
export function getMultistoreConfig(): MultistoreExtensionMethods {
const { CHANNEL_ID_1, CHANNEL_ID_2 } = process.env;
return {
fetchConfiguration: () => ({
"pl.mycommerce.com": {
channelId: CHANNEL_ID_1,
},
"de.mycommerce.com": {
channelId: CHANNEL_ID_2,
},
}),
mergeConfigurations({ baseConfig, storeConfig }) {
return {
...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.
// multistore.config.ts
export function getMultistoreConfig(): MultistoreExtensionMethods {
const { CHANNEL_1, CHANNEL_2 } = process.env;
return {
fetchConfiguration: () => ({
"pl.mycommerce.com": {
channel: CHANNEL_1,
},
"de.mycommerce.com": {
channel: CHANNEL_2,
},
}),
mergeConfigurations({ baseConfig, storeConfig }) {
return {
...baseConfig,
...storeConfig,
};
},
cacheManagerFactory() {
const client = new NodeCache({
stdTTL: 10,
});
return {
get(key) {
return client.get(key);
},
set(key, value) {
return client.set(key, value);
},
};
},
};
}