🕌

Next.js で SVG をインポートしたら型が any になる問題を解決する

2024/01/16に公開

🌼 はじめに

最近 Next.js の開発で SVG を扱うことがありますが、インポートした SVG の型が any になってめんどくさいなと思ってました。

例えば、以下のように SVG をインポートして、オブジェクトや配列の値として入れます。

import UserIcon from "@/assets/svg/user_icon.svg"

type ComponentProps = { userName: string }

const Component = ({ userName }: ComponentProps) => {
  const user = { 
    userName, 
    icon: UserIcon 
  } // user の型が { userName: string; icon: any } になってしまう
  
  // ...
}

この状態だと user.icon の型は any に推論されます。もし他のところで user.icon の型で React.FC<React.SVGProps<SVGElement>> を期待してるなら、型があわなくてエラーになるでしょう。

仕方ないので形をアサーションするとなんとかなりますが、、

const user = {
  userName,
  icon: UserIcon as React.FC<React.SVGProps<SVGElement>>
}

SVG 使う度に型アサーションしないといけないって絶対めんどくさい!と思って自動で型がつくようにしました。その解決案を共有します。

+) ちなみに Next.js は App Router(14.0.4)を使ってました。

1. 原因: なぜ SVG の型が any になるのか

そもそも、なぜ SVG をインポートしたら型が any になるかというと、Next.js が SVG の型を any に指定しているからです。

Next.js + TypeScript のプロジェクトには、ルートに next-env.d.ts というファイルがあると思います。これは TypeScript のコンパイラーが Next.js の型を認識できるようにしるファイルです。

そして、その next-env.d.tsnext/image-types/global.d.ts という型ファイルを呼んでおり、その中に SVG の型が定義されています。

https://github.com/vercel/next.js/blob/canary/packages/next/image-types/global.d.ts#L10-L19

「プラグインとのコンフリクトを避けるために any を使ってる」というコメントもあるので、意図的に any にしてることは間違いないです。

2. 解決: カスタムの型定義ファイルを追加する

残念ながら next-env.d.ts は自動生成されるので、このファイルを直接変更しても新しく自動生成されたら変更内容が消えるのでカスタムすることはできません。

でも方法はあります。カスタムの型定義ファイルを追加することです。

https://nextjs.org/docs/app/building-your-application/configuring/typescript#custom-type-declarations

まずは、プロジェクトのルートに新しい型定義ファイルを追加します。私は svg.d.ts という名前にしましたが、ファイル名は何でも大丈夫です。

そしてそのファイルに、SVG の型情報を書きます。

svg.d.ts
declare module "*.svg" {
  const content: React.FC<React.SVGProps<SVGElement>>

  export default content
}

そして tsconfig.json"include" 配列に、svg.d.ts を追加します。

注意点は、必ず svg.d.tsnext-env.d.tsより前におくことです。next-env.d.tsより後においたらちゃんと反映されず、SVG が any のままでした。

tsconfig.json
{
  "include": [
+   "svg.d.ts", 
    "next-env.d.ts",
    ".next/types/**/*.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": ["node_modules"]
}

そしてキャッシュ消したり再ビルドしたりして、新しい型を反映させます。

これで SVG をインポートしたら型がちゃんと React.FC<React.SVGProps<SVGElement>> になってました。やったー!

🌷 終わり

型定義カスタムは別の場面でも使えそうな気がします。めんどくさがってよかった。

GitHubで編集を提案

Discussion