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>
</>
)
}
これで表示される。