nextjs×reg-suitで画像が読み込まれない問題を解決する

 
0
このエントリーをはてなブックマークに追加
Hakucho
Hakucho (白鳥)

結論

次のissueのコメントを参考にした。

issue link

リンク先で説明してることを少しアレンジして

↓こういうコンポーネントを作ってnext/imageをラップして、プロジェクト全体のnext/imageを使っているところを置き換える

import NextImage, { ImageProps } from 'next/image';
import { forwardRef } from 'react';

export let Image = NextImage;

const UnoptimizedImage = forwardRef<HTMLImageElement, ImageProps>(function Image({ src, ...props }, ref) {
  return (
    <img ref={ref} src={typeof src == "string" ? src : "src" in src ? src.src : src.default.src} {...props} />
  );
}) as typeof NextImage;

export function unoptimizeNextImageForStorybook() {
	Image = UnoptimizedImage;
}

そのあとstorybookのpreview.tsunoptimizeNextImageForStorybookを読み込む

// .storybook/preview.ts
import { unoptimizeNextImageForStorybook } from '@/components/Image';
unoptimizeNextImageForStorybook();

こうするとstorybookの中ではnext/imageじゃなく普通のimgタグを使うようになる。

なぜ治るのかは知らないが、とにかくnext/imageを使わなければ問題が解決したのでヨシ

どんな問題が起きていたか

次の画像のように、reg-suitを実行したとき画像が読み込まれたり読み込まれなかったりが、ランダムに起きていた

Untitled

試したこと

結論としては冒頭のことを実践すれば解決するのだが、メモとして試したことを残しておく

next/imageのdefaultPropsを変える

// .storybook/preview.ts
import Image from "next/image";

Image.defaultProps = { unoptimized: true }

上記コードでstorybook内でのみImageのdefaultPropsが変えられる
これをいくつかのpropsで試した。

next/imageの最適化処理が何かしらの悪さをしていると仮説を立てて、最適化系の処理をいろいろ無効化してみた。

unoptimized=trueにする

// .storybook/preview.ts
import Image from "next/image";

Image.defaultProps = { unoptimized: true }

問題は解決しなかった

loading=eagerにする

// .storybook/preview.ts
import Image from "next/image";

Image.defaultProps = { loading: "eager" }

問題は解決しなかった

loaderを変える

// .storybook/preview.ts
import Image from "next/image";

Image.defaultProps = { loader: ({src})=>src }

問題は解決しなかった

Object.definePropertyでnext/imageをモックする

next/imageを置き換える発想自体はあったので、Object.definePropertyでnext/imageをモックしてみた

import * as NextImage from "next/image";

const OriginalNextImage = NextImage.default;

// Override next/image so that we can render images unoptimized in Storybook.
Object.defineProperty(NextImage, "default", {
  configurable: true,
  // @ts-ignore
  value: (props) => <OriginalNextImage {...props} unoptimized />,
});

TypeError: Cannot redefine property: default というエラーが出て、storybookが落ちた。

waitForで読み込まれるまで待ってみる

reg-suitを走らせる前にstorycapというライブラリを使ってスクショを取っているので、storycapで何か問題が起きているだろうと考えた。

storycapにはwaitForなるオプションがあり、そこでimgの読み込みを待つようなコードを書く

parameters:{
    screenshot: {
      waitFor: function waitForImage() {
			  const images: HTMLImageElement[] = Array.from(
			    document.querySelectorAll("img")
			  );

			  return Promise.all(
			    images.map((im) => new Promise((resolve) => (im.onload = resolve)))
			  );
			}
		}
}

storycapがタイムアウトし、そもそもスクショが取れなかった。

waitAsset=trueにする

前項と同様にstorycapに原因があると考え、storycapのオプションを変えてみた

storycapには waitAssets なるオプションがある

parameters:{
    screenshot: {
      waitAssets: true,
    }
}

デフォルトがtrueなので望みは薄い

問題は解決しなかった

まとめ

なぜ解決したのかはわからないが、とにかく解決したのでヨシ

info-outline

お知らせ

K.DEVは株式会社KDOTにより運営されています。記事の内容や会社でのITに関わる一般的なご相談に専門の社員がお答えしております。ぜひお気軽にご連絡ください。