Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@svgr/webpack does not work with Next.js #897

Open
jarimustonen opened this issue Aug 20, 2023 · 14 comments
Open

@svgr/webpack does not work with Next.js #897

jarimustonen opened this issue Aug 20, 2023 · 14 comments

Comments

@jarimustonen
Copy link
Contributor

🐛 Bug Report

@svgr/webpack does not work with next 13.4.19. It generates error message:

./public/next.svg
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
> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" 

To Reproduce

Run the basic project creation: npx create-next-app@latest.

Run npm install --save-dev @svgr/webpack

Update next.config.js per https://react-svgr.com/docs/next/

In the file src/app/page.tsx replace the next.svg reference with

import Logo from '../../public/next.svg';

and

<Logo />

Expected behavior

Expect the image to work.

Run npx envinfo --system --binaries --npmPackages @svgr/core,@svgr/cli,@svgr/webpack,@svgr/rollup --markdown --clipboard

Paste the results here:

## System:
 - OS: macOS 13.4.1
 - CPU: (10) arm64 Apple M1 Max
 - Memory: 981.70 MB / 32.00 GB
 - Shell: 5.9 - /bin/zsh
## Binaries:
 - Node: 20.4.0 - /opt/homebrew/bin/node
 - npm: 9.7.2 - /opt/homebrew/bin/npm
## npmPackages:
 - @svgr/webpack: ^8.1.0 => 8.1.0 
@jarimustonen
Copy link
Contributor Author

And for extended details.

Selections when creating next app

✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use src/ directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias? … No

Full version of next.config.js:

Here is the full version of the config file:

/** @type {import('next').NextConfig} */
const nextConfig = {
    webpack(config) {
        // Grab the existing rule that handles SVG imports
        const fileLoaderRule = config.module.rules.find((rule) =>
            rule.test?.test?.('.svg'),
        )

        config.module.rules.push(
            // Reapply the existing rule, but only for svg imports ending in ?url
            {
                ...fileLoaderRule,
                test: /\.svg$/i,
                resourceQuery: /url/, // *.svg?url
            },
            // Convert all other *.svg imports to React components
            {
                test: /\.svg$/i,
                issuer: /\.[jt]sx?$/,
                resourceQuery: { not: /url/ }, // exclude if *.svg?url
                use: ['@svgr/webpack'],
            },
        )

        // Modify the file loader rule to ignore *.svg, since we have it handled now.
        fileLoaderRule.exclude = /\.svg$/i

        return config
    },
}

module.exports = nextConfig

Simple version of page.tsx generating the error

Here's a simple version of src/app/page.tsx that generates the error:

import Logo from '../../public/next.svg';

export default function Home() {
  return (
    <main>
      <Logo />
    </main>
  )
}

@Karina1703
Copy link

Same problem

@Volosojui
Copy link

Same problem here

@Volosojui
Copy link

@jarimustonen @Karina1703 I've found the solution in case you still need it
#860 (comment)

@jarimustonen
Copy link
Contributor Author

Thank you. To state the obvious: This fixed version should be used here: https://react-svgr.com/docs/next/

@gregberge
Copy link
Owner

Feel free to submit a PR to update the doc please!

@jarimustonen
Copy link
Contributor Author

I know the fix works but I don't understand it. Anyway, I did make a pull request for it. Here: #898

@pascalpp
Copy link

pascalpp commented Oct 14, 2023

FWIW I just ran into this issue with a brand new nextjs install (first time trying it out). The instructions at https://react-svgr.com/docs/next/ didn't work for me, but cribbing from there I figured out this solution:

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack(config) {
    // grab the default rule for handling all images
    const fileLoaderRule = config.module.rules.find((rule) => rule.test?.test?.('.svg'))

    config.module.rules = [
      // keep all rules except the default image loader rule
      ...config.module.rules.filter((rule) => rule !== fileLoaderRule),

      // re-add the default image loader rule, but exclude svg
      {
        ...fileLoaderRule,
        exclude: /\.svg$/i,
      },

      // add a new rule for svg files, excluding svg files that are imported as React components
      {
        ...fileLoaderRule,
        test: /\.svg$/i,
        resourceQuery: {
          ...fileLoaderRule.resourceQuery,
          not: [
            ...fileLoaderRule.resourceQuery.not,
            /component/, // *.svg?component
          ],
        },
      },

      // add a new rule for svg files that are imported as React components
      {
        test: /\.svg$/i,
        issuer: /\.[jt]sx?$/,
        use: '@svgr/webpack',
        resourceQuery: /component/, // *.svg?component
      },
    ]

    return config
  },
}

