next/imageでremote imagesをblurさせたい
next/imageで外部からfetchしてきた画像にうまいことblurエフェクトをかけて、マウント時の不格好さを改善したい。デフォルトだとちらつきっぽく見えてしまう。
公式ドキュメントにはこう書いてある。
For dynamic images, you must provide the blurDataURL property. Solutions such as Plaiceholder can help with base64 generation.
例が多少載っているけどどちらもlocal image。
blurDataURLの説明
Must be a base64-encoded image. It will be enlarged and blurred, so a very small image (10px or less) is recommended. Including larger images as placeholders may harm your application performance.
紹介されているライブラリを見に行く
いい感じのドキュメントがついている
"Plaiceholder" is a suite of Node.js functions for creating low quality image placeholders (LQIP).
Base64もイケるって書いてある。変換の際に色んなストラテジがあるらしいけど一旦パス。
sharpに依存しているらしい。Gatsbyのときに見たな。
npm install sharp plaiceholder
getPlaiceholder(src, options);
これだけで使えるらしい、まじか。src
はremote image URLイケるって書いてある。
おおー、しかもNext.jsでの例も載ってる。
import * as React from "react";
import type { InferGetStaticPropsType } from "next";
import Image from "next/image";
import { getPlaiceholder } from "plaiceholder";
export const getStaticProps = async () => {
const { base64, img } = await getPlaiceholder("/path-to-your-image.jpg");
return {
props: {
imageProps: {
...img,
blurDataURL: base64,
},
},
};
};
const Page: React.FC<InferGetStaticPropsType<typeof getStaticProps>> = ({
imageProps,
}) => (
<div>
<Image {...imageProps} placeholder="blur" />
</div>
);
export default Page;
app dirだとclientコンポーネントにするかserverコンポーネントにするか迷うなこれ。
'use client'
つけない場合、clientコンポーネントから呼び出したいときは工夫が必要になりそう。
一旦これでやりたいことはできてる。
import NextImage from "next/image";
import { getPlaiceholder } from "plaiceholder";
import { ComponentProps } from "react";
type ImageProps = ComponentProps<typeof NextImage>;
export const Image = async ({ src, ...props }: ImageProps) => {
const { base64, img } = await getPlaiceholder(src as string);
return (
<NextImage {...img} placeholder="blur" blurDataURL={base64} {...props} />
);
};
{/* @ts-expect-error Async Server Component */}
<Image
src="https://opengraph.githubassets.com/3e2e0d38d0967422099b2487ecab99b5ee9b752afb81043623b2571c9a96e540/hajimism/my-playground"
alt=""
/>```
ただこの場合だとこんな感じのときにダメそう
"use client";
import { Image } from "./image";
export const ClientComponent = () => {
return (
<>
{/* @ts-expect-error Async Server Component */}
<Image
src="https://opengraph.githubassets.com/3e2e0d38d0967422099b2487ecab99b5ee9b752afb81043623b2571c9a96e540/hajimism/my-playground"
alt=""
/>
</>
);
};
こうすればいいかなと思ったけど
"use client";
import NextImage from "next/image";
import { getPlaiceholder } from "plaiceholder";
import { ComponentProps, use } from "react";
type ImageProps = ComponentProps<typeof NextImage>;
export const Image = ({ src, ...props }: ImageProps) => {
const { base64, img } = use(getPlaiceholder(src as string));
return (
<NextImage {...img} placeholder="blur" blurDataURL={base64} {...props} />
);
};
こういうエラーが出た
これが同様のIssueっぽい
ほんでこれが解決策っぽい
Add to webpack config
{ externals: { sharp: 'commonjs sharp' } }
Next.jsのWebpack configをいじる会になりました。
書き方わからん。とりあえずこうしてみたけど動かなかった。
module.exports = {
...nextConfig,
webpack: (config, { isServer }) => {
if (!isServer) {
config.externals.sharp = "commonjs sharp";
}
return config;
},
};
なんか配列っぽいのでこう書いてみたけど変わらん。そもそも対応が間違ってる説isある
module.exports = {
...nextConfig,
webpack: (config, { isServer }) => {
if (!isServer) {
config.externals = [...config.externals, { sharp: "commonjs sharp" }];
}
return config;
},
};
と思って公式ドキュメントみたら公式プラグインがありますな
うーん、一旦はclientでつかえないことをかくにん
よく見たらプラグインこれとは関係ないわ
An essential plugin for Next.js, ensuring that all Plaiceholder functions start in the main thread.
Hi, sharp requires the Node.js runtime and will not work in a browser.
ということなので node-loader
を使う提案がされているけどうーん
あたぼーですけどいわゆるcompositionをとれば動く。これが正しい気がするからこうするかー。
import { ClientComponent } from "./client-component";
import { Image } from "./image/server";
export default function Page() {
return (
<div className="flex flex-col justify-center items-center min-h-screen">
{/* @ts-expect-error Async Server Component */}
<Image
src="https://opengraph.githubassets.com/3e2e0d38d0967422099b2487ecab99b5ee9b752afb81043623b2571c9a96e540/hajimism/my-playground"
alt=""
/>
<ClientComponent>
{/* @ts-expect-error Async Server Component */}
<Image
src="https://opengraph.githubassets.com/3e2e0d38d0967422099b2487ecab99b5ee9b752afb81043623b2571c9a96e540/hajimism/my-playground"
alt=""
/>
</ClientComponent>
</div>
);
}