📚

Next.js + TypeScriptでimportしたSVGの型がanyになってしまう

2021/08/27に公開5

前提: TypeScript + Next.js + svgrでSVGをインポートする

自分の関わっているNext.js製アプリケーションでは、svgrを導入することによりSVGを他のReactコンポーネントと同じ形でimportできるようにしています。

この記事の本筋からそれますが、svgrは以下のようにNext.jsへの導入できます。

↓ インストール

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

@svgr/webpackをWebpackのloaderとして設定します。細かな設定はsvgrのドキュメントを確認してください。

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

↓ 他のコンポーネントと同じように扱うために*svgの型を宣言しておきます。

index.d.ts
declare module '*.svg' {
  const content: React.FC<React.SVGProps<SVGElement>>;
  export default content;
}

↓ これでSVGをimportできるようになりました。

import Icon from "./assets/icon.svg"

export const Foo = () => {
  return <div><Icon widht={16} height={16} /></div>
}

本題: importしたSVGがanyになってしまう

以下Next.js v11.1.0 時点での話です。上で紹介した方法でSVGファイルをimportすると、なぜかany型として扱われてしまいます。

import Icon from "./assets/icon.svg"
👆 `<Icon />`の型がanyになってしまう

自分で書いたdeclare module '*.svg' { ... }が反映されていないというわけです。

なぜanyになってしまうのかを追ってみると、Next.jsのnode_modules/next/image-types/global.d.tsに同じく*.svgへの宣言がされており、そちらが優先されてしまっているようです。

// node_modules/next/image-types/global.d.ts

declare module '*.svg' {
  /**
   * Use `any` to avoid conflicts with
   * `@svgr/webpack` plugin or
   * `babel-plugin-inline-react-svg` plugin.
   */
  const content: any

  export default content
}

Next.jsの画像のimportについての型定義を無効にする

色々と調べてみると、公式ドキュメントに以下のような記述がありました。
https://nextjs.org/docs/basic-features/image-optimization#disable-static-imports

Disable Static Imports

The default behavior allows you to import static files such as import icon from './icon.png and then pass that to the src property.

In some cases, you may wish to disable this feature if it conflicts with other plugins that expect the import to behave differently.

You can disable static image imports with the following configuration below.

next.config.js
module.exports = {
 images: {
   disableStaticImages: true,
 },
}

Next.jsではデフォルトでjpegやpngやsvgなどのファイルをimportできるようにするためにnext/image-types/global.d.tsで画像ファイルに関する型定義をしてくれているようです。

Next.js v11.1.0時点ではsvgのimportはanyとなっており、これが自分で書いたものより優先されてしまっていたというわけです。

自分のプロジェクトではsvg以外の画像ファイルをimportして読み込むことはないため、ドキュメントの通り設定を無効にすることにしました。

next.config.js
module.exports = {
 images: {
   disableStaticImages: true,
 },
}

これで自分で用意したdeclare module '*.svg' { ... }が効くようになりました。

Discussion

hanetsukihanetsuki

next.config.jsに対して下記のように型定義が効くように起票した場合。
imagesで添付画像のようなエラーが出てしまいました。🤔

何か考えられる原因等もし知っていたらお知恵をお借りできたらと思っております。

現状は// @ts-checkを外して対応エラーが出ないようにしてあります。

// @ts-check

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack: (config) => {
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack']
    })
    return config
  },
  images: {
    disableStaticImages: true
  }
}

▽ vs codeのエラー

型定義情報を調べたところImageConfigには、disableStaticImages がちゃんとありました...

catnosecatnose

自分のプロジェクトでは.jsファイルを型エラーのチェック対象としていないため、ちょっと分からないですね…。

ただImageConfigの型定義をimageSizedなどプロパティがoptionalになっていないため、images.disableStaticImagesだけ指定すれば確かに型エラーとなるはずです。

export declare type ImageConfig = {
    deviceSizes: number[];
    imageSizes: number[];
    loader: LoaderValue;
    path: string;
    domains?: string[];
    disableStaticImages?: boolean;
    minimumCacheTTL?: number;
};

Next.js側でそのうち修正される気がしますね。

hanetsukihanetsuki

確かに、必須項目部分がありますね。
見落としていました👀

issueとPRの作成ありがとうございます!

catnosecatnose

こちらマージされたので今後のリリースで修正されると思います!