Next.jsの画像最適化をnext/imageを使用せずに活用する方法
はじめに
v13.4.8 からunstable_getImgProps
というメソッドが next/image
から提供されるようになりました。
これは next/image
のコンポーネントが内部で処理していた img
要素に渡す props
を取得するためのメソッドです。
簡単な例として以下のように使用できます。
import { unstable_getImgProps as getImgProps } from "next/image";
export default function Page() {
const { props } = getImgProps({
src: "/sample.png",
alt: "sample",
width: 800,
height: 400,
});
return <img {...props} />;
}
具体的な実用例としては大きく 3 つに分けられそうです。
-
next/image
を使わずにimg
要素を使いたい -
next/image
を使いたいがpicture
要素を使いたい - 直接
img
要素を使わずに画像を表示したい
またここから先の具体的な実装パターンではあらかじめ getImgProps
をエクスポートするファイルを用意している前提で進めていきます。
export { unstable_getImgProps as getImgProps } from "next/image";
next/image
を使わずに img
要素を使いたい
このケースでは以下のようなメリットがあります。
- サーバーコンポーネントで機能する
サーバーコンポーネントで機能する
next/image
はサーバーコンポーネントでは機能しません。
これは、placeholder
属性による画像読み込み中の処理や、onLoad
や onError
属性を処理するために useState
などの ReactHooks やブラウザ API を使用しているためです。
一方で getImgProps
は next/image
の内部で使用している img
要素に渡す props を取得するだけなので、サーバーコンポーネントでも機能します。
またご存知の方もいるかとは思いますが、v13.0.0 から next/image
は元々 next/future/image
として提供されていたものに差し変わりました。
このタイミングでほぼ単純な img
要素になっています。
遅延読み込みは loading="lazy"
属性で実現していたり、layout
属性で色々な CSS が適用されていたのもこのタイミングで削除されています。(placeholder
属性などの影響でゼロではありません)
そのため使い方によっては実は next/image
を使わずに img
要素を使う今回の方法が適している場合もあるかもしれませんね。
実装例
import { getImgProps } from "@/lib/getImgProps";
import { ImageProps } from "next/image";
function ImgTag(props: ImageProps) {
return <img {...getImgProps(props).props} />;
}
export default ImgTag;
import ImgTag from "@/components/ImgTag";
import imgSrc from "@/images/sample.jpg";
export default function Home() {
return (
<main>
<ImgTag src={imgSrc} alt="" />
</main>
);
}
next/image
を使いたいが picture
要素を使いたい
このケースでは以下のようなメリットがあります。
- アートディレクションができる
- Light/Dark モードに対応できる
アートディレクションができる
ここでいうアートディレクションとは、画面サイズに応じて表示される画像を切り替えることを指します。
例えば、デスクトップでは横長の背景画像を表示している場合、モバイルで同じ画像を表示すると縮小されダサくなることは予想できます。
これをモバイルでは、正方形もしくは縦長の画像を表示するような手法です。
next/image
は直接このようなことはできないため、picture
要素を使う必要があります。
(個人的には getImgProps はアートディレクションできるようになることが一番のメリットだと思っています)
Light/Dark モードに対応できる
Light/Dark モードに対応したコンポーネントもアートディレクションと同じような理由で picture
要素を使う必要があります。
厳密には CSS の display:none
を使用すれば next/image でも実現できますが、1 つの画像を表示するために複数のコンポーネントが必要となりオーバーヘッドも少なからず存在する点やブラウザのネイティブ機能を活用できていない点から、picture
要素を使うほうが望ましいと考えています。
実装例
アートディレクションと Light/Dark モードに対応したコンポーネントはどちらもやることはほぼ同じなため、アートディレクションに対応したコンポーネントを例に挙げます。
import { ImageProps } from "next/image";
import { getImgProps } from "@/lib/getImgProps";
type Src = ImageProps["src"];
type ArtDirectionProps = {
src: {
desktop: Src;
mobile: Src;
};
} & Omit<ImageProps, "src">;
function ArtDirection({ src, ...props }: ArtDirectionProps) {
const { props: imgProps } = getImgProps({ ...props, src: src.mobile });
const {
props: { srcSet: desktopSrcSet },
} = getImgProps({ ...props, src: src.desktop });
return (
<picture>
<source media="(min-width: 768px)" srcSet={desktopSrcSet} />
<img {...imgProps} />
</picture>
);
}
export default ArtDirection;
import ArtDirection from "@/components/ArtDirection";
import imgMobileSrc from "@/images/sample-mobile.jpg";
import imgSrc from "@/images/sample.jpg";
export default function Home() {
return (
<main>
<ArtDirection
src={{ desktop: imgSrc, mobile: imgMobileSrc }}
alt=""
sizes="100vw"
/>
</main>
);
}
img
要素を使わずに画像を表示したい
直接 ここでは img
要素を使わずに画像を表示したい場合について考えます。
具体的には CSS の background-image
や image-set()
や、canvas
要素、new Image()
で画像を表示したい場合です。
実装例
ここでは CSS の background-image
と image-set()
を例に挙げます。
他の方法についても同様に getImgProps
を使って srcSet
または src
を取得し、適切な形式にフォーマットすれば実現できます。
import { getImgProps } from "@/lib/getImgProps";
import { ImageProps } from "next/image";
type BackgroundImageProps = Omit<ImageProps, "sizes" | "placeholder">;
function BackgroundImage(props: BackgroundImageProps) {
const backgroundImageUrl = getImgProps(props)
// 解像度ごとの画像候補文字列に分割
.props.srcSet?.split(", ")
// 画像URLと解像度に分割
.map((src) => src.split(" "))
// CSSの`image-set`形式にフォーマット
.map(([src, width]) => `url(${src}) ${width}`)
.join(",");
return (
<div
className="w-full aspect-[3/2] bg-cover bg-center"
style={{
backgroundImage: `-webkit-image-set(${backgroundImageUrl})`,
}}
/>
);
}
export default BackgroundImage;
import BackgroundImage from "@/components/BackgroundImage";
import imgSrc from "@/images/sample.jpg";
export default function Home() {
return (
<main>
<BackgroundImage src={imgSrc} alt="" />
</main>
);
}
実装イメージ
以下のURLにて 上記のパターンを実装しているので検証ツールなどでHTMLがどうなるか見ていただくとより理解しやすいと思います。
まとめ
今回は next/image
の unstable_getImgProps
について紹介しました。
現在は unstable なメソッドなため本番利用については注意が必要ですが、Next.js の画像最適化を next/image
以外で活用できるようになりますので、安定版としてリリースされるのが待ち遠しいですね。
参考
Discussion