Next.js: Importing correctly scoped css from node_modules outside of _app

Created on 21 Apr 2020  路  49Comments  路  Source: vercel/next.js

Bug report

Describe the bug

Importing a stylesheet from a package is not possible in a page, because next throws with this error:

Global CSS cannot be imported from files other than your Custom <App>. Please move all global CSS imports to pages/_app.tsx.
Read more: https://err.sh/next.js/css-global

While I understand where this stems from, it makes code-splitting impossible. If I import a component from a component library, I need to import the CSS as well. There might be libraries that don't correctly scope their selectors, but that shouldn't stop me from overwriting this warning. CSS that is imported from a library isn't inherently "global".

To Reproduce

  1. import "my-library/index.css"
  2. yarn dev
  3. I get the error from above

Expected behavior

The file should be imported.

I see these possible solutions:

  • Global flag in next.config.js
  • Comment annotation on import
  • Warn instead of completely blocking it
  • Check if the CSS only contains scoped selectors

Additional context

There have been previous discussions about this.

From the blog

Since stylesheets are global by nature, they must be imported in the Custom component. This is necessary to avoid class name and ordering conflicts for global styles.

I disagree with this statement, the reasoning being that an external library can use CSS modules and package them as a CSS file to import. Which is perfectly valid and common practice and does not have side effects.

#10059

This issue got closed because the global import in _app is the correct choice there.
This comment describes the exact problem, but there hasn't been any response, as the issue is closed. The comment got a lot of positive reactions though, so I suppose I'm not the only one with this problem.

#10975

Seems to be unrelated.

#9830

Might be related, but I'm not sure.

My use case

I write long articles with a lot of custom artwork and interactive illustrations. Articles use private npm packages with react components that render SVG with quite a bit of CSS. These packages use CSS modules and export an index.js and index.css. Adding all the CSS files to _app causes all the CSS to be loaded, even if people are on the home page, the contact form, or any other article, even though it's 100% unused. It also goes against having the file system take care of your pages because almost every page corresponds to a CSS import in _app.

story 8 feature request

Most helpful comment

We're going to be allowing importing CSS from node_modules into any component file within the next week (on canary)! We'll post here when it's ready to test.

All 49 comments

I'm facing this same problem simply trying to use Linaria, which scopes its own class names. Though the css files it produces don't end in .module.css, they're "modules." I need an easy way to integrate with the library.

why did even switch to nextjs again?

I'd also like to be able to use GlobalCSS outside of node_modules. This would help us incrementally adopt CSS Modules

yeah this is very important! many npm packages are not working with nextjs but work with CRA, or other frameworks

For anyone else that's trying to use this with dart sass' js implementation for things like @use support and sass modules, if you have -any- other node module that has a dependency on node-sass, the default next setup will use node-sass instead of sass. Locally I've fixed it by doing the following:

// example next.config.js
module.exports = {
webpack(config, options) {
  config.module.rules.forEach(rule => {
          if (rule.oneOf) {
            const nestedScss = rule.oneOf.find((one) => {
              return one.test
                && 'some.scss'.match(one.test) 
                && one.issuer 
                && one.issuer.include 
                && one.issuer.include.includes('_app');
            });
            if (nestedScss) {
              const sassLoader = nestedScss.use.find(u => u.loader.includes('sass-loader'));
              // Set implementation to sass instead of node-sass here.
              sassLoader.options.implementation = require('sass');
            }
          }
        })
  }
}

You'll then need to import your scss files in _app.js.

@smurrayatwork this is hacking not coding sorry

Also the restriction on it being exclusively _app.js is a little cumbersome.

If we're not going to support CSS references everywhere then could we make it so CSS can also be referenced by direct dependencies of _app (that are not referenced anywhere else)?
ie. It's fine if it's required by _app (and nowhere else) which would give the CSS a deterministic order based off imports.

It's not ideal however the use case I have is that I have is one codebase shared by multiple applications that import a shared module which imports shared CSS. I'd hate to duplicate those shared CSS imports in _app.js for every application. Currently to get around that I'd have to do some fancy js metaprogramming because we can't require css in other modules.

Instead I would like my current approach to work which is I have an "App Factory" which imports all the shared CSS. _app then uses the factory and imports its own CSS on top of the shared ones.

I'm adding https://github.com/vercel/next.js/discussions/13991 as I think it relates to this issue.

+100 to this. I'm having to copy and paste node module css files into my project and adding a .module.css on them

Here's another example.

In the case of the package pdf-viewer-reactjs its dependencies require CSS that need to be imported from _app.js as well.

This is bloating the CSS for the whole app and I am not sure about conflicts at this stage.

