Next.js: Can't using CSS Modules feature with Ant Design

Created on 4 Jul 2020  路  14Comments  路  Source: vercel/next.js

Bug report

Describe the bug

When I setup my Next.js with support Ant Design. I can't use CSS Modules feature of Next.js

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Here is my test project:
    https://github.com/thobn24h/nextjs-with-ant
  2. Run yarn dev to build project
  3. Go to link: http://localhost:3000 to view web
  4. Get error:
./components/Header.module.css 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> .appc {
|   color: #fff;
|   background: #16191e;

Configurations:

  1. next.config.js
/* eslint-disable */
const withLess = require('@zeit/next-less');
const withSass = require('@zeit/next-sass');
const lessToJS = require('less-vars-to-js');
const fs = require('fs');
const path = require('path');

// Where your antd-custom.less file lives
const themeVariables = lessToJS(
  fs.readFileSync(path.resolve(__dirname, './css/antd.less'), 'utf8')
);

module.exports = withSass({
  cssModules: true,
  ...withLess({
    lessLoaderOptions: {
      javascriptEnabled: true,
      modifyVars: themeVariables, // make your antd custom effective
      importLoaders: 0
    },
    cssLoaderOptions: {
      importLoaders: 3,
      localIdentName: '[local]___[hash:base64:5]'
    },
    webpack: (config, { isServer }) => {
      //Make Ant styles work with less
      if (isServer) {
        const antStyles = /antd\/.*?\/style.*?/;
        const origExternals = [...config.externals];
        config.externals = [
          (context, request, callback) => {
            if (request.match(antStyles)) return callback();
            if (typeof origExternals[0] === 'function') {
              origExternals[0](context, request, callback);
            } else {
              callback();
            }
          },
          ...(typeof origExternals[0] === 'function' ? [] : origExternals)
        ];

        config.module.rules.unshift({
          test: antStyles,
          use: 'null-loader'
        });
      }
      return config;
    }
  })
});
  1. test file: pages/index.tsx
import headerStyled from '../components/Header.module.css'

export default () => {
  return (
    <>
      <div className={headerStyled.appc}>
        Hello World
      </div>
    </>
  )
}

Expected behavior

Can view website normally with using CSS Modules feature.

Screenshots

css_modules_error

System information

  • OS: macOS 10.15.5
  • Browser (if applies) safari
  • Version of Next.js: next@^9.4.4
  • Version of Node.js: v10.16.0

Additional context

Add any other context about the problem here.

Most helpful comment

After ask on some channels ( Stack Overflow, Medium, GitLab, ... ), Sumit Sarkar (@sumitsarkar01) was helped me config the next.config.js ,
And now, It work correctly!

@jamesmosier, could you review it for me ? Is the any way simple or official about this case ?

const withLess = require('@zeit/next-less')
const lessToJS = require('less-vars-to-js')
const withPlugins = require('next-compose-plugins')

const fs = require('fs')
const path = require('path')

const dotenv = require('dotenv')

dotenv.config()

// Where your antd-custom.less file lives
const themeVariables = lessToJS(
  fs.readFileSync(path.resolve(__dirname, './css/antd.less'), 'utf8')
)

const plugins = [
  [withLess({
    lessLoaderOptions: {
      javascriptEnabled: true,
      modifyVars: themeVariables, // make your antd custom effective
    },
    webpack: (config, { isServer }) => {
      if (isServer) {
        const antStyles = /antd\/.*?\/style.*?/
        const origExternals = [...config.externals]
        config.externals = [
          (context, request, callback) => {
            if (request.match(antStyles)) return callback()
            if (typeof origExternals[0] === 'function') {
              origExternals[0](context, request, callback)
            } else {
              callback()
            }
          },
          ...(typeof origExternals[0] === 'function' ? [] : origExternals),
        ]

        config.module.rules.unshift({
          test: antStyles,
          use: 'null-loader',
        })
      }

      const builtInLoader = config.module.rules.find((rule) => {
        if (rule.oneOf) {
          return (
            rule.oneOf.find((deepRule) => {
              return deepRule.test && deepRule.test.toString().includes('/a^/');

            }) !== undefined
          );
        }
        return false;
      });

      if (typeof builtInLoader !== 'undefined') {
        config.module.rules.push({
          oneOf: [
            ...builtInLoader.oneOf.filter((rule) => {
              return (rule.test && rule.test.toString().includes('/a^/')) !== true;
            }),
          ],
        });
      }

      config.resolve.alias['@'] = path.resolve(__dirname);
      return config;
    }
  })]
]

const nextConfig = {
  env: {
  }
}

module.exports = withPlugins(plugins, nextConfig)

@maysam, I was updated this config to my test project, you can check it out
https://github.com/thobn24h/nextjs-with-ant

All 14 comments

Using custom CSS plugins opts out of Next's built-in CSS modules features. In order to use them you would have to remove your CSS plugins.

@jamesmosier,
Currently, my project using Next.js with Ant Design. And I want to customize theme of Ant Design, therefore I need @zeit/next-less module to re-compile Ant Design CSS.
Is there any way to still keep built-in CSS features plus with @zeit/next-less to customize Ant Design Theme ?

I have similar problem

There is no way around this currently when you are using a custom CSS/LESS/SASS plugin with Next.

See this docs for more info: https://err.sh/next.js/built-in-css-disabled

After ask on some channels ( Stack Overflow, Medium, GitLab, ... ), Sumit Sarkar (@sumitsarkar01) was helped me config the next.config.js ,
And now, It work correctly!

@jamesmosier, could you review it for me ? Is the any way simple or official about this case ?

const withLess = require('@zeit/next-less')
const lessToJS = require('less-vars-to-js')
const withPlugins = require('next-compose-plugins')

const fs = require('fs')
const path = require('path')

const dotenv = require('dotenv')

dotenv.config()

// Where your antd-custom.less file lives
const themeVariables = lessToJS(
  fs.readFileSync(path.resolve(__dirname, './css/antd.less'), 'utf8')
)

const plugins = [
  [withLess({
    lessLoaderOptions: {
      javascriptEnabled: true,
      modifyVars: themeVariables, // make your antd custom effective
    },
    webpack: (config, { isServer }) => {
      if (isServer) {
        const antStyles = /antd\/.*?\/style.*?/
        const origExternals = [...config.externals]
        config.externals = [
          (context, request, callback) => {
            if (request.match(antStyles)) return callback()
            if (typeof origExternals[0] === 'function') {
              origExternals[0](context, request, callback)
            } else {
              callback()
            }
          },
          ...(typeof origExternals[0] === 'function' ? [] : origExternals),
        ]

        config.module.rules.unshift({
          test: antStyles,
          use: 'null-loader',
        })
      }

      const builtInLoader = config.module.rules.find((rule) => {
        if (rule.oneOf) {
          return (
            rule.oneOf.find((deepRule) => {
              return deepRule.test && deepRule.test.toString().includes('/a^/');

            }) !== undefined
          );
        }
        return false;
      });

      if (typeof builtInLoader !== 'undefined') {
        config.module.rules.push({
          oneOf: [
            ...builtInLoader.oneOf.filter((rule) => {
              return (rule.test && rule.test.toString().includes('/a^/')) !== true;
            }),
          ],
        });
      }

      config.resolve.alias['@'] = path.resolve(__dirname);
      return config;
    }
  })]
]

const nextConfig = {
  env: {
  }
}

module.exports = withPlugins(plugins, nextConfig)

@maysam, I was updated this config to my test project, you can check it out
https://github.com/thobn24h/nextjs-with-ant

@thobn24h you are my new hero

Anytime you alter the webpack config you run the risk of something within Next breaking unintentionally. So I would certainly exercise caution with this. But glad it worked for you!

For now there is no official way to do this.

@thobn24h @maysam

https://github.com/mit123suki/next-ant-setup

This repo shows how to setup with ant without customizing the webpack. But this requires some effort for first time, refer readme for more details

you can try next-plugin-antd-less , support next.js v9.5.

@thobn24h Thankyou verymuch bro

spent last night struggling with this.

Key Points

  • NextJS does not have built-in LESS support, see this issue to see why https://github.com/vercel/next.js/issues/11584
  • NextJS will disable the CSS/SASS built in support by default if any other css custom config is detected (see the source code here about how exactly they detect the config):
  • The @zeit/next-css and @zeit/next-less are all quite outdated (next-less uses next-less). They are using [email protected]. So basically the configs we built using next-less and next-css will be probably quite different from the NextJS builtin config. I do prefer to keep the built in CSS/SASS support while having extra LESS support
  • personally for me, I want to be able to compile .less file without CSS module support (especially for third party libs like Ant Design) at the same time I want to use CSS Module for my own style files like **.module.less

My solution:

npm i @zeit/next-css @zeit/next-less less

*@zeit/next-less is mainly for the less-loader, you can install it youself, but should adjust the the structure of lessLoaderOptions that pass down to it based on the version of less-loader you use.

creating a new next-less.config.js:

const cssLoaderConfig = require('@zeit/next-css/css-loader-config')

module.exports = (nextConfig = {}) => {
    return Object.assign({}, nextConfig, {
        webpack(config, options) {
            if (!options.defaultLoaders) {
                throw new Error(
                    'This plugin is not compatible with Next.js versions below 5.0.0 https://err.sh/next-plugins/upgrade'
                )
            }
            const { dev, isServer } = options
            const {
                cssLoaderOptions,
                postcssLoaderOptions,
                lessLoaderOptions = {}
            } = nextConfig

            // use Object for `test` to bypass NextJS detection
            // so that it will keep CSS and SASS support.
            config.module.rules.push({
                test: {
                    and: [/\.less$/],
                    not: [/\.module\.less$/]
                },
                use: cssLoaderConfig(config, {
                    extensions: ['less'],
                    cssModules: false,
                    cssLoaderOptions,
                    postcssLoaderOptions,
                    dev,
                    isServer,
                    loaders: [
                        {
                            loader: 'less-loader',
                            options: lessLoaderOptions
                        }
                    ]
                })
            })

            config.module.rules.push({
                test: /\.module\.less$/,
                use: cssLoaderConfig(config, {
                    extensions: ['module.less'],
                    cssModules: true,
                    cssLoaderOptions,
                    postcssLoaderOptions,
                    dev,
                    isServer,
                    loaders: [
                        {
                            loader: 'less-loader',
                            options: lessLoaderOptions
                        }
                    ]
                })
            })

            if (typeof nextConfig.webpack === 'function') {
                return nextConfig.webpack(config, options)
            }

            return config
        }
    })
}

then just use this as a new plugin:

const withLessExcludeAntd = require("./next-less.config.js");

module.exports = withLessExcludeAntd({
    lessLoaderOptions: {
        javascriptEnabled: true,
    }
})

A little break down here:

  • I added two rules so that css module will only be enabled when you use .module.less
  • I used the object format for rule.test in my first less rule, which will bypass the NextJS detection, and my second rule.test /\.module\.less$\ will bypass the detection as well.

Is It Hacky?

Yes, bypassing the NextJS detection is hacky, but the whole solution atm NextJS team provides for Less users are far from perfect neither.

So I would suggest anyone who cares about using Less UPVOTE this issue

https://github.com/mit123suki/next-ant-setup

This repo shows how to setup with ant without customizing the webpack. But this requires some effort for first time, refer readme for more details

I used a similar approach but only @import './../node_modules/antd/lib/style/index.less'; at the global level and only import more when I have added the ANTD component. So far it works OK.

If anyone is still struggling with this, i managed to get it working with @zeit/next-css, here is the config:

module.exports = withPlugins(
  [
    [
      withCSS,
      {
        postcssLoaderOptions: {
          parser: true,
        },
        cssModules: true,
        cssLoaderOptions: {
          importLoaders: 2,
          localIdentName: "[local]___[hash:base64:5]",
          sourceMap: true,
          getLocalIdent: (context, localIdentName, localName, options) => {
            const hz = context.resourcePath.replace(context.rootContext, "");
            if (/node_modules/.test(hz)) {
              return localName;
            } else {
              return cssLoaderGetLocalIdent(
                context,
                localIdentName,
                localName,
                options
              );
            }
          },
        },
      },
    ],
    [
      withLess,
      {
        lessLoaderOptions: {
          javascriptEnabled: true,
          modifyVars: themeVariables,
        },
      },
    ],
  ],
  nextConfig
);

spent last night struggling with this.

Key Points

  • NextJS does not have built-in LESS support, see this issue to see why #11584
  • NextJS will disable the CSS/SASS built in support by default if any other css custom config is detected (see the source code here about how exactly they detect the config):
  • The @zeit/next-css and @zeit/next-less are all quite outdated (next-less uses next-less). They are using [email protected]. So basically the configs we built using next-less and next-css will be probably quite different from the NextJS builtin config. I do prefer to keep the built in CSS/SASS support while having extra LESS support
  • personally for me, I want to be able to compile .less file without CSS module support (especially for third party libs like Ant Design) at the same time I want to use CSS Module for my own style files like **.module.less

My solution:

npm i @zeit/next-css @zeit/next-less less

*@zeit/next-less is mainly for the less-loader, you can install it youself, but should adjust the the structure of lessLoaderOptions that pass down to it based on the version of less-loader you use.

creating a new next-less.config.js:

const cssLoaderConfig = require('@zeit/next-css/css-loader-config')

module.exports = (nextConfig = {}) => {
    return Object.assign({}, nextConfig, {
        webpack(config, options) {
            if (!options.defaultLoaders) {
                throw new Error(
                    'This plugin is not compatible with Next.js versions below 5.0.0 https://err.sh/next-plugins/upgrade'
                )
            }
            const { dev, isServer } = options
            const {
                cssLoaderOptions,
                postcssLoaderOptions,
                lessLoaderOptions = {}
            } = nextConfig

            // use Object for `test` to bypass NextJS detection
            // so that it will keep CSS and SASS support.
            config.module.rules.push({
                test: {
                    and: [/\.less$/],
                    not: [/\.module\.less$/]
                },
                use: cssLoaderConfig(config, {
                    extensions: ['less'],
                    cssModules: false,
                    cssLoaderOptions,
                    postcssLoaderOptions,
                    dev,
                    isServer,
                    loaders: [
                        {
                            loader: 'less-loader',
                            options: lessLoaderOptions
                        }
                    ]
                })
            })

            config.module.rules.push({
                test: /\.module\.less$/,
                use: cssLoaderConfig(config, {
                    extensions: ['module.less'],
                    cssModules: true,
                    cssLoaderOptions,
                    postcssLoaderOptions,
                    dev,
                    isServer,
                    loaders: [
                        {
                            loader: 'less-loader',
                            options: lessLoaderOptions
                        }
                    ]
                })
            })

            if (typeof nextConfig.webpack === 'function') {
                return nextConfig.webpack(config, options)
            }

            return config
        }
    })
}

then just use this as a new plugin:

const withLessExcludeAntd = require("./next-less.config.js");

module.exports = withLessExcludeAntd({
    lessLoaderOptions: {
        javascriptEnabled: true,
    }
})

A little break down here:

  • I added two rules so that css module will only be enabled when you use .module.less
  • I used the object format for rule.test in my first less rule, which will bypass the NextJS detection, and my second rule.test /\.module\.less$\ will bypass the detection as well.

Is It Hacky?

Yes, bypassing the NextJS detection is hacky, but the whole solution atm NextJS team provides for Less users are far from perfect neither.

So I would suggest anyone who cares about using Less UPVOTE this issue

The custom styles are not updated when using route jumps by next/router, the page must be refreshed to work properly

Was this page helpful?
0 / 5 - 0 ratings