module.exports = nextConfig

With the above config, importing an SVG as a component is opt-in, using ?component on the path, so you can still use regular SVG imports with Next's Image component if you want.

import PngImage from './some.png'
import JpegImage from './some.jpg'
import GifImage from './some.gif'
import SvgImage from './some.svg'
import SvgComponent from './some.svg?component'

export default function App() {
  return (
    <main>
      <Image src={PngImage} alt="PNG Image" />
      <Image src={JpegImage} alt="JPEG Image" />
      <Image src={GifImage} alt="GIF Image" />
      <Image src={SvgImage} alt="SVG Image" />
      <SvgComponent />
    </main>
  )
}

Finally, if you're using TypeScript, you'll want to declare a module for this import type. I put this in src/types.ts:

declare module '*.svg?component' {
  import { FC, SVGProps } from 'react'
  const content: FC<SVGProps<SVGElement>>
  export default content
}

Doing that gets you nice autocomplete for passing SVG props to your SVG components:

image
Original comment

Update: erg I just discovered that the setup I described below breaks non-component SVG imports. Will update again when I have a fix.

FWIW I just ran into this issue with a brand new nextjs install (first time trying it out). The instructions at https://react-svgr.com/docs/next/ didn't work for me, but cribbing from there I figured out this solution:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack(config) {
    config.module.rules.push({
      test: /\.svg$/i,
      issuer: /\.[jt]sx?$/,
      use: ['@svgr/webpack'],
      resourceQuery: /component/, // *.svg?component
    })

    return config
  },
}

module.exports = nextConfig

So importing an SVG as a component is opt-in, using ?component on the path, leaving the existing NextJS SVG rules alone.

import SomeIcon from './some-icon.svg?component'

export default App() {
  return (
    <SomeIcon/>
  )
}

Finally, if you're using TypeScript, you'll want to declare a module for this import type. I put this in src/types.ts:

declare module '*.svg?component' {
  import { FC, SVGProps } from 'react'
  const content: FC<SVGProps<SVGElement>>
  export default content
}

Doing that gets you nice autocomplete for passing SVG props to your SVG components:
image

@MrOxMasTer
Copy link

Do you have svgs working in server components? I just have such a mistake:

Uncaught Error: Unsupported Server Component type: {...}

@MrOxMasTer
Copy link

image
image

@MrOxMasTer
Copy link

FWIW I just ran into this issue with a brand new nextjs install (first time trying it out). The instructions at https://react-svgr.com/docs/next/ didn't work for me, but cribbing from there I figured out this solution:

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack(config) {
    // grab the default rule for handling all images
    const fileLoaderRule = config.module.rules.find((rule) => rule.test?.test?.('.svg'))

    config.module.rules = [
      // keep all rules except the default image loader rule
      ...config.module.rules.filter((rule) => rule !== fileLoaderRule),

      // re-add the default image loader rule, but exclude svg
      {
        ...fileLoaderRule,
        exclude: /\.svg$/i,
      },

      // add a new rule for svg files, excluding svg files that are imported as React components
      {
        ...fileLoaderRule,
        test: /\.svg$/i,
        resourceQuery: {
          ...fileLoaderRule.resourceQuery,
          not: [
            ...fileLoaderRule.resourceQuery.not,
            /component/, // *.svg?component
          ],
        },
      },

      // add a new rule for svg files that are imported as React components
      {
        test: /\.svg$/i,
        issuer: /\.[jt]sx?$/,
        use: '@svgr/webpack',
        resourceQuery: /component/, // *.svg?component
      },
    ]

    return config
  },
}

module.exports = nextConfig

With the above config, importing an SVG as a component is opt-in, using ?component on the path, so you can still use regular SVG imports with Next's Image component if you want.

