Reputation: 7007
I have a Next.js site with the @svgr/webpack
library installed. I have configured next.config.js
to work with @svgr/webpack
and now want to import an svg image and use it with the new next/image
component.
Here is how I set up my next.config.js
file:
module.exports = {
images: {
domains: ["images.vexels.com"],
},
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["@svgr/webpack"],
});
return config;
},
};
And here is what I am trying to do:
import Image from 'next/image'
import Logo from '@/svg/logo.svg'
<Image src={Logo} width={174} height={84} />
However, when I do that I get the following error:
Unhandled Runtime Error
TypeError: src.startsWith is not a function
Source
client\image.tsx (278:13) @ Image
276 | let isLazy =
277 | !priority && (loading === 'lazy' || typeof loading === 'undefined')
> 278 | if (src && src.startsWith('data:')) {
| ^
279 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
280 | unoptimized = true
281 | isLazy = false
I thought that perhaps I should include the Logo component as an actual component, like this: <Image src={<Logo />} width={174} height={84} />
However, that also did not work.
Any idea what is wrong and how to fix it?
Upvotes: 10
Views: 11656
Reputation: 1
I also working on it and just want to share my solution and touch all nuances. I am using NextJS (new App router) with tailwind CSS. I have next.config.mjs that I grabbed from the answers above.
// next.config.mjs
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({
oneOf: [
// 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;
},
};
export default nextConfig;
I have the following folders structure: src --app --components --assets ----images (png, gif, jpeg ...) ------images.d.ts ----icons (svg) ------svg.d.ts
// svg.d.ts
declare module "*.svg" {
const content: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
export default content;
}
/** svg imports with a ?url suffix can be used as the src value in Image components */
declare module "*.svg?url" {
import type { StaticImport } from "next/image";
const defaultExport: StaticImport | string;
export default defaultExport;
}
// images.d.ts
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.bmp';
declare module '*.tiff';
declare module '*.webp';
You do not need to import svg.d.ts inside tsconfig.json. And inside my tsx component I can import my logo.svg
as React Component or URL (to paste this URL into Next/image)
import Logo from "assets/icons/logo.svg";
import logo from "assets/icons/logo.svg?url";
<Link href="/" className="flex-auto">
{/* as data url */}
<Image src={logo} width={155} height={28} alt="logotype" />
</Link>
{/* as component */}
<Logo />
That is all. Good luck.
Upvotes: 0
Reputation: 2300
This is similar to previous answers, but the only way that worked for me to allow for both url-only imports as well as React component imports:
// next.config.js
module.exports = {
webpack(config) {
// Grab the existing rule that handles SVG imports
const fileLoaderRule = config.module.rules.find(
(rule) => rule.test && rule.test.test?.(".svg")
);
config.module.rules.push({
oneOf: [
// 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;
},
// ...other config
};
// svg.d.ts
/** svg imports with a ?url suffix can be used as the src value in Image components */
declare module "*.svg?url" {
import { StaticImport } from "next/image";
const defaultExport: StaticImport | string;
export default defaultExport;
}
import Image from "next/image";
import Icon from "./my-icon.svg";
import iconUrl from "./my-icon.svg?url"
// ...
<Image src={iconUrl} />
<Icon />
Upvotes: 3
Reputation: 143
Other answers sacrifice the default width + height importing behavior provided by NextJS. My answer below retains this behaviour so that you don't need to manually check the dimensions of the file
import MySVG from "./mySVG.svg?svgr"; // SVGR loader
<MySVG />
import Image from "next/image";
import mySVG from "./mySVG.svg"; // Default NextJS loader
<Image src={mySVG} alt="" /> // (width and height will be applied automatically)
webpack(config, { dev: isDev, isServer }) {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
resourceQuery: /svgr/, // only use svgr to load svg if path ends with *.svg?svgr
use: ["@svgr/webpack"],
});
// Re-add default nextjs loader for svg
config.module.rules.push({
test: /\.svg$/i,
loader: "next-image-loader",
issuer: { not: /\.(css|scss|sass)$/ },
dependency: { not: ["url"] },
resourceQuery: { not: [/svgr/] }, // Ignore this rule if the path ends with *.svg?svgr
options: { isServer, isDev, basePath: "", assetPrefix: "" },
});
}
declare module "*.svg?svgr";
webpack(config) {
const defaultSvgLoader = config.module.rules.find(
(rule) => typeof rule?.test?.test === "function" && rule.test.test(".svg")
);
console.log(defaultSvgLoader);
}
resourceQuery: { not: [/svgr/] }
into the logged output object so that *.svg?svgr
paths will be ignoredUpvotes: 7
Reputation: 63
A workaround for this maybe by having a specific pattern for svg
file name, and then configuring the default loader to ignore this pattern and svgr/webpack
to load matches for this pattern
webpack(config) {
const fileLoaderRule = config.module.rules.find(
(rule) => rule.test && rule.test.test(".svg")
);
fileLoaderRule.exclude = /\.icon\.svg$/;
config.module.rules.push({
test: /\.icon\.svg$/,
loader: require.resolve("@svgr/webpack"),
});
return config;
},
here i'm using the pattern *.icon.svg
, so any svg image that ends with it can be used like this
import Logo from "whatever/logo.icon.svg
const Whatever = () => <Logo />
and for other icons this'll work
import Image from "next/image";
import Logo from "whatever/logo.svg"
const Whatever = () => <Image src={Logo} alt="logo" width={100} height={100}/>
Upvotes: 1
Reputation: 50368
With your current webpack config importing @/svg/logo.svg
will only import the SVG file as a React component.
To import it as a data URL, you will need the following webpack config in your next.config.js
.
module.exports = {
images: {
domains: ['images.vexels.com']
},
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack', 'url-loader']
});
return config;
}
};
You'll then be able to use it in both ways: as a URL or as a component.
import Image from 'next/image'
import svgUrl, { ReactComponent as Logo } from '@/svg/logo.svg'
<Image src={svgUrl} width={174} height={84} />
// or as a component
<Logo />
Upvotes: 11