Next.js 블로그 제작기 (9)

user profile img

신현호

TypeScript
Next.js

블로그 제작기

Post Thumbnail

목차

    서론

    오늘도 블로그를 여느때와 다름없이 살펴보는데, 이미지가 처음 로딩될 때 아예 보이지 않는 문제를 찾아냈습니다.
    이번 포스팅은 제가 블로그에 적용한 이미지 로딩 시 해당 이미지를 blur처리 하는 방법에 대해서 포스팅 해보도록 하겠습니다 :)

    변경점

    Plaiceholder와 Sharp

    Plaiceholder 는 이름에서도 대강 유추할 수 있듯이, 말 그대로 placeholder를 만들어주는 라이브러리입니다.
    Sharp 는 이미지 리사이징 라이브러리입니다. 다만 이 라이브러리 사용시 OS간의 호환성 이슈가 있는데 이건 뒤에서 마저 설명하도록 하겠습니다

    Sharp의 경우 Next 프로덕션 페이지 만들때나, vercel 을 사용하고계시다면 지속적으로 Sharp를 설치하는걸 권장한다고 알려주는데요,
    이는 Next에서 제공하는 Next/Image에서 sharp, squoosh 라이브러리를 사용하는데 sharp가 의존성에 없다면 squoosh를 사용하기 때문입니다.
    물론 vercel을 통해 배포한다면 자기들이 알아서 해줍니다.

    필요한 라이브러리는 다음과 같습니다.

    bash

    yarn add sharp plaiceholder @plaiceholder/next
    npm install sharp plaiceholder @plaiceholder/next
    

    next config 세팅

    패키지를 다 설치하셨다면 우선 next.config를 설정해야합니다.

    js

    /** @type {import('next').NextConfig} **/
    import { withContentlayer } from 'next-contentlayer'
    import withPlaiceholder from '@plaiceholder/next'
    
    const nextConfig = {
      webpack(config) {
        config.module.rules.push({
          test: /\.svg$/i,
          use: ['@svgr/webpack'],
        })
    
        return config
      },
      images: {
        remotePatterns: [
          {
            protocol: 'https',
            hostname: 'avatars.githubusercontent.com',
          },
        ],
      },
    }
    
    export default withPlaiceholder(withContentlayer(nextConfig))
    

    nextConfig를 export해줄 때 withPlaiceholder를 감싸주면됩니다.
    단, commonJS의 require로는 작동하지 않았습니다 (esm으로만 가능했습니다), 저도 이때문에 확장자를 mjs로 바꾸어 esm으로 next.config를 사용중입니다.
    해당 내용에 대해서 자세히 알고 계시는분은 댓글 남겨주시면 감사하겠습니다 :)

    설정을 마쳤다면 다음 스텝으로 넘어갈 수 있습니다.

    Next/Image에 placeholder로 blur된 이미지 적용하기

    tsx

    <Image
      src={src}
      alt={src}
      width={img.width}
      height={img.height}
      placeholder="blur"
    />
    

    공식 문서에서 정적인 이미지는 단순하게 placeholder='blur' 이것만 해줘도 된다고 하는데, 저는 뭐가 문제인지 그건 안됐습니다.
    Next/Image에서는 placeholder 옵션을 위한 blurDataURL 속성도 받는데 우리는 이 옵션을 사용할 것 입니다.

    이 blurDataURL는 그냥 이미지 src를 넣어주면 안되고, base64로 처리된 이미지를 넣어줘야합니다.
    이때 여기에서 이미지를 base64로 처리해주기 위해서 plaiceholder 라이브러리를 사용합니다. (plaiceholder가 sharp 기반이라 같이 설치해야합니다.)

    tsx

    import fs from 'node:fs/promises'
    import path from 'node:path'
    import { getPlaiceholder } from 'plaiceholder'
    
    const getBlurredData = async (imgSrc: string) => {
      const buffer = await fs.readFile(path.join('./public', imgSrc))
      const {
        metadata: { height, width },
        ...plaiceholder
      } = await getPlaiceholder(buffer, { size: 10 })
    
      return {
        ...plaiceholder,
        img: { imgSrc, height, width },
      }
    }
    
    export default getBlurredData
    

    fs모듈과 path를 사용해서 이미지의 path를 buffer 변수에 넣어주고, Next/Image에 기본적으로 들어가는 width와 height를 받아와줬습니다.
    그리고 plaiceholder 라이브러리의 getPlaiceholder를 사용하여 해당 buffer와 placeholder의 사이즈를 넣어줍니다. 기본값은 4지만 저는 커스텀 값을 넣어줬습니다.

    tsx

    const getBlurredData = async (imgSrc: string) => {
      const buffer = await fs.readFile(path.join('./public', imgSrc))
      const { base64 } = await getPlaiceholder(buffer, { size: 10 })
    
      return base64
    }
    

    width와 height같은 속성이 필요하지 않으시다면 위의 방법으로도 가능하겠네요

    그리고 로딩되기 전 blur된 이미지를 보여주는 img 태그를 컴포넌트화시켰습니다.

    tsx

    import getBlurredData from '@/utils/getBlurredData'
    import Image from 'next/image'
    
    export default async function ImgWithPlacehlder({
      src,
      tailwindClassNames,
    }: {
      src: string
      tailwindClassNames: string
    }) {
      const { base64, img } = await getBlurredData(src)
    
      return (
        <Image
          className={tailwindClassNames}
          src={src}
          alt={src}
          width={img.width}
          height={img.height}
          placeholder="blur"
          blurDataURL={base64}
        />
      )
    }
    

    tailwindClassNames는 제가 tailwind를 사용해서 그런거고, tailwind 안쓰시는분들은 필요 없습니다.
    이렇게 해주면 로딩시 해당 이미지 기반의 blur된 이미지를 보여주는 img태그를 만들어낼 수 있습니다.

    적용된 모습은 다음과 같습니다!

    blur

    트러블 슈팅

    Sharp의 OS 문제

    Sharp 라이브러리를 설치할 때 주의해야 하는 부분은 Sharp가 OS의 바이너리를 이용하기 때문에 OS에 따라 설치되는 모듈이 다릅니다. 단순 설치로는 해당 OS의 모듈만 받아집니다.
    그렇기에 AWS 혹은 Vercel에 올려다 쓸 때 해당 OS의 모듈과 맞지 않는다면 문제가 생깁니다. 그러니 라이브러리를 다운로드할 때 사용하는 OS를 고려하여 설치하셔야 합니다.

    bash

    npm install --platform=linux sharp
    

    Sharp와 yarn classic

    sharp는 yarn classic에서 일반적으로는 설치가 안됩니다. 이 때는 yarn의 버전을 무시하고 설치하는 --ignore-engines 플래그를 같이 써주시면 됩니다.

    bash

    yarn add sharp --ignore-engines
    

    Could not load the "sharp" module using the linux-x64 runtime / Vercel

    Vercel에서 빌드 시 해당 오류가 발생하며 빌드에 실패하는데, issue를 확인한 결과 sharp 버전 다운그레이드를 통해 해결할 수 있었습니다.

    bash

    yarn upgrade sharp@0.32.6
    

    이슈 하단을 확인해보면 node의 버전을 18.19.0으로 업데이트해서 해결되었다는 경우도 보이는데, 아닌 경우도 있다고 하니 오류가 발생했을 경우 두 방법 모두 시도해보시길 바랍니다.

    참고

    Profile Image

    신현호

    Frontend Developer

    프론트엔드 개발자를 꿈꾸고 있는 대학생입니다. 끊임없이 배우고 성장하는 개발자가 되기 위해 노력하고 있습니다.

    블로그 제작기

    총 11개의 포스트가 존재합니다.