csssassnext.jscss-modulesreact-css-modules

Selector ":root" is not pure (pure selectors must contain at least one local class or id) - NextJS with SASS modules


I've recently been switching to using modules in my next.js project, but I keep receiving this error in my newly created .module.scss files: "Selector ":root" is not pure (pure selectors must contain at least one local class or id)". I know this is because I'm not using pure css selectors as I've seen elsewhere online, and the only problem is the imports that I'm using, but I need those imports for variables like $cl-light-gray as seen below in this example file:

@import "src/common/styles/global-styles.scss";
@import "node_modules/bootstrap/scss/bootstrap";
@import "src/common/styles/palette.scss";
@import "src/common/styles/typography.scss";

.dashboard-dropdown-hover {
  @extend .px-1;
  @extend .py-2;
  @extend .mt-3;
  border: 1px solid transparent;
  border-radius: 8px;
  transition: 200ms;
  background-color: transparent;
}
.dashboard-dropdown-hover:hover {
  background-color: $cl-light-gray;
}

Does anyone have a solution to how I should fix this import problem? I know that if I switch back to .scss it will work, but I'm trying to avoid importing all the .scss files in _app.tsx because that would be at least 30 imports and also these styles aren't intended to be global. Lastly, why does Next.js expect me to use pure css selectors when I'm using Sass, which is used because of its non-pure elements?


Solution

  • After scouring the internet for a few hours I found a great solution from here: https://dhanrajsp.me/snippets/customize-css-loader-options-in-nextjs

    EDIT: If you're using Next.js 12, check the bottom of the article above, because the solution is a little different.

    You'll want to change your next.config.js file to include the following:

    /** @type {import('next').NextConfig} */
    require("dotenv").config();
    const regexEqual = (x, y) => {
      return (
        x instanceof RegExp &&
        y instanceof RegExp &&
        x.source === y.source &&
        x.global === y.global &&
        x.ignoreCase === y.ignoreCase &&
        x.multiline === y.multiline
      );
    };
    // Overrides for css-loader plugin
    function cssLoaderOptions(modules) {
      const { getLocalIdent, ...others } = modules; // Need to delete getLocalIdent else localIdentName doesn't work
      return {
        ...others,
        localIdentName: "[hash:base64:6]",
        exportLocalsConvention: "camelCaseOnly",
        mode: "local",
      };
    }
    module.exports = {
      webpack: (config) => {
        const oneOf = config.module.rules.find(
          (rule) => typeof rule.oneOf === "object"
        );
        if (oneOf) {
          // Find the module which targets *.scss|*.sass files
          const moduleSassRule = oneOf.oneOf.find((rule) =>
            regexEqual(rule.test, /\.module\.(scss|sass)$/)
          );
    
          if (moduleSassRule) {
            // Get the config object for css-loader plugin
            const cssLoader = moduleSassRule.use.find(({ loader }) =>
              loader.includes("css-loader")
            );
            if (cssLoader) {
              cssLoader.options = {
                ...cssLoader.options,
                modules: cssLoaderOptions(cssLoader.options.modules),
              };
            }
          }
        }
        return config;
      },
    };
    
    

    I'm not seasoned with webpack or how it exactly works, but this solution worked for me. You can also change the regex to include css by doing (scss|sass|css) if you want.