import PngImage from './some.png'
import JpegImage from './some.jpg'
import GifImage from './some.gif'
import SvgImage from './some.svg'
import SvgComponent from './some.svg?component'

export default function App() {
  return (
    <main>
      <Image src={PngImage} alt="PNG Image" />
      <Image src={JpegImage} alt="JPEG Image" />
      <Image src={GifImage} alt="GIF Image" />
      <Image src={SvgImage} alt="SVG Image" />
      <SvgComponent />
    </main>
  )
}

Finally, if you're using TypeScript, you'll want to declare a module for this import type. I put this in src/types.ts:

declare module '*.svg?component' {
  import { FC, SVGProps } from 'react'
  const content: FC<SVGProps<SVGElement>>
  export default content
}

Doing that gets you nice autocomplete for passing SVG props to your SVG components:

image Original comment

have you had problems with server components?

@alexleach
Copy link

I was also having a nightmare with this. I've just started a new Material UI project, using the Next.js App router. None of the code examples in official documentation [SVGR docs, MUI docs] seem to work with this software stack.

I was trying to use MUI's SvgIcon class as described in the MUI docs, which suggests using SVGR to render svg files as React Components.

A couple of GitHub Issue comments pointed me in the right direction:

These suggest adding those various changes to next.config.js, which then at least would get webpack to use @svgr/webpack to compile the svg as a React component.

However, following the MUI docs, I used the code <SvgIcon component={MyIcon} > to wrap my svg icon file, and started getting the below error. Maybe, this is similar to what you were having, @MrOxMasTer ?

Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".

Anyway, what fixed it was the recommendation in the next.js docs:

In a parent Server Component, you can import both the <ClientComponent> and <ServerComponent> and pass <ServerComponent> as a child of <ClientComponent>

For example

//layout.tsx
import * as React from 'react';
import SvgIcon from '@mui/material/SvgIcon';
import MyIcon from '/public/images/my-icon.svg';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <SvgIcon>
          <MyIcon />
        </SvgIcon>
      </body>
    </html>
  )

Next steps:

  • There doesn't seem to be an issue in MUI for this, so I'll open one now.
  • I'd like to re-implement the (great) suggestion by @pascalpp, of writing a types.ts file, but instead of adding the SVGProps from react, adding the props from SvgIconProps or SvgIconOwnProps, from MUI. I'm getting errors when I try to do that...

I'm not sure if there's anything that could be done within @svgr/webpack to help simplify this, or if it's just an issue with the various documentation online?

@LambArchie
Copy link

Do you have svgs working in server components? I just have such a mistake:

Uncaught Error: Unsupported Server Component type: {...}

I was facing the same issue, figured out it was because I was using turbopack (via next dev --turbo) which doesn't use the webpack config in next.config.js.
I needed to follow this comment I found to fix this problem vercel/turbo#4832 (comment)

@apetta
Copy link
Contributor

apetta commented Mar 1, 2024

For those struggling with SVGR in next 14+ with Typescript, here's what worked for me:

  1. Ensure next.config.js is set up as per svgr docs

  2. Add a type declaration file at the root of repo (e.g. svgr.d.ts) that contains:

declare module '*.svg' {
  import { FC, SVGProps } from 'react'
  const content: FC<SVGProps<SVGElement>>
  export default content
}

declare module '*.svg?url' {
  const content: any
  export default content
}
  1. Extend tsconfig.json include field to add the decleration file (Note: order is important!):
  "include": [
    "svgr.d.ts",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts"
  ],

You should now have everything working with VSCode autocomplete, e.g.: import Logo from "@/assets/logo.svg";, and you can still use static imports with next/image with import Logo from "@/assets/logo.svg?url";

For configuration of svgr, add a config file to the root (e.g. svgrrc.json) with your desired config, for example:

{
  "dimensions": false,
  "svgoConfig": {
    "plugins": [
      "removeDimensions",
      {
        "name": "convertColors",
        "params": {
          "currentColor": true
        }
      },
      {
        "name": "preset-default",
        "params": {
          "overrides": {
            "removeTitle": false
          }
        }
      }
    ]
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants