Closed22

next/imageでremote imagesをblurさせたい

hajimismhajimism

next/imageで外部からfetchしてきた画像にうまいことblurエフェクトをかけて、マウント時の不格好さを改善したい。デフォルトだとちらつきっぽく見えてしまう。

hajimismhajimism

公式ドキュメントにはこう書いてある。

For dynamic images, you must provide the blurDataURL property. Solutions such as Plaiceholder can help with base64 generation.

https://nextjs.org/docs/pages/api-reference/components/image#placeholder

例が多少載っているけどどちらもlocal image。

hajimismhajimism

紹介されているライブラリを見に行く
https://github.com/joe-bell/plaiceholder

hajimismhajimism

"Plaiceholder" is a suite of Node.js functions for creating low quality image placeholders (LQIP).

Base64もイケるって書いてある。変換の際に色んなストラテジがあるらしいけど一旦パス。

hajimismhajimism

sharpに依存しているらしい。Gatsbyのときに見たな。

npm install sharp plaiceholder
hajimismhajimism
getPlaiceholder(src, options);

これだけで使えるらしい、まじか。srcはremote image URLイケるって書いてある。

hajimismhajimism

おおー、しかも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;
hajimismhajimism

app dirだとclientコンポーネントにするかserverコンポーネントにするか迷うなこれ。
'use client'つけない場合、clientコンポーネントから呼び出したいときは工夫が必要になりそう。

hajimismhajimism

一旦これでやりたいことはできてる。

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=""
      />```
hajimismhajimism

ただこの場合だとこんな感じのときにダメそう

"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=""
      />
    </>
  );
};
hajimismhajimism

こうすればいいかなと思ったけど

"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} />
  );
};

こういうエラーが出た

hajimismhajimism

Next.jsのWebpack configをいじる会になりました。
https://nextjs.org/docs/pages/api-reference/next-config-js/webpack

hajimismhajimism

書き方わからん。とりあえずこうしてみたけど動かなかった。

module.exports = {
  ...nextConfig,
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.externals.sharp = "commonjs sharp";
    }

    return config;
  },
};
hajimismhajimism

なんか配列っぽいのでこう書いてみたけど変わらん。そもそも対応が間違ってる説isある

module.exports = {
  ...nextConfig,
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.externals = [...config.externals, { sharp: "commonjs sharp" }];
    }

    return config;
  },
};
hajimismhajimism

よく見たらプラグインこれとは関係ないわ

An essential plugin for Next.js, ensuring that all Plaiceholder functions start in the main thread.

hajimismhajimism

Hi, sharp requires the Node.js runtime and will not work in a browser.

ということなので node-loaderを使う提案がされているけどうーん

hajimismhajimism

あたぼーですけどいわゆる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>
  );
}
このスクラップは2023/05/14にクローズされました