Next.js でリモート画像用の blurDataURL を生成する


Originally posted at: nikolovlazar.com/blog/generating-blur-for-dynamic-images-nextjs



Next.js Image Component は IMO であり、画像を確実にするために使用できる最高のツールです.
Next.js Web サイトの最適化が行われ、ページの読み込みが速くなります. next/image コンポーネントの興味深い機能の 1 つ
provides は placeholder prop で、その値は blur または empty のいずれかです.

プレースホルダーが blur に設定されている場合、 blurDataURL を提供する必要があります.ローカル画像を静的にインポートする場合、Next.js
リソースにアクセスして、blurDataURL を生成できます.しかし、そこにあるリモート画像にぼかし効果を追加したい場合
私たちがしなければならないことがいくつかあります:
  • Register the provider's domain in next.config.js
  • Generate the blurDataURL and pass it to the NextImage component

  • 私は自分のウェブサイトのコンテンツ (これ!) に MDX を使用しているので、この記事では blurDataURL について説明します.
    世代は MDX と統合されていますが、機能は一般的であり、MDX とはまったく関係がありません.それでは始めましょう!

    プロバイダー ドメインの登録



    まず、リモート イメージを next/image でレンダリングするには、プロバイダーのドメインを登録する必要があります.私の場合、
    GitHub から og:image を読み込んでいます.URL は次のようになります.

    https://opengraph.githubassets.com/f4a95bd3aa5113a1f599f5a810edeb16b885f3364b0443dc3c34a02c3290a5d8/chakra-ui/chakra-ui-docs/pull/154
    


    URL を見ると、opengraph.githubassets.com ドメインを登録する必要があることがわかります.next.config.js にジャンプしましょう.
    そしてそれをします:

    // next.config.js
    
    module.exports = {
      images: {
        domains: ['opengraph.githubassets.com'],
      },
    };
    


    以上です!邪魔にならないようになったので、blurDataURL prop の生成を開始しましょう.

    blurDataURL を生成する



    私は MDX を使用しており、ページを静的にレンダリングしているので、すべてをフィルター処理する単純なプラグインを追加しました.
    マークダウンからの画像は、それらの widthheight 、および blurDataURL を計算し、小道具として渡します.

    // src/utils/plugins/image-metadata.ts
    
    import imageSize from 'image-size';
    import { ISizeCalculationResult } from 'image-size/dist/types/interface';
    import path from 'path';
    import { getPlaiceholder } from 'plaiceholder';
    import { Node } from 'unist';
    import { visit } from 'unist-util-visit';
    import { promisify } from 'util';
    
    // Convert the imageSize method from callback-based to a Promise-based
    // promisify is a built-in nodejs utility function btw
    const sizeOf = promisify(imageSize);
    
    // The ImageNode type, because we're using TypeScript
    type ImageNode = {
      type: 'element';
      tagName: 'img';
      properties: {
        src: string;
        height?: number;
        width?: number;
        blurDataURL?: string;
        placeholder?: 'blur' | 'empty';
      };
    };
    
    // Just to check if the node is an image node
    function isImageNode(node: Node): node is ImageNode {
      const img = node as ImageNode;
      return (
        img.type === 'element' &&
        img.tagName === 'img' &&
        img.properties &&
        typeof img.properties.src === 'string'
      );
    }
    
    async function addProps(node: ImageNode): Promise<void> {
      let res: ISizeCalculationResult;
      let blur64: string;
    
      // Check if the image is external (remote)
      const isExternal = node.properties.src.startsWith('http');
    
      // If it's local, we can use the sizeOf method directly, and pass the path of the image
      if (!isExternal) {
        // Calculate image resolution (width, height)
        res = await sizeOf(path.join(process.cwd(), 'public', node.properties.src));
        // Calculate base64 for the blur
        blur64 = (await getPlaiceholder(node.properties.src)).base64;
      } else {
        // If the image is external (remote), we'd want to fetch it first
        const imageRes = await fetch(node.properties.src);
        // Convert the HTTP result into a buffer
        const arrayBuffer = await imageRes.arrayBuffer();
        const buffer = Buffer.from(arrayBuffer);
    
        // Calculate the resolution using a buffer instead of a file path
        res = await imageSize(buffer);
        // Calculate the base64 for the blur using the same buffer
        blur64 = (await getPlaiceholder(buffer)).base64;
      }
    
      // If an error happened calculating the resolution, throw an error
      if (!res) throw Error(`Invalid image with src "${node.properties.src}"`);
    
      // add the props in the properties object of the node
      // the properties object later gets transformed as props
      node.properties.width = res.width;
      node.properties.height = res.height;
    
      node.properties.blurDataURL = blur64;
      node.properties.placeholder = 'blur';
    }
    
    const imageMetadata = () => {
      return async function transformer(tree: Node): Promise<Node> {
        // Create an array to hold all of the images from the markdown file
        const images: ImageNode[] = [];
    
        visit(tree, 'element', (node) => {
          // Visit every node in the tree, check if it's an image and push it in the images array
          if (isImageNode(node)) {
            images.push(node);
          }
        });
    
        for (const image of images) {
          // Loop through all of the images and add their props
          await addProps(image);
        }
    
        return tree;
      };
    };
    
    export default imageMetadata;
    

    widthheight 、および blurDataURL 小道具を計算するために必要なことはこれだけです.これを使うには
    プラグイン、ブログ投稿自体をレンダリングする pages/blog/[slug].tsx ページにジャンプしましょう.

    export const getStaticProps: GetStaticProps<Props> = async (ctx) => {
      // get the post slug from the params
      const slug = ctx.params.slug as string;
    
      // get the post content. readBlogPost just reads the file contents using fs.readFile(postPath, 'utf8')
      const postContent = await readBlogPost(slug);
    
      // Use the gray-matter package to isolate the markdown matter (title, description, date) from the content
      const {
        content,
        data: { title, description, date },
      } = matter(postContent);
    
      return {
        props: {
          // use the serialize method from the 'next-mdx-remote/serialize' package to compile the MDX
          source: await serialize(content, {
            mdxOptions: {
              // pass the imageMetadata utility function we just created
              rehypePlugins: [imageMetadata],
            },
          }),
          title,
          description,
          date,
          slug,
        },
      };
    };
    


    以上です!これを実際に確認するには、MDX イメージ コンポーネントに console.log を配置し、小道具を確認します.
    これが私の MDX イメージ コンポーネントです.

    const Image = (props) => {
      return (
        <NextImage {...props} layout='responsive' loading='lazy' quality={100} />
      );
    };
    

    props オブジェクトは、実際には node.properties ファイル内の image-metadata.ts オブジェクトです.

    この記事を読んでいる場合は、既にぼかし効果が発生していることがわかるはずです.

    このソリューションは、MDX 以外のさまざまなシナリオにも適用できます.取得することに注意してください
    画像データ ( !isExternalimage-metadata.ts if ステートメント) はサーバー側の機能です.
    Node.JS の fs パッケージを使用しているためです.何らかの理由でクライアント側でこれを行う必要がある場合は、
    画像データの取得方法を変更する必要があります.

    システム全体を確認したい場合は、私のウェブサイトのソースを確認してください: nikolovlazar/nikolovlazar.com

    Note: if you're applying the blur effect on user submitted images, make sure you know where those images
    will be stored, and don't forget to register the domain in the next.config.js file.