vercel/next-plugins

next-css SSR not working with CSS files from node_modules

Closed this issue ยท 14 comments

Hi guys, great work with Next!

I encountered an issue which involves next-css, node_modules and Server-Side Rendering.

Basically, when a NPM package requires a CSS file, SSR breaks. This is an issue when trying to use a component library that bundles CSS files in itself (in my case, fyndiq-ui).

The bug happens with or without CSSModules, and with or without PostCSS-loader. It also happens in production build

Note that the bug doesn't happen when importing local CSS files (thanks to #50 I presume).

I made a small repo that demonstrates the issue https://github.com/thibautRe/next-css-ssr-bug. You can run the README instructions to reproduce the bug.

I'm willing to submit a PR to fix this behavior. I checked the next-css code but didn't see anything glaringly problematic.

Hi @thibautRe thanks for opening this issue, I'm having the same problems :)

I did some deep investigation on how the next.js webpack compilation works and on what the next-css plugin does.

What is my scenario?

  1. On my project I was trying to import a package that has some js and imports some css.
  2. When I imported the project as import theme from 'former-kit-skin-pagarme' it wouldn't work โŒ. The CSS would be compiled and extracted to static/styles.css, but I would have an error when accessing an URL.
  3. When I imported the project with the absolute path import theme from './node_modules/former-kit-skin-pagarme/dist' it would work โœ….

What I found out

I created an isolated environment, skimmed through the code and recreated next-css step-by-step to determine if some configuration, loader or plugin could be the problem. And, as it turns out, there's nothing wrong with next-css or its configuration.

So my next suspect was next.js itself. With the behavior I noticed, I knew that there would probably be some configuration related to the node_modules folder. next.js have two distinct (but similar) webpack configurations: one that runs to compile for the server and on for the client part of your application.

On the server, configuration, next.js basically marks everything from node_modules (except webpack) as a webpack externals. ๐Ÿ‘‰ Link to the source code

Simply put, every dependency that appears on webpack externals do not get bundled, but instead webpack thinks you will want to require the package by yourself (which makes a lot of sense on a server context) and it puts this on your build: require('former-kit-skin-pagarme). Now, here's the problem: the server part of your application is going to try to require your package that requires some CSS. The result: your application will face a runtime error because your server will try to require some plain CSS file.

Workaround

Once I found out what was happening, it was easier to come with a workaround to fix my problem. As any workaround, this is not ideal but can solve some problems and buy time until the problem is addressed on the core of next.js.

Basically, I recreated the externals configuration used by next.js to blacklist my module from webpack externals. Here's what my next.config.js look like:

const resolve = require('resolve')
const withCSS = require('@zeit/next-css')

module.exports = withCSS({
  cssModules: true,

  webpack (config, options) {
    const { dir, isServer } = options

    config.externals = []

    if (isServer) {
      config.externals.push((context, request, callback) => {
        resolve(request, { basedir: dir, preserveSymlinks: true }, (err, res) => {
          if (err) {
            return callback()
          }

          // Next.js by default adds every module from node_modules to
          // externals on the server build. This brings some undesirable
          // behaviors because we can't use modules that require CSS files like
          // `former-kit-skin-pagarme`.
          //
          // The lines below blacklist webpack itself (that cannot be put on
          // externals) and `former-kit-skin-pagarme`.
          if (
            res.match(/node_modules[/\\].*\.js/)
            && !res.match(/node_modules[/\\]webpack/)
            && !res.match(/node_modules[/\\]former-kit-skin-pagarme/)
          ) {
            return callback(null, `commonjs ${request}`)
          }

          callback()
        })
      })
    }

    return config
  },
})

Important: in this case, the project I want to use is called former-kit-skin-pagarme. You should change this on your next.config.js to use your own projects

Definitive solution

The definitive solution must come from next.js core team, as this problem mainly happens because the way the webpack externals configuration are used on the server side.

The ideal solution would be to add externals conditionally: only add if the module itself does not require any CSS. However, I don't know how one would do that with webpack.

Very interesting investigation, thank you for sharing it here. Your solution works really nicely for my use-case, and fixed my issue ๐ŸŽ‰.

As this issue doesn't seem to be a problem with next-css, I will close this issue here.

example does not work

Server is stuck compiling
screen shot 2018-09-26 at 2 45 07 pm

Same here, compiles then crashes for me

Same issue, how to configure sass with react flext box grid?

Get this problem fixed by adding this plugin: https://www.npmjs.com/package/next-plugin-transpile-modules

Just make webpack to transpile the module containing css imports.

Something like

const withTM = require('next-plugin-transpile-modules');
module.exports = withTM({
  transpileModules: ['the-package-that-imports-css']
}, ...)
Vrq commented

If you are coming from Google search, especially if you see the:

Module build failed: ModuleParseError: Module parse failed: Unexpected character '' (1:0) You may need an appropriate loader to handle this file type.

this might be the solution you are looking for.

3 simple steps:

  1. Install next-css plugin:
npm install --save @zeit/next-css
  1. Create in your root directory next.config.js with the following content:
// next.config.js 
const withCSS = require('@zeit/next-css')

module.exports = withCSS({
  cssLoaderOptions: {
    url: false
  }
})
  1. Now you should be able to import styleshets from node_modules like this:
import 'bootstrap-css-only/css/bootstrap.min.css';

Note: Using Next v 8+

Background:
I spent a few hours trying to simply import a CSS installed as a node_module and the various solutions are mostly hacky workarounds, but as shown above, there is a simple solution.
It was provided by one of the core team members: https://spectrum.chat/next-js/general/ignoring-folders-files-specifically-fonts~4f68cfd5-d576-46b8-adc8-86e9d7ea0b1f

T

If you are coming from Google search, especially if you see the:

Module build failed: ModuleParseError: Module parse failed: Unexpected character '' (1:0) You may need an appropriate loader to handle this file type.

this might be the solution you are looking for.

3 simple steps:

  1. Install next-css plugin:
npm install --save @zeit/next-css
  1. Create in your root directory next.config.js with the following content:
// next.config.js 
const withCSS = require('@zeit/next-css')

module.exports = withCSS({
  cssLoaderOptions: {
    url: false
  }
})
  1. Now you should be able to import styleshets from node_modules like this:
import 'bootstrap-css-only/css/bootstrap.min.css';

Note: Using Next v 8+

Background:
I spent a few hours trying to simply import a CSS installed as a node_module and the various solutions are mostly hacky workarounds, but as shown above, there is a simple solution.
It was provided by one of the core team members: https://spectrum.chat/next-js/general/ignoring-folders-files-specifically-fonts~4f68cfd5-d576-46b8-adc8-86e9d7ea0b1f

This solves the problem. Thanks.

@Vrq your posted solution does not work if cssModules: true

provided by one of the core team mem

This solves the problem! Thanks dude :)