import 'react-quill/dist/quill.snow.css';
import 'react-image-crop/dist/ReactCrop.css';
import '../../node_modules/material-design-icons/iconfont/material-icons.css';
import '../../node_modules/bulma/css/bulma.css';
import '../../node_modules/bulma-helpers/css/bulma-helpers.min.css';

Additionally the following is output to console:

warn - ./node_modules/material-design-icons/iconfont/material-icons.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm
Location: node_modules/pdf-viewer-reactjs/dist/pdf-viewer-reactjs.js

./node_modules/bulma/css/bulma.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm
Location: node_modules/pdf-viewer-reactjs/dist/pdf-viewer-reactjs.js

./node_modules/bulma-helpers/css/bulma-helpers.min.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm
Location: node_modules/pdf-viewer-reactjs/dist/pdf-viewer-reactjs.js

./node_modules/material-design-icons/iconfont/material-icons.css
Module build failed: Error: Final loader (./node_modules/next/dist/build/webpack/loaders/error-loader.js) didn't return a Buffer or String

./node_modules/bulma/css/bulma.css
Module build failed: Error: Final loader (./node_modules/next/dist/build/webpack/loaders/error-loader.js) didn't return a Buffer or String

./node_modules/bulma-helpers/css/bulma-helpers.min.css
Module build failed: Error: Final loader (./node_modules/next/dist/build/webpack/loaders/error-loader.js) didn't return a Buffer or String

Hi ! Do someone resolved this and how ? So many node modules that I can't import because of that.

Maybe using global styles in components could be activated via next.config.js, or an ugly console warning against global styles could be shown, in case there is concern about breaking from NextJS' best practices / opinions.

But this is important for users converting from CRA > NextJS. It's a blocker for us b/c we can't switch & then incrementally adopt things like CSS modules.

Still unable to get around this. For my own needs I used a custom CSS handler, but this disables built-in CSS support but it may not be a good solution for all cases . The below is discouraged , use only until the package authors sort it out

next.config.js

const withCSS = require('@zeit/next-css');
const withPlugins = require('next-compose-plugins');
...
module.exports = withPlugins([
...
withCSS,
]);

@abdelrahmantoptal's Do you know how to get that working for SASS?

It appears it would work for CSS, but throws an error when encountering a SASS import:

