Получение компонента изображения NextJS и @ svgr / webpack для совместной работы
у меня есть
next.js
сайт с установленной библиотекой. Я настроен на работу с
@svgr/webpack
и теперь вы хотите импортировать изображение svg и использовать его с новым
next/image
компонент.
Вот как я настроил свой
next.config.js
файл:
module.exports = {
images: {
domains: ["images.vexels.com"],
},
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["@svgr/webpack"],
});
return config;
},
};
И вот что я пытаюсь сделать:
import Image from 'next/image'
import Logo from '@/svg/logo.svg'
<Image src={Logo} width={174} height={84} />
Однако, когда я это сделаю, я получаю следующую ошибку:
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
Я подумал, что, возможно, мне следует включить компонент Logo как фактический компонент, например:
<Image src={<Logo />} width={174} height={84} />
Однако это тоже не сработало.
Есть идеи, что не так и как это исправить?
Спасибо.
4 ответа
С вашим текущим импортом конфигурации веб-пакета
@/svg/logo.svg
импортирует SVG как компонент React.
Чтобы импортировать его как URL-адрес данных , вам понадобится следующая конфигурация веб-пакета в вашем
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;
}
};
После этого вы сможете использовать его следующим образом:
import Image from 'next/image'
import svgUrl from '@/svg/logo.svg'
<Image src={svgUrl} width={174} height={84} />
Другие ответы жертвуют поведением импорта ширины + высоты по умолчанию, предоставляемым NextJS. Мой ответ ниже сохраняет это поведение, поэтому вам не нужно вручную проверять размеры файла.
Желаемое использование
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)
Требуется next.config.js
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: "" },
});
}
Требуемое объявление машинописного текста (при использовании ts)
declare module "*.svg?svgr";
Как я это понял
- Прочитайте эти документы: https://react-svgr.com/docs/webpack/
- Использовал этот фрагмент, чтобы получить правило по умолчанию, применяемое к svgs.
webpack(config) {
const defaultSvgLoader = config.module.rules.find(
(rule) => typeof rule?.test?.test === "function" && rule.test.test(".svg")
);
console.log(defaultSvgLoader);
}
- Добавлен
resourceQuery: { not: [/svgr/] }
в зарегистрированный выходной объект, чтобы*.svg?svgr
пути будут игнорироваться
Это похоже на предыдущие ответы, но единственный способ, который сработал для меня, разрешить импорт только URL-адресов, а также импорт компонентов React:
// 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
};
Объявление TypeScript (при необходимости)
// 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 />
Обходным путем для этого может быть наличие определенного шаблона для
svg
имя файла, а затем настроить загрузчик по умолчанию для игнорирования этого шаблона и
svgr/webpack
чтобы загрузить совпадения для этого шаблона
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;
},
здесь я использую шаблон
*.icon.svg
, поэтому любое изображение svg, которое заканчивается им, можно использовать следующим образом.
import Logo from "whatever/logo.icon.svg
const Whatever = () => <Logo />
и для других значков это будет работать
import Image from "next/image";
import Logo from "whatever/logo.svg"
const Whatever = () => <Image src={Logo} alt="logo" width={100} height={100}/>