axeen commented

Get this problem fixed by adding this plugin: https://www.npmjs.com/package/next-plugin-transpile-modules

Just make webpack to transpile the module containing css imports.

Something like

const withTM = require('next-plugin-transpile-modules');
module.exports = withTM({
  transpileModules: ['the-package-that-imports-css']
}, ...)

This solved it for me!! Thank you.
Although the module has changed name to next-transpile-modules

I've been using RMWC, which requires users to load additional CSS. Importing the CSS files directly in the JSX files (eg. import "@material/textfield/dist/mdc.textfield.css") was working for me in development, but for some reason they weren't getting included in production.

What worked for me (for reasons I don't really understand) was creating a local CSS file, importing it from the JSX file, and within it importing the RMWC CSS, like so:

/* pages/mypage.css */
@import "~@material/textfield/dist/mdc.textfield.css";
@import "~@material/floating-label/dist/mdc.floating-label.css";
@import "~@material/notched-outline/dist/mdc.notched-outline.css";
@import "~@material/line-ripple/dist/mdc.line-ripple.css";
// pages/mypage.jsx
import "./mypage.css";

const MyPage = () => { //....

Possibly related: #498. Note that I wasn't getting errors in production that I could see -- the stylesheets were seemingly just silently dropped in prod.

I've been using RMWC, which requires users to load additional CSS. Importing the CSS files directly in the JSX files (eg. import "@material/textfield/dist/mdc.textfield.css") was working for me in development, but for some reason they weren't getting included in production.

What worked for me (for reasons I don't really understand) was creating a local CSS file, importing it from the JSX file, and within it importing the RMWC CSS, like so:

/* pages/mypage.css */
@import "~@material/textfield/dist/mdc.textfield.css";
@import "~@material/floating-label/dist/mdc.floating-label.css";
@import "~@material/notched-outline/dist/mdc.notched-outline.css";
@import "~@material/line-ripple/dist/mdc.line-ripple.css";
// pages/mypage.jsx
import "./mypage.css";

const MyPage = () => { //....

Possibly related: #498. Note that I wasn't getting errors in production that I could see -- the stylesheets were seemingly just silently dropped in prod.

This was the only thing that worked for me. In my case Im working with SaSS and added the default config from next-sass but was having a lot of problems importing some package styles. Importing the css inside my sass file is what worked for me