環境
技術 | バージョン |
---|---|
React.js | ^18 |
Next.js | 14.0.3 |
Storybook | ^7.6.6 |
svgr | ^8.1.0 |
Webpack @ Storybook | 5 |
問題
Storybookを起動すると、ビルドは成功するが、Failed to execute 'createElement' on 'Document': The tag name provided ('static/media/public/images/icons/check.svg') is not a valid name.
のエラーが出てしまう。
前提
アイコンを表示するコンポーネントを作成しています。
// Iconコンポーネント import Check from '/public/images/icons/check.svg'; const ICONS = { Check }; type IconName = keyof typeof ICONS; type Size = 16 | 24 | 32 | 64; type Props = { name: IconName; size: Size; }; export default function Icon({ name, size }: Props) { const Icon = ICONS[name]; return <Icon height={size} width={size}></Icon>; }
アイコン画像は、Next.jsプロジェクトのルートディレクトリのpublic
ディレクトリ内に作成しています。
Next.js側の設定では、Webpackにsvgrの設定を行っています。
また、コンポーネント内で画像をimport
したときの型定義設定を無効にしています。
(そのため、svgファイルをimportした場合の独自の型定義を作成していますが、ここでは割愛させていただきます)
/** @type {import('next').NextConfig} */ const nextConfig = { webpack: (config) => { config.module.rules.push({ test: /\.svg$/, use: [ { loader: '@svgr/webpack', options: {}, }, ], }); return config; }, images: { disableStaticImages: true, }, }; export default nextConfig;
Storybookの設定は以下になります。
// .storybook/main.ts import type { StorybookConfig } from '@storybook/nextjs'; import path from 'path'; const config: StorybookConfig = { stories: ['../src/**/*.stories.(tsx)'], addons: [ '@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-onboarding', '@storybook/addon-interactions', ], framework: { name: '@storybook/nextjs', options: {}, }, staticDirs: ['../public'], docs: { autodocs: 'tag', }, webpackFinal: async (config) => { if (config.resolve) { config.resolve.alias = { ...config.resolve.alias, '~': [path.resolve(__dirname, '../src/')], }; } if (config.module) { config.module.rules = [ ...(config.module.rules || []), { test: /\.svg$/i, issuer: /\.tsx?$/, use: [ { loader: '@svgr/webpack', options: {}, }, ], }, ]; } return config; }, }; export default config;
原因
公式において、上記のコードでsvg用のloader
を設定しています。
// code/builders/builder-webpack5/src/preview/base-webpack.config.ts // (省略...) { test: /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/, type: 'asset/resource', generator: { filename: isProd ? 'static/media/[name].[contenthash:8][ext]' : 'static/media/[path][name][ext]', }, }, // (省略...)
これによると、Webpack5のAsset Modulesである asset/resource
を使っています。
こちらの読み込みを優先してしまい、パスが正しい読み込みをできておらず、エラーが出ているとのことでした。
解決
上記のAsset Modulesを使った読み込みを外せば良さそうです。
ということで、svgファイルを読み込むloader
を持つルールに、svgを除外する設定を行います。
(変更部分だけ記載します)
// .storybook/main.ts // (省略...) const config: StorybookConfig = { // (省略...) webpackFinal: async (config) => { // (省略...) if (config.module) { const newRule = config.module.rules?.map((rule) => { if ( rule && typeof rule === 'object' && rule.test?.toString().includes('svg') ) { return { ...rule, exclude: /\.svg$/ }; } return rule; }); config.module.rules = [ ...(newRule || []), { test: /\.svg$/i, issuer: /\.tsx?$/, use: [ { loader: '@svgr/webpack', options: {}, }, ], }, ]; } // (省略...) }, }; // (省略...)
上記設定を行うことで、無事Storybookでアイコンコンポーネントが表示できました。
参考
こちらの記事を参考にさせていただきました。