Supporting Sass Variables in XM Cloud and Storybook
Use Sitecore XM Cloud’s Sass plugin and Storybook’s Sass processing options to resolve image path issues
Start typing to search...
Use Sitecore XM Cloud’s Sass plugin and Storybook’s Sass processing options to resolve image path issues
When upgrading a Sitecore XP solution to Sitecore XM Cloud, there are often unexpected challenges. We faced such a hurdle when we were migrating our client’s custom Sass files, which contained paths to SVG icons. These paths were hardcoded in the Sass files.
&.icon-download {
content: url(/img/acmecorp/icons/icon_download.svg);
}
![]()
This worked seamlessly in normal mode. However, in the Experience Editor and Pages, these icons didn’t display because Sitecore attempted to fetch the SVGs from the Content Management (CM) server rather than the rendering host. To resolve this, we needed to dynamically reference the correct hostname, ensuring that icons would load properly for both the public website and inside Sitecore editors.
To do this, we had to modify the src\lib\next-config\plugins\sass.js file that comes with the Sitecore
XM Cloud starter kit. This file is called by next.config.js, and by default it just sets
path aliases for importing Sass files. We extended the sassOptions object of that file to include an
additionalData property, where we could create the Sass variable that would hold our hostname. Then to
get the hostname, we called Sitecore JSS's getPublicUrl() function to fetch the domain for whichever
environment the code was running on (defaulted to an empty string if it couldn’t be found).
const path = require('path');
const SassAlias = require('sass-alias');
// BEGIN CUSTOMIZATION - make the public URL available to the sass files
const getPublicUrl = require('@sitecore-jss/sitecore-jss-nextjs/utils').getPublicUrl;
const publicUrl = getPublicUrl() || '';
// END CUSTOMIZATION
/*
* @param {import('next').NextConfig} nextConfig
*/
const sassPlugin = (nextConfig = {}) => {
return Object.assign({}, nextConfig, {
sassOptions: {
importer: new SassAlias({
'@sass': path.join(__dirname, '../../../assets', 'sass'),
'@fontawesome': path.join(__dirname, '../../../../node_modules', 'font-awesome'),
}).getImporter(),
// BEGIN CUSTOMIZATION - make the public URL available to the sass files
additionalData: `$base-url: "${publicUrl}";`,
// END CUSTOMIZATION
},
});
};
module.exports = sassPlugin;
Then we were able to use $base-url in the SVG paths. The icons now appeared in edit mode.
&.icon-download {
content: url(#{$base-url}/img/acmecorp/icons/icon_download.svg);
}
However, our changes caused our Storybook build to fail with the following error:
ERROR in ./src/assets/main.scss (./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[15].use[1]!./node_modules/postcss-loader/dist/cjs.js!./node_modules/resolve-url-loader/index.js!./node_modules/@storybook/nextjs/node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[15].use[4]!./src/assets/main.scss)
Module build failed (from ./node_modules/@storybook/nextjs/node_modules/sass-loader/dist/cjs.js):
SassError: Undefined variable.
╷
12 │ content: url(#{$base-url}/img/acmecorp/icons/icon_download.svg);
│ ^^^^^^^^^
╵
src\assets\links.scss 12:26 @import
src\assets\main.scss 30:11 root stylesheet
Storybook doesn’t need next.config.js to run, so it won’t have access to this variable we
created in our src\lib\next-config\plugins\sass.js plugin. We’ll need to configure that variable
another way for Storybook.
In our Storybook’s .storybook\main.ts file, we were already using the
@storybook/addon-styling addon, and found that it has further options to configure it. We used the
scssBuildRule object to tell Storybook how to process the Sass files.
const config: StorybookConfig = {
staticDirs: ['../public'],
addons: [
// Other addons
{
name: '@storybook/addon-styling',
options: {
scssBuildRule: {
test: /\.s[ac]ss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 3,
url: {
filter: (url) => {
if (url.startsWith("/img/acmecorp/icons/")) {
return false;
}
return true;
},
},
},
},
'postcss-loader',
'resolve-url-loader',
{
loader: 'sass-loader',
options: {
additionalData: '$base-url: "";'
}
}
]
},
// Other options
},
},
],
// Rest of the config
};
This configuration contains many parts:
.scss and .sass files (Sass stylesheets).scssBuildRule uses a series of loaders to process these files:
style-loader: Injects the CSS into the DOM at runtime.css-loader
@import statements, url(), and require() and resolves
them.importLoaders: 3)."/img/acmecorp/icons/".
This filter is used to prevent icon paths from being resolved because they do not exist at that location in
the Storybook application.postcss-loader: processes CSS with PostCSS.resolve-url-loader: resolves relative paths in url() statements based on the
original source file.sass-loader: compiles Sass/SCSS files to CSS.
$base-url: ""; to all processed files so our icon URLs stay
relative without a hostname in Storybook. The icon files are located in the ../public folder
configured in staticDirs.In the process of migrating from Sitecore XP to Sitecore XM Cloud, even seemingly minor details like handling icon
paths in Sass files can present unexpected challenges. By leveraging Sitecore JSS's getPublicUrl()
function, extending the sass.js configuration and changing how Storybook processes Sass files, we were
able to ensure that the icons loaded correctly for Experience Editor and Pages.