Next.js にてアイコンフォント(SVG と CSS の両方)を表示させる

Next.js でアイコンフォントを取り扱う際、
やる事が多かったのでメモ。

環境

  • Next.js v15
  • TypeScript v5

やりたいこと

  • SVG と CSS の両方でアイコンフォントを表示させる
  • 管理しやすいものを選ぶ

今回使用するアイコンフォント

今まで FontAwesome を使ってきたけど、
無料プランが使いづらくなってきたので、下記を使用してみる。

Material Symbols を試す

Next.js の App Router では外部から css ファイルを読み込めない。
Unsupported Metadata - Next.js

Unsupported Metadata
<link rel="stylesheet" />
import stylesheets directly in the layout or page itself.

Pages Router を使えば読み込めるらしい。
今回は App Router を使用するので、
SVG ファイルを適当にダウンロードして、それを読み込んで使用してみる。

構成

関係あるファイルのみ抜粋。
SVG ファイルは Material Symbols - Google Fonts から適当にダウンロードした。

next.config.ts
/src
  ├ /app
  │  ├ globals.scss
  │  └ page.tsx
  └ /assets
     └ search.svg
tsconfig.json

src/app/globals.scss

下記追加。

svg {
	width: 1em;
	height: 1em;
	fill: currentColor;
}

tsconfig.json

import パスの確認。

{
	"compilerOptions": {
		"paths": {
			"@/*": ["./src/*"]
		}
	}
}

src/app/page.tsx

Adding SVGs - Create React App によると、
ReactComponent を使うことによって、タグとして SVG を使えるらしい。
管理しやすい。

import { ReactComponent as Search } from '@/assets/search.svg'

export default function Home() {
	return (
		<>
			<Search/>
		</>
	)
}

エラーが出た。

Export ReactComponent doesn't exist in target module
The export ReactComponent was not found in module [project]/src/assets/search.svg.mjs { IMAGE => "[project]/src/assets/search.svg [app-rsc] (static)" } [app-rsc] (structured image object, ecmascript).

SVG components example with SWC - vercel/next.js - GitHub によると、
@svgr/webpack - npm を使って解決するらしい。

$ cd /path/to/your/project/app
$ npm install @svgr/webpack --save-dev

src/app/page.tsx を下記に変更。

import Search from '@/assets/search.svg'

export default function Home() {
	return (
		<>
			<Search/>
		</>
	)
}

このままだと下記エラーが出るので、
next.config.js を編集する。

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

Check the render method of `Home`.

next.config.js

next.config.js - vercel/next.js - GitHub をほぼコピペ。
Next.js v15 未満はこちら が参考になるかも。

import type { NextConfig } from "next"

const nextConfig: NextConfig = {
	/* config options here */
	reactStrictMode: true,
	experimental: {
		turbo: {
			rules: {
				"*.svg": {
					loaders: ["@svgr/webpack"],
					as: "*.js",
				},
			},
		},
	},
	webpack(config) {
		config.module.rules.push({
			test: /\.svg$/i,
			issuer: /\.[jt]sx?$/,
			use: ["@svgr/webpack"],
		})
		return config
	},
}

これで SVG のアイコンフォントが使えるようになった。
が、ビルドできなくなった。

$ npm run build
Failed to compile.

./src/assets/icon/xxxxx.svg
SvgoParserError: /path/to/your/project/src/assets/icon/xxxxx.svg:1:1: Non-whitespace before first tag.

Import trace for requested module:
./src/assets/icon/xxxxx.svg
./src/app/page.tsx

> Build failed because of webpack errors

[Next.js 13.3.0] Upgrade breaks @svgr/webpack in appDir を参考に、
下記へ修正。

おそらくだけど、
/public の SVG ファイルと /src/assets の SVG ファイルが混在している事が原因?
かもしれない。

import type { NextConfig } from "next"

const nextConfig: NextConfig = {
	/* config options here */
	reactStrictMode: true,
	experimental: {
		turbo: {
			rules: {
				"*.svg": {
					loaders: ["@svgr/webpack"],
					as: "*.js",
				},
			},
		},
	},
	webpack(config) {

		const fileLoaderRule = config.module.rules.find((rule) => rule.test?.test?.('.svg'))

		config.module.rules.push(
			// 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: fileLoaderRule.issuer,
				resourceQuery: { not: [...fileLoaderRule.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
	},
}

ビルドできるようになった。

SVG : Material Design Icons を試す(非推奨)

SVG と CSS の両方ある。
SVG の方は非推奨。というか Next.js で使用すると不具合?が出てて使えない。
表示はされるがリサイズされない。

構成

next.config.ts
/src
  └ /app
     ├ globals.scss
     └ page.tsx

インストール

$ npm install @material-design-icons/svg@latest --save
$ npm install @svgr/webpack --save-dev

next.config.ts

同上。
記述しないと下記エラーが出る。

InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('/_next/static/media/face.123456.svg') is not a valid name.

src/app/globals.scss

同上

src/app/page.tsx

チュートリアルほぼコピペ。
フォントサイズを変更してもアイコンの大きさが変わらない。

import Face from "@material-design-icons/svg/filled/face.svg"

export default function Home() {
	return (
		<>
			<div style={ { fontSize: "14px" } }>
				<Face/>
			</div>
		</>
	)
}

CSS : Material Design Icons を試す

こちらは簡単に導入できた。

$ npm install @material-design-icons/font@latest
import "@material-design-icons/font"

export default function Home() {
	return (
		<>
			<span className="material-icons">face</span>
		</>
	)
}

エラーが出る。

Module not found: Can't resolve '@material-design-icons/font'

調べると index ファイルが3つあった(index.css, index.d.ts, index.scss)ので、
下記に変更。

import '@material-design-icons/font/index.css'
// もしくは
// import '@material-design-icons/font/filled.css'

export default function Home() {
	return (
		<>
			<span className="material-icons">face</span>
		</>
	)
}

これで表示される。