DEV Community 👩‍💻👨‍💻

Kevin Cunningham
Kevin Cunningham

Posted on

Importing SVGs to Next.js

SVGs are great! They are scalable vector graphics. As they are vector based they can be scaled to be huge or tiny and they won't pixelate. They are made up of XML describing the paths and shapes needed to make the final image. They are an excellent choice for responsive web design but importing them to your Next.js application isn't always straight-forward.

This article will look at a few different ways to approach this common task.

Including directly

<svg> is a standard HTML tag that can be directly used in JSX. That means if your SVG is quite short, it can be easiest to include it in place.

Using the <img> tag

You can use a regular img tag and reference the SVG by URL. You need to place the image in the /public directory and reference it relative to that.

<img src="/eye.svg" alt="An SVG of an eye" />
Enter fullscreen mode Exit fullscreen mode

This file would be in the root of the public directory.

Use next/image

Equally, you could use the Image component that is included in next/image. The only thing to be aware here is that this component requires the height and width to be passed through as props.

<Image src="/eye.svg" height={30} width={30} />
Enter fullscreen mode Exit fullscreen mode

As a component

You can include it as component in your document and call it as you need. This has the benefit of keeping the SVG code in one place.

It isn't much further from this to have this SVG as a component in a separate file.

Once you have your SVG as a component, you can pass it props to alter it's appearance. You receive the props and then use them directly on your SVG.

Importing as a file

If you try to import an SVG directly as a file like this,

import eye from "../assets/eye.svg";
Enter fullscreen mode Exit fullscreen mode

you will get an error that looks like this:

 Module parse failed: Unexpected token (1:2)
 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
Enter fullscreen mode Exit fullscreen mode

To be able to make this work, you'll need to either add a webpack or babel extension.

Let's look at a webpack solution first.

SVGR

This tool comes built in with create-react-app, so if you've used that before then this will be a familiar tool.

First, use yarn or npm to install @svgr/webpack to your project. You'll then need to create or add to your next.config.js in the root of your project.

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

    return config;
  }
};
Enter fullscreen mode Exit fullscreen mode

Webpack can sometimes be a little bit incomprehensible to me. Here we are adding a rule (rules.push). Specifically, we test if the file is an SVG and, if it is, we use the library we installed above.

It's worth noting that if you make changes to your next.config.js you'll need to restart the development server before you'll notice the changes.

Once restarted, the error above should be gone.

babel plugin

If you'd rather not hook into webpack, you can use babel instead. There is a plugin called babel-plugin-inline-react-svg. Install the library and then create a .babelrc in the root of your repo.

.babelrc is an object that has two keys, presets and plugins. In presets, we are going to add next/babel. This is included in the project anyway but by adding a configuration file we need to specify it explicitly.

For our plugins, we are going to add inline-react-svg. So, the final file will look like this.

{
  "presets": [
    "next/babel"
  ],
  "plugins": [
    "inline-react-svg"
  ]
}
Enter fullscreen mode Exit fullscreen mode

next-images

If you'd rather have one solution that covers a number of different image types, then next-images is a library that might be of interest. This is a plugin optimised for Next.js and allows for a svg, ico, jp2 and other types of images to be imported into your projecct.

Once the dependency has been installed, this one needs a next.config.js configuration as well.

const withImages = require("next-images");
module.exports = withImages();
Enter fullscreen mode Exit fullscreen mode

Which one should you use?

I'm not the boss of you :)

I have a decision tree that looks a bit like this:

  • Do I want control over the SVG?
    • Component with props
  • Is it an image that is going to be content managed?
    • next-images or webpack
  • Is it an image I'm only going to use once?
    • Stick it in the public direct and reference it directly

How about you? Which approach do you prefer and why?

Here's a Codesandbox with all of the various options we've discussed in this post.


Come follow me on Twitter. I share helpful tips and fun things I find on the web.

Top comments (7)

Collapse
davidalejandroaguilar profile image
David Alejandro

Hey, thanks for this compilation!

SVGR Webpack worked for me to create a dynamic Icon component. Based on this SO answer.

Collapse
hyetigran profile image
hyetigran

One thing to watch out for when using SVGR is that it was erring out when using the latest version. Downgrading to an earlier versin 5.5.0 seemed to do the trick. Not sure why exactly.

-react 17.0.2
-next 11.0.1
-typescript 4.5.5

Collapse
emmiep profile image
Emmie Päivärinta

I didn't have any luck with the babel plugin. I would've preferred that because I don't really like having to configure webpack either, but svgr worked out of the box and I suppose once you get used to configuring webpack in next it's not that big of a deal.

It seems like this is the same issue I had with the babel plugin: github.com/airbnb/babel-plugin-inl...
Seemingly its SVG parser has issues with some SVG files.

Collapse
jcubic profile image
Jakub T. Jankiewicz

Thanks for the article. One note with SVGR you need:

import Eye from "../assets/eye.svg";
// and in JSX use
<Eye/>
Enter fullscreen mode Exit fullscreen mode
Collapse
ooloth profile image
Michael Uloth

The webpack approach worked better for than the babel option.

With the babel approach, I always had reconciliation errors in the console when I tried to add aria-hidden to an SVG.

Collapse
travelintervals profile image
Euphoric

Is it an image that is going to be content managed?

What does "content managed" mean?

Collapse
patarapolw profile image
Pacharapol Withayasakpunt

Which one is the most optimized?

🌚 Life is too short to browse without dark mode