next.js: SVGR fails to load SVGs with Next 11

UPDATE

A temporary solution that seems to work and does not disable Webpack 5

If you find any issues with this solution, please tag me @scottagirs to update as appropriate. By @dohomi

  webpack (config) {
    const fileLoaderRule = config.module.rules.find(rule => rule.test && rule.test.test('.svg'))
    fileLoaderRule.exclude = /\.svg$/
    config.module.rules.push({
      test: /\.svg$/,
      loader: require.resolve('@svgr/webpack')
    })
    return config
  }

What version of Next.js are you using?

Next 11.0.0

What version of Node.js are you using?

14.x

What browser are you using?

Chrome, Brave

What operating system are you using?

macOS 11.4

How are you deploying your application?

localhost atm

Describe the Bug

https://discord.com/channels/752553802359505017/752668543891276009/854396809497673788

After upgrading from 10.x Next.js with webpack 4 config, to Nextjs 11, application fails to build with the below errors:

error - ./icon.svg
TypeError: unsupported file type: undefined (file: undefined)
Screen Shot 2021-06-15 at 1 12 25 PM

Full stack trace when trying to load the app (open in browser tab)

<span>----- Expand to view -----</span>
Error: Cannot find module '/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/.next/server/pages-manifest.json'
Require stack:
- /Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/require.js
- /Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/load-components.js
- /Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/api-utils.js
- /Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/next-server.js
- /Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/server/next.js
- /Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/server/lib/start-server.js
- /Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/cli/next-dev.js
- /Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/bin/next
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:880:15)
    at Function.mod._resolveFilename (/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/build/webpack/require-hook.js:4:1855)
    at Function.Module._load (internal/modules/cjs/loader.js:725:27)
    at Module.require (internal/modules/cjs/loader.js:952:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at getPagePath (/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/require.js:1:735)
    at requirePage (/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/require.js:1:1397)
    at loadComponents (/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/load-components.js:1:1289)
    at DevServer.findPageComponents (/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/next-server.js:77:296)
    at DevServer.renderErrorToHTML (/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/next-server.js:139:209) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/require.js',
    '/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/load-components.js',
    '/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/api-utils.js',
    '/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/next-server/server/next-server.js',
    '/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/server/next.js',
    '/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/server/lib/start-server.js',
    '/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/cli/next-dev.js',
    '/Users/scottagirs/workspace/iJS/ijs.to/iJStoFE/node_modules/next/dist/bin/next'
  ]
}

To Reproduce

Upgrade to Next 11 and add next.config.js

module.exports = {
  webpack(config, options) {
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack'],
    });

    return config;
  },
};

in package.json

"@svgr/webpack": "^5.5.0",
"next": "^11.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 116
  • Comments: 38 (9 by maintainers)

Commits related to this issue

Most upvoted comments

The fix is available in next@11.0.1-canary.4

You can try it out today with yarn add next@canary. Thanks!

I’m not entirely sure if #26281 and #26548 solve anything.

You test specifically for rule.test.test('.svg') https://github.com/vercel/next.js/blob/8cbaa409ca7dce8786cef5625700df094c9c7cd9/packages/next/build/webpack-config.ts#L1563 but most people running SVGR and next.js have more complicated regexes than just /.svg$/. For them, this fix doesn’t do much.

On top of that, you can now either use SVG in SVGR or in next/image, not both. At least not by default.

Consider our case. We filter all SVGs that match /\.react\.svg/ pattern (discord.react.svg would match, while checkbox.svg would not). Clearly, my regex pattern would not go through the current fix’s checks so the build fails on unsupported file type: undefined because I cannot mix SVG in both next.js and SVGR.

The workaround is to use this ugly config that includes the regex pattern for SVGR while excluding it in the next-image-loader (commented out part doesn’t work for some reason):

  webpack(config) {
    
   config.module.rules.push({
     test: /\.react\.svg$/,
     use: ["@svgr/webpack"]
   });

  //  config.module.rules.push({
  //    test: /\.(png|jpg|jpeg|gif|webp|ico|bmp|.svg)$/i,
  //    exclude: [
  //      /\.react\.svg$/,
  //    ],
  //    use: ["next-image-loader"]
  //  });

   const imageLoaderRule = config.module.rules.find(rule => rule.loader == "next-image-loader")
   imageLoaderRule.exclude = /\.react\.svg$/

   return config;
  }
 }

“Just use this config then” - Well, it’s incredibly ugly, requires maintenance from the user and if next.js is already trying to be smart, I don’t see why should I. Considering the logic is already there, can’t you just test if the non-next-image-loader RegExp contains .svg pattern (eg. .svg in react.svg) and exclude the whole previous regex pattern in next-image-loader (as what I tried to do with exclude)?

Because right now if you search for SVGR and next.js compatibility, you’ll find that it has been fixed. You test, realize the build is still not working and spend 4 hours tracking down the issues only to start reading the source code anyway. In the end, you realize the official support is minor and works on this specific issue that does not cover your use-case at all.

I don’t see a problem with the fix itself, but it would be nice if it would either support most (if not all) use-cases. It’s almost there already.

I have the same issue. When I add webpack5: false in next.config.js it works. I am using next-images without any loader configuration.

Thanks for reporting this issue!

A large percentage of Next.js user are relying on @svgr/webpack and similar alternatives so we’ll get this fixed!

See PR #26281

For anyone still looking for a solution, to add to what @dohomi suggested in https://github.com/vercel/next.js/issues/26130#issuecomment-863299674 - in case u’re using SVGO or need to configure any other options, this should work just fine:

next.config.js

webpack(config, options) {
    const fileLoaderRule = config.module.rules.find(rule => rule.test && rule.test.test('.svg'))
    fileLoaderRule.exclude = /\.svg$/

    config.module.rules.push({
      loader: '@svgr/webpack',
      options: {
        prettier: false,
        svgo: true,
        svgoConfig: {
          plugins: [{ removeViewBox: false }],
        },
        titleProp: true,
      },
      test: /\.svg$/,
    });

    return config;
}

I thought a lot about this change. First I was frustrated, then I reflected on how I am using SVGs. I’ve also been utilising SVGR to inline my SVGs. But only to be able to re-color and modify a few SVG attributes.

It actually makes sense to me not to inline SVGs if these are my requirements. But so far I had not thought about a different approach. Asking Google, it turned out it’d be an option to leverage the <use/> tag in combination with the src attribute that comes with StaticImageData. However, this requires all SVGs to have a unique id as in the following snippet:


// MySvg.svg
<svg id="root" ... />

// then use it later when imported through `next-image-loader`
import {src} from './MySvg.svg'

<svg>
   <use href={`${src}#root`} />
</svg>

That was not the case for my assets.

Therefore I experimented with a few webpack loaders. I tried svg-as-symbol-loader and string-replace-loader. The former had to be modified, the latter only allowed a simple string replacement, which can easily fail or be insufficient.

However, the experiment turned out to solve my issue AND my SVGs can now be cached properly while still being able to use my SVG attributes as before. I also don’t have to use an img tag, which I see as a less flexible alternative as we cannot style the svg any longer.

I went a step further and created a custom loader that is a bit smarter than those I tried before. You can find it here together with some docs that further describe its usage and implementation:

https://www.npmjs.com/package/svgid-loader

All it does is to add an id attribute to the first svg element it finds. Take a look at the sources here and leave your feedback.

Might not be a solution for everyone, but I actually will stick with it from now on as it also reduces markup, enables caching and complies with how nextjs loads such assets.

I flagged this issue yesterday in Discord (see https://discordapp.com/channels/752553802359505017/752647196419031042/854484227047555102), the proposed solution by @styfle was to use disableStaticImages: true, but this means losing support for some of the amazing new features for next/images. The other proposed solution was to import SVGs as fs.readFile('./file.svg') and then setting them using dangerouslySetInnerHTML, but I don’t think this is a viable solution as firstly the readFile needs to happen serverside and secondly it adds unnecessary complexity.

I believe that inline SVGs are a very common use case and this should be patched in a minor version release soon. One option could be to exclude SVGs from static imports, when svgr/webpack is set up in next.config.js

I added a feature request to internalize SVGR into Next.js’s core webpack configuration. Please voice your support there!

https://github.com/vercel/next.js/issues/26157

I would argue, that an inline SVG almost always has benefits for your performance and therefore the lighthouse score over additional requests by loading them from an external file. If you get to the point that the number of child elements of your DOM becomes an issue you have other problems to look at first 😉.

Exceptions for this rule are complex SVGs, which should probably be imported as an image or even be stored in a more efficient format (such as webp / png).

I found a temporary workaround until an official solution is available.

Firstly, add the following to next.config.js:

module.exports = {
  images: {
    disableStaticImages: true,
  },
};

After that, fix type checking errors via patch-package:

  • Remove the following snippet from node_modules/next/types/global.d.ts:
    declare module '*.svg' {
      const content: StaticImageData
    
      export default content
    }
    
  • Change a line as follows in node_modules/next/dist/client/image.d.ts:
      export declare type ImageProps = Omit<JSX.IntrinsicElements['img'], 'src' | 'srcSet' | 'ref' | 'width' | 'height' | 'loading' | 'style'> & {
          loader?: ImageLoader;
          quality?: number | string;
          priority?: boolean;
          loading?: LoadingValue;
          unoptimized?: boolean;
          objectFit?: ImgElementStyle['objectFit'];
          objectPosition?: ImgElementStyle['objectPosition'];
    - } & (StringImageProps | ObjectImageProps);
    + } & StringImageProps;
    

Run npx patch-package next and make sure to apply the patch in every environment by installing patch-package and adding it as a postinstall script.

You can try this solution which does not disable static images: https://github.com/vercel/next.js/issues/25950#issuecomment-863298702

…
webpack(config, options) {
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack', 'url-loader'],
    });

    return config;
},

When I tested it the order mattered. Using config.module.rules.push in my example first applies the svgo-loader, then the svgid-loader and afterwards the next-image-loader that is set for all image assets by nextjs. So yes, it works well. I’m stripping styles and convert them to inline. Otherwise some styles seem to not be applied. And using the extendDefaultPlugins should cover what SVGR comes out of the box with.

I’m facing this issue too sadly. I make heavy use of both next/image and inline SVGs (using SVGR) in most of my Next projects so the workarounds propsed aren’t adequate. Curious why SVG was added to static image type definitions when the use cases for inlining SVGs are so common?

Was using svg-url-loader, got following error

error - ./assets/images/logo.svg
TypeError: unsupported file type: undefined (file: undefined)

Tried after installing @svgr/webpack Still facing the same issue.