error - ./src/components/layouts/Footer.scss 1:0
Module parse failed: Unexpected character '@' (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
> @import 'styles/vars';
| 
| footer {

so I tried adding a SASS loader to the webpack config before using the withCSS plugin:

      config.module.rules.push({
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          'style-loader',
          // Translates CSS into CommonJS
          'css-loader',
          // Compiles Sass to CSS
          'sass-loader'
        ]
      });

But that caused:

error - ./src/components/App.scss
ReferenceError: self is not defined

I also tried substituting @zeit/next-sass, but that caused the same error:

error - ./src/components/App.scss
ReferenceError: self is not defined

Any suggestions on how to tweak your code to use SASS?

I've now thrown out Gatsby and soon Next.js because of their small, but very blocking, opinionated features such as this one. I am now unable to use the CodeBlock plugin for CKEditor 5 because I can't get around this error. There should always be some way to circle these configurations.

It would be very helpful to have news from @Timer or someone from Vercel about this problem. This is a big problem with Next.js. Something is planned to fix this?

Agreed! It's incredibly common to have css in node modules. As a developer, I have no control over how other developers structure their node modules, and other developers don't expect that putting css in a node module would break a web framework.

@OssiPesonen have you seen this yet? This workaround isn鈥檛 ideal, but it solved the problem for me in the meantime.

@OssiPesonen have you seen this yet? This workaround isn鈥檛 ideal, but it solved the problem for me in the meantime.

I don't see how this helps? The problem is not me having to manually import some CSS files from node modules. The problem is npm packages doing the CSS importing in themselves. A package that includes a row like this:

import '../theme/stylesheet.css'

Will cause next.js to crash with a vengeance. And apparently the maintainer's advice is:

Reach out to the maintainer and ask for them to publish a compiled version of their dependency.

In what kind of fantasyland do people live in where they imagine you can just contact maintainers ask them to re-compile their package for you in a very fast pace? This will hinder anyone for weeks! This ticket has been open for 4 months. That's unacceptable when working on quickly moving projects.

We're going to be allowing importing CSS from node_modules into any component file within the next week (on canary)! We'll post here when it's ready to test.

If anyone needs this prior to the release, I was able to use the next-transpile-modules plugin to transpile the module from node_modules that was importing CSS. Worked like a charm for me.

@BrandonE seems that next-transpile-modules still needs to have the modules named *.module.css. Did you find a way around that?

@rjoaopereira I can't say I have a deep understanding of how any of this works, but most of my node_modules that imported CSS only worked with the @zeit/next-css plugin. Only one did not, at which point transpiling fixed the issue. Far from an elegant solution, and I hope that future versions of Next.js allow us to spend less time on Babel / Webpack alchemy and more on making web applications.

I got this almost working with the following changes.

next 9.5.3
next-transpile-modules 4.1.0
1st party components with emotion.
3rd party components with a mix of css modules and global css

scopedcomponents is to be replaced with the 3rd party components being used

//next.config.js
const withCustomWebpack = require("./webpack-custom.config");
const withNextCSSOverride = require("./next.config.css");
const withTM = require("next-transpile-modules")(["@scopedcomponents"]);

module.exports = withCustomWebpack(
  withTM(
    withNextCSSOverride({
      poweredByHeader: false
    })
  )
);

///next.config.css.js
const {
  getCssModuleLocalIdent
} = require("next/dist/build/webpack/config/blocks/css/loaders/getCssModuleLocalIdent");
const path = require("path");
/**
 * 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
  );
};

module.exports = (nextConfig = {}) => {
  return Object.assign({}, nextConfig, {
    webpack(config, options) {
      const nextCssLoaders = config.module.rules.find(
        rule => typeof rule.oneOf === "object"
      );

      if (nextCssLoaders) {
        const nextCssLoader = nextCssLoaders.oneOf.find(
          rule =>
            rule.sideEffects === false &&
            regexEqual(rule.test, /\.module\.css$/)
        );

        if (nextCssLoader) {
          /***********************************************************
           * change the rule to match all scopedcomponents css files
           ***********************************************************/
          nextCssLoader.test = /(@scopedcomponents|react\-virtualized)\/.*\.css$/;

          const cssLoader = nextCssLoader.use.find(({ loader }) =>
            loader.includes("css-loader")
          );

          if (cssLoader) {
            /***********************************************************
             * Override the default behaviour for CSS modules discovery
             * auto = true makes webpack search for *.module.css
             * https://webpack.js.org/loaders/css-loader/#auto
             ***********************************************************/
            cssLoader.options.modules.auto = /@scopedcomponents\/.*\.css$/;
            /***********************************************************
             * Nextjs overrides the default mode to "Pure"
             * https://github.com/vercel/next.js/blob/v9.5.2/packages/next/build/webpack/config/blocks/css/loaders/modules.ts#L35
             * Put it back to normal
             ***********************************************************/
            cssLoader.options.modules.mode = "local";
            /***********************************************************************************************************************
             * There is a problem when using components built with css-modules with Nextjs.                                        *
             * NextJS will consume code from `lib` on the server side and from `es` on the client.                                 *
             * https://github.com/vercel/next.js/blob/v9.5.2/packages/next/build/webpack-config.ts#L374                            *
             * This raises a problem when generating the classes for different environments,                                       *
             * throwing an error of className mismatch due to the hash created being based on the file path                        *
             * https://github.com/vercel/next.js/blob/v9.5.2/packages/next/build/webpack/config/blocks/css/loaders/modules.ts#L26  *
             * https://github.com/webpack/loader-utils/blob/v1.4.0/lib/interpolateName.js#L39                                      *
             * To solve this, when generating the classNames for 3rd party components,                                                 *
             * we need to tell cssloader to always use the same path                                                               *                                                                          *
             *                                                                                                                     *
             *  https://github.com/zeit/next-plugins/issues/595                                                                    *
             ***********************************************************************************************************************/
            cssLoader.options.modules.getLocalIdent = (
              context,
              localIdentName,
              localName,
              options
            ) => {
              const newContext = { ...context };
              if (newContext.resourcePath.includes("@scopedcomponents")) {
                newContext.resourcePath = newContext.resourcePath.replace(
                  `${path.sep}es${path.sep}`,
                  `${path.sep}lib${path.sep}`
                );
              }
              return getCssModuleLocalIdent(
                newContext,
                localIdentName,
                localName,
                options
              );
            };
          }
        }
      }

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

      return config;
    }
  });
};

Problems:

@Timer any update on this?

We're going to be allowing importing CSS from node_modules into any component file within the next week (on canary)! We'll post here when it's ready to test.

Will there be dynamic import of CSS from a component after this fix?

Thank you so much @Timer

next@^9.5.4-canary.10 now allows you to import Global CSS from node_modules anywhere in your application. This improves interoperability with third-party React libraries that require you import their CSS, but don't want it to increase bundle size for your entire application.

