🛠 Customize CSS Loader options in Next.jsMay 22, 2021JavaScript 240 words

Recently when working on a project migrating the code from Gatsby to Next.js, I faced a very interesting problem.

Next.js supports CSS Modules using the [name].module.css file naming convention. Really cool stuff. I can now import class names using JS syntax like import styles from './styles' and use object notations to reference those styles.

The problem however, Gatsby code had styles referencing HTML tags like a and section.

section {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 480px;
    margin: 20px 0;
    scroll-margin-top: 74px;
    &.full-width{
        width: 100%;
        margin: 20px auto;
    }
}

This unfortunately doesn’t work with the opinions of the creators of Next.js. And there isn’t an easy way to override the config of css-loader plugin using next.config.js12. I was not in the mood to update the CSS files, (honestly , there were a lot of them).

So after browsing the internet for a couple of hours, I found a hack3, which works well for my particular use case.

/**
 * Stolen from https://stackoverflow.com/questions/10776600/testing-for-equality-of-regular-expressions
 */
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) {  // eslint-disable-next-line no-unused-vars  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
  },
}

cssLoaderOptions method returns the css-loader options which I want to override. L#36, L#42 and L#47 is where the magic happens. Looping through all the modules Next.js uses, we find using a regex module which targets *.module.scss|*.module.sass files, and get the object for css-loader plugin in L#41-42.

If you want to target *.css files instead, change the regex on L#36.

And et voilà, problem solved! I do hope the RFC2 moves forward and we get a built in option from Next.js to override the config, but until then, this hack remains.