Получение компонента изображения 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";

Как я это понял

  1. Прочитайте эти документы: https://react-svgr.com/docs/webpack/
  2. Использовал этот фрагмент, чтобы получить правило по умолчанию, применяемое к svgs.
      webpack(config) {
    const defaultSvgLoader = config.module.rules.find(
      (rule) => typeof rule?.test?.test === "function" && rule.test.test(".svg")
    );
    console.log(defaultSvgLoader);
}
  1. Добавлен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}/>
Другие вопросы по тегам