@Timer Can't wait to have that release, really appreciated your work 馃挴 鉂わ笍

Thanks @Timer !

This has been a blocking issue for me currently, however when I tested this out today, still seeing the same error message. Is there anything more to it than simply upgrading to 9.5.4-canary-10? This example is trying to use 3rd party lib @rmwc

image

@johmike Are you importing using the following syntax??

import "@rmwc/avatar/avatar.css";

Did you tried restarting the dev server after installing the latest version of next?

@Timer Thanks a lot for this feature. Works great for importing CSS file from node_modules folder.

import 'prism-themes/themes/prism-darcula.css';

Any plans to support import of global css outside the node_modules dir?

@sasivarnan
This is coming from another library that is importing the @rmwc components. That library is using @require("@rmwc/avatar/avatar.css"). I am importing import {Avatar} from "library/Avatar" and that is failing.

@sasivarnan

This is coming from another library that is importing the @rmwc components. That library is using @require("@rmwc/avatar/avatar.css"). I am importing import {Avatar} from "library/Avatar" and that is failing.

Got it. I thought it was imported directly in your application. My bad.

Judging from the comments here, this actually isn't resolved or it was resolved but many people here report a different issue. Many people still cannot import modules, which import CSS from the packge itself (an import style.css statement inside a package file).

The fix seems to allow the app to import CSS from node_modules/ path, but there's a pretty easy way to go around this: just copy the CSS to your app for now until it's fixed. It's not a blocker level issue. So it did not really solve the blocker issue to which there is no easy solution. If you import a component which has an import statement to a CSS file the package itself contains, the app crashes.

@sasivarnan @OssiPesonen you both seem to be talking about a different issue than what was being discussed and fixed in this OP issue.

This specifically fixes libraries that require you import their CSS in your application, for example:

// components/MySlider.tsx
import { Slider } from "@reach/slider";
import "@reach/slider/styles.css";

function Example() {
  return <Slider min={0} max={200} step={10} />;
}

What you're talking about is a duplicate of #706 and #13282, or the ability to treat node_modules like first-party code.

@Timer I just tested the expected use case and it does indeed work fine.

When I import the css in a component directly as part of the next structure it works as expected, no errors.
However if I move that component into another package outside of the next structure, build, and then install that package, it fails back to the same error as before.

It's possible something else is going on, as I'm not even using the Avatar component in this example, I am importing Button and yet Avatar is the failing error.

image

Also I added next-transpile-modules as we are working from a monorepo, but that didn't seem to help this particular issue.

I got this working by a strange configuration file from digging through a bunch of other issues around next-transpile-modules.

const withCSS = require("@zeit/next-css");
module.exports = withCSS();
require.extensions[".css"] = () => {
  return;
};

I eliminated next-transpile-modules and this works. I have no idea why, it seems like it shouldn't do anything?

I spoke too soon! While that does work for next dev, next build fails with an unknown token . (dot) error from one of the CSS files.

@Timer Any thoughts? Should this work out of the box with a monorepo and multiple packages? Or is there something else I need to configure so that @team/packageA can import css from node_modules and then be imported into @team/packageB?

You can follow https://github.com/vercel/next.js/issues/13282 for that behavior.

Using [email protected] it is possible to import css anywhere in my application. But will the same be possible for scss files? I would like to import only the scss files that I am actually using on a page.

// pages/_app.tsx
import '../styles/common.scss'

// pages/index.tsx I use a Button
import '@mynpm/custom-ui/_Button.scss'

// pages/about.tsx I use a Carousel
import '@mynpm/custom-ui/_Carousel.scss'

The example at https://nextjs.org/docs/basic-features/built-in-css-support
Schermata 2020-10-13 alle 16 43 19

Returns the error:
error - /Users/gp/dev/next-kolumbus/node_modules/@reach/dialog/styles.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm

The example at https://nextjs.org/docs/basic-features/built-in-css-support
Schermata 2020-10-13 alle 16 43 19

Returns the error:
error - /Users/gp/dev/next-kolumbus/node_modules/@reach/dialog/styles.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm

Make sure you're on the latest version of Next.js.

Sorry, I didn't specified it in the previous comment. I used version 9.5.5. just updated from npm.

I cleared all the .next cache and now it works as expected.

The error still there version 9.5.5, in _app --> import "react-gauge-chart-nextjs-support/dist/GaugeChart/style.css";

Screenshot 2020-11-12 at 14 12 11

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rauchg picture rauchg  路  3Comments

sospedra picture sospedra  路  3Comments

jesselee34 picture jesselee34  路  3Comments

irrigator picture irrigator  路  3Comments

swrdfish picture swrdfish  路  3Comments