<

/>

imagenext

NEXT.JS에서 동적인 이미지 플레이스홀더 구현하기

Gatsby, Discord같은 이미지 구현 과정과 후기

2024년 02월 08일

개요

Discord 이미지 플레이스홀더

Discord 이미지 플레이스홀더

최근에 Discord를 사용하다가 흥미로운 부분을 확인할 수 있었습니다.
이미지 공간에 동적인 플레이스홀더를 넣어 사용자 경험을 향상시켜줬고,
저는 이것을 블로그에도 적용 시키고 싶었습니다.
Gatsby를 활용해 블로그를 만들었다면 gatsby-image 플러그인을 통해 자동으로 적용 가능했지만,
Next.js에서 제가 가진 개발 지식으로 직접 구현해보고 싶었습니다.

그렇게, node.js로 사진을 blurData로 만들어 자동화 하는 과정을 공유하려고합니다.

설계

loading = "lazy"; // {lazy} | {eager}
blurDataUrl = "~~"; // base64로 인코딩된 이미지만 가능

next의 Image 컴포넌트는 loadingblurDataUrl 이라는 props 를 가지고있습니다.
제가 원하는 LazyLoading 기능과 로딩될때 보여주는 Placeholder 기능을 Next.js 에서 간편하게 제공하고있으니,
이제는 blurDataUrl에 필요한 동적인 base64 데이터를 구할 차례입니다.

서버에서 로직을 처리하고 사용자에게 결과 UI만 보여주는 서버 컴포넌트의 장점과,
static export(SSG)의 장점을 살린 설계를 하고싶었습니다.

그렇다면 아래와 같은 역할을 할수있는 자바스크립트가 필요했습니다.

  1. imgs 폴더에 접근한다
  2. 해당 이미지의 base64 데이터를 생성한다.
  3. 생성된 base64 데이터는 json 파일로 저장하여 컴포넌트에서 정적 참조를 한다.

위 역할을 해내기 위해 node.js 환경에서 사용하는, 파일 시스템에 액세스 할수있는 fs 라이브러리와,
이미지를 처리하는 sharp 라이브러리를 이용하여 코드를 구현하겠습니다.

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

사실 Next.js Image 문서에서는 동적인 blurData 프로퍼티를 가져오고싶으면 Plaiceholder 라는 라이브러리를 사용하라고 추천 해줍니다.
Plaiceholder 라이브러리를 사용하려면 sharp 라이브러리를 같이 설치해줘야합니다.
이미 라이브러리 개발자가 여러 고민을 거쳐서 출시한것이겠지만, sharp를 어차피 사용해야한다면,
직접 설계를 해서 그것이 올바른 설계인지 시행착오를 겪고 싶었습니다.

구현

Dependencies

  • next.js@14.1.0
  • sharp@0.33.2
  1. sharp 라이브러리를 최신 버전으로 사용하시려면 next버전을 14.1.0 이상 버전으로 업데이트해야 vercel 배포 환경에서 오류가 나지 않습니다. Next.js 14.1
  2. node.js 라이브러리를 사용하기때문에 서버컴포넌트에서만 작동합니다.
  3. Next.js app router static export 를 기준으로 설계했습니다.

동적인 blurData(base64) 생성

아래 코드는 브라우저에서 작동시 제한적이거나 작동하지 않을수도 있습니다.

// generateBase64Image.mjs
import { readdir, readFile, writeFile, mkdir } from "fs/promises";
import sharp from "sharp";
 
const generateAllBase64 = async (dir) => {
  // 1. 파일 접근
  const imageDir = path.join(process.cwd(), "public", "imgs", dir);
  let result = {};
 
  // 2. 이미지 파일 필터
  let files = await readdir(path.join(imageDir));
  files = files.filter((file) => {
    return (
      file.endsWith(".jpg") ||
      file.endsWith(".jpeg") ||
      file.endsWith(".png") ||
      file.endsWith(".gif") ||
      file.endsWith(".webp")
    );
  });
 
  // 3. 이미지 읽기 및 base64 변환
  for (const filename of files) {
    const imageBuffer = await readFile(
      path.join("public", "imgs", dir, filename)
    );
 
    const base64 = await sharp(imageBuffer)
      .resize(10, 5)
      .toBuffer()
      .then((buffer) => {
        const base64Image = `data:image/jpeg;base64,${buffer.toString(
          "base64"
        )}`;
        return base64Image;
      })
      .catch((err) => console.error(err));
 
    // 4. result 객체 구성
    result[filename] = {
      base64,
      img: { src: `/imgs/${dir}/${filename}` },
    };
  }
 
  // 5. result 객체 반환
  return result;
};

public 에 있는 제 imgs 폴더에 접근해 이미지 파일들을 가져와서 base64로 변환하는 코드입니다.
사이즈는 Next.js 에서 권장하듯이 10px 이하로 하는게 좋습니다.

자동화

// generateBase64Image.mjs
const init = async () => {
  // 1. imgs 특정 폴더에 접근해서 폴더명 추출
  const dirNames = getDirNames();
  let result = {};
 
  // 2. 폴더명에 맞게 base64 데이터 변환
  for (const dir of dirNames) {
    const base64Data = await generateAllBase64(dir);
    result[dir] = base64Data;
  }
 
  // 3. base64 json 파일 저장
  for (const dir in result) {
    await writeJSON(result[dir], dir);
  }
};
 
init();

imgs 폴더를 탐색하고 그 폴더명에 맞게 json 파일을 저장하는 코드입니다.
package.json에서 prebuild 스크립트를 작성하면, build를 하기 전 이 자바스크립트를 실행하기 때문에 운영/배포 환경에서도 문제없이 사용할 수 있습니다.

마치며

적용 화면

적용 화면

여러가지 시행착오를 많이 겪은 작업이었습니다.
처음에는 json이 아닌 txt파일로 저장해서 참조하여 운영/배포 환경에서 오류를 일으키기도 하였고,
Next.js의 버전과 sharp의 버전이 충돌한것을 몰라서
무엇이 문제인지 한참을 헤매기도 했습니다.(--legacy-peer-deps가 무서운이유)

또한, 제 블로그 환경만 고려하여 제한이 많은 저의 구현 과정과 다르게
Plaiceholder 라이브러리는 많은 추상화를 거쳐 사람들이 쓸수있게 배포한것도 대단하다고 느껴졌습니다.

참고로 Plaiceholder의 라이브러리는 static export 환경이 아닐때,
base64 데이터를 매번 생성한다는걸 알게됐습니다.
오픈소스 contributor로 참여해 본적이 없었는데,
이 부분을 해결해서 이 라이브러리에 기여하고 싶은 욕심이 생겼습니다.

참조

2024﹒©

 OU9999

Powered by Next.js﹒Vercel