Next.js: SCSS modules cannot load images correctly with `next-images` package

Created on 18 Mar 2020  路  15Comments  路  Source: vercel/next.js

Update: Read my other comments below for a more pinpointed problem.

Bug report

Describe the bug

I tried to set a background image in a SCSS module with background: url("./footer-region-bg.jpg") repeat; (is in the same directory as the component). But this results in the following when fetching the image.

image

This was working with @zeit/next-sass, but it doesn't with the native scss modules.

To Reproduce

Load an Image from the same directory as the component in an SCSS module like mentioned above. Use the native SCSS modules from NextJs.

Expected behavior

The image should be loaded correctly

System information

  • OS: Windows
  • Version of Next.js: 9.3.1
upstream

Most helpful comment

I'm not sure if it is a next-images issue or the Next.js itself.

I faced the same issue after upgrading from 9.1.7 to 9.3.1. I was using next-sass and url-loader with the custom webpack config.

By using the built-in Sass feature, background and font face url()s inside .scss files stop working. After digging into Next.js files I realized that it handles url() imports through the file-loader now.

https://github.com/zeit/next.js/blob/v9.3.1/packages/next/build/webpack/config/blocks/css/index.ts#L278-L292

https://github.com/zeit/next.js/blob/v9.3.1/packages/next/build/webpack/config/blocks/css/index.ts#L17

I hadn't used the built-in CSS feature before and that is why I hadn't faced the issue.

Workaround

I ended up with excluding the url() file imports by setting a test regex in webpack issuer config as follows:

// next.config.js
module.exports = {
  // ...
webpack: (config) => {
    config.module.rules.push({
      issuer: {
        // nextjs already handles url() in css/sass/scss files
        test: /\.\w+(?<!(s?c|sa)ss)$/i,
      },
      test: /\.(jpg|gif|png|svg|ico)$/i,
      use: [
        {
          loader: 'url-loader',
          options: {
            // sample options
            limit: 8192,
            outputPath: '...',
            context: 'src',
            name: '[path][name].[hash:8].[ext]',
            publicPath: `your/public/path`,
          },
        },
      ],
    });

    return config;
  },
  // ...
};

Hope this helps.

All 15 comments

Sone new things I tried:

Didn't work:

  • Remove .next directory
  • Changing back to next 9.3.0

What worked:

  • Created a new Next.js project simulating the same scenario

I will now check why it works in a new project but not in my project

Alright, after some more testing I found something interesting. The problem can be reproduced when using the next-images package. My current next.config.js looks like this:

const withImages = require("next-images");

module.exports = withImages({
    poweredByHeader: false // This doesn't really matter
});

For some reason this config breaks the SCSS/CSS module images resolver.

Update: The same is true for the https://www.npmjs.com/package/next-optimized-images package.

same for me

Same here.
When I remove my url-loader related code in next.config.js, background-image works fine.
There's my code :

// next.config.js
// ....
    config.module.rules.push(
      {
        test: /\.(jpg|png|gif|ico)$/,
        use: [
          {
            loader: "url-loader"
          }
        ]
      }
    );
// ...

I'm not sure if it is a next-images issue or the Next.js itself.

I faced the same issue after upgrading from 9.1.7 to 9.3.1. I was using next-sass and url-loader with the custom webpack config.

By using the built-in Sass feature, background and font face url()s inside .scss files stop working. After digging into Next.js files I realized that it handles url() imports through the file-loader now.

https://github.com/zeit/next.js/blob/v9.3.1/packages/next/build/webpack/config/blocks/css/index.ts#L278-L292

https://github.com/zeit/next.js/blob/v9.3.1/packages/next/build/webpack/config/blocks/css/index.ts#L17

I hadn't used the built-in CSS feature before and that is why I hadn't faced the issue.

Workaround

I ended up with excluding the url() file imports by setting a test regex in webpack issuer config as follows:

// next.config.js
module.exports = {
  // ...
webpack: (config) => {
    config.module.rules.push({
      issuer: {
        // nextjs already handles url() in css/sass/scss files
        test: /\.\w+(?<!(s?c|sa)ss)$/i,
      },
      test: /\.(jpg|gif|png|svg|ico)$/i,
      use: [
        {
          loader: 'url-loader',
          options: {
            // sample options
            limit: 8192,
            outputPath: '...',
            context: 'src',
            name: '[path][name].[hash:8].[ext]',
            publicPath: `your/public/path`,
          },
        },
      ],
    });

    return config;
  },
  // ...
};

Hope this helps.

I'm not sure if it is a next-images issue or the Next.js itself.

I faced the same issue after upgrading from 9.1.7 to 9.3.1. I was using next-sass and url-loader with the custom webpack config.

By using the built-in Sass feature, background and font face url()s inside .scss files stop working. After digging into Next.js files I realized that it handles url() imports through the file-loader now.

https://github.com/zeit/next.js/blob/v9.3.1/packages/next/build/webpack/config/blocks/css/index.ts#L278-L292

https://github.com/zeit/next.js/blob/v9.3.1/packages/next/build/webpack/config/blocks/css/index.ts#L17

I hadn't used the built-in CSS feature before and that is why I hadn't faced the issue.

Workaround

I ended up with excluding the url() file imports by setting a test regex in webpack issuer config as follows:

// next.config.js
module.exports = {
  // ...
webpack: (config) => {
    config.module.rules.push({
      issuer: {
        // nextjs already handles url() in css/sass/scss files
        test: /\.\w+(?<!(s?c|sa)ss)$/i,
      },
      test: /\.(jpg|gif|png|svg|ico)$/i,
      use: [
        {
          loader: 'url-loader',
          options: {
            // sample options
            limit: 8192,
            outputPath: '...',
            context: 'src',
            name: '[path][name].[hash:8].[ext]',
            publicPath: `your/public/path`,
          },
        },
      ],
    });

    return config;
  },
  // ...
};

Hope this helps.

Works perfectly. I looked in the source code of the next-images and it basically includes a url-loader with a predefined configuration for you. I created an issue in their repository and I think that your fix should work there.

This is a bug with the next-images package(s), please file them upstream.

tl;dr the new CSS support does not require next-images, so next-images needs to be sure to only apply to JS files. There could be an option to "force on" for CSS files for legacy use cases.

Thanks guys for reporting this issue. I'll fix that in the next-images package today and will release a new patch for that.

@Timer Thank you. As an aside, it would be great if we can choose between file-loader and url-loader for url()s inside CSS/Sass files (or even setting the options, perhaps in the next.config.js?) to take advantage of the Data-URI thing.

@theshem Can you open a new issue proposing to add support for data URIs OOTB?

One thing to note, using data URIs are not generally a safe default because it's incompatible with SVG sprite systems, for example.

If we were to use data URIs out of the box (for small assets), SVGs would need to be excluded:
https://css-tricks.com/svg-fragment-identifiers-work/

Thanks to @theshem, this issue is solved now in next-images.

Closing as it was solved upstream. Thanks everyone!

The "fix" here appears to be mostly a workaround though. It would be nice if Next detected the use of image processing plugins like next-images or next-optimized-images, particularly the latter. Right now there's a working workaround for next-optimized-images but it doesn't allow you to run images imported in SCSS through i.e. optipng etc.

This might still be an upstream issue (some way to tie into Next's handling of images to apply optimizations?) but it seems like there's precedent here for the "detect and disable" functionality for handling images since that's what happens right now i.e. if you're using next-sass. From the official 9.3 blog post:

This new feature is fully backwards compatible. If you are using @zeit/next-sass or other CSS related plugins the feature is disabled to avoid conflicts.

I realize this issue mentions next-images specifically in the title, let me know if I should open a new issue instead.

The "fix" doesn't solve importing images into js files through template literals.

Given the src/styles/index.module.css file:

.test::before {
  content: url("./images/image.jpg");
}

Example 1

no next.config.js

src/pages/index.js:

import styles from "../styles/index.module.css";
export default function IndexPage() {
  return (
    <div>
      {/* this works */}
      <div className={styles.test}></div>
      {/* this doesn't work */}
      <div><img src={require('../styles/images/image.jpg')} /></div>
      {/* this doesn't work */}
      <div>{["image.jpg"].map((src) => (<img key={src} src={require(`../styles/images/${src}`)} />))}</div>
    </div>
  );
}

Example 2

next.config.js:

module.exports = {
  webpack: (config, options) => {
    config.module.rules.push({
      test: /\.(png|gif|jpg|jpeg)(\?|$)/,
      use: [
        {
          loader: "file-loader",
          options: {
            publicPath: `/_next/static/media/`,
            outputPath: `${options.isServer ? "../" : ""}static/media/`,
            name: "[name].[hash].[ext]",
          },
        },
      ],
    });

    return config;
  },
};

src/pages/index.js:

import styles from "../styles/index.module.css";
export default function IndexPage() {
  return (
    <div>
      {/* this doesn't work */}
      <div className={styles.test}></div>
      {/* this works */}
      <div><img src={require('../styles/images/image.jpg')} /></div>
      {/* this works */}
      <div>{["image.jpg"].map((src) => (<img key={src} src={require(`../styles/images/${src}`)} />))}</div>
    </div>
  );
}

Example 3

next.config.js:

module.exports = {
  webpack: (config, options) => {
    config.module.rules.push({
      test: /\.(png|gif|jpg|jpeg)(\?|$)/,
      issuer: {
        exclude: /\.(css|sass|scss)$/i,
      },
      use: [
        {
          loader: "file-loader",
          options: {
            publicPath: `/_next/static/media/`,
            outputPath: `${options.isServer ? "../" : ""}static/media/`,
            name: "[name].[hash].[ext]",
          },
        },
      ],
    });

    return config;
  },
};

src/pages/index.js:

import styles from "../styles/index.module.css";
export default function IndexPage() {
  return (
    <div>
      {/* this works */}
      <div className={styles.test}></div>
      {/* this works */}
      <div><img src={require('../styles/images/image.jpg')} /></div>
      {/* this doesn't work */}
      <div>{["image.jpg"].map((src) => (<img key={src} src={require(`../styles/images/${src}`)} />))}</div>
    </div>
  );
}

So what should I do to get all three this works together ?

Was this page helpful?
0 / 5 - 0 ratings