Next.js 13을 사용하여 프로젝트를 진행하던 중, 이미지 로딩 시 layout shift 현상을 경험했습니다. 이 문제는 이미지 컴포넌트를 잘못 설정하여 발생했으며, 이를 해결하기 위한 방법을 정리해보았습니다.
layout shift의 원인
Next.js에서 제공하는 <Image> 컴포넌트를 사용할 때는 반드시 너비와 높이를 지정해야 합니다. 하지만, 동적으로 이미지를 불러오는 경우에는 이 크기를 미리 알 수 없기 때문에, 다음과 같이 코드를 작성하게 됩니다.
jsx
<Image
src={src}
alt={src}
width={0}
height={0}
sizes="65vw"
style={{ height: "auto" }}
/>
이 코드에서는 width와 height 모두 0으로 설정되어 있어, 실제 이미지가 로드될 때 밀림 현상이 발생하는 것이었습니다. 따라서 이러한 설정으로 인해 사용자가 이미지를 로딩하는 동안 빈 화면이 보이는 현상이 발생하게 되었습니다.
이미지 로딩 시 Placeholder 사용하기
Next.js의 <Image> 컴포넌트는 이미지 로딩 전에 보여줄 placeholder 속성을 제공하고 있습니다. 기본값은 “empty”이며, “blur” 옵션을 사용할 경우에는 blurDataURL이 필요합니다. 이 속성은 블러 이미지의 주소로, base64로 인코딩된 최대 10픽셀짜리 이미지여야 합니다.
정적으로 이미지를 불러올 경우 blurDataURL이 자동으로 생성되므로, placeholder="blur"만 설정하면 됩니다. 하지만 동적으로 이미지를 불러오는 경우에는 blurDataURL을 수동으로 작성해야 합니다. 이때 plaiceholder 라이브러리를 사용하는 것이 좋습니다.
Plaiceholder 라이브러리 설치
plaiceholder는 이미지의 placeholder를 생성하는 라이브러리입니다. 다음과 같이 설치할 수 있습니다.
bash
npm install sharp
npm install plaiceholder
npm install @plaiceholder/next
Next.js 설정 파일(next.config.mjs)에서 withPlaiceholder를 사용하여 설정을 추가해야 합니다.
“`javascript
// next.config.mjs
import withPlaiceholder from “@plaiceholder/next”;
import withImages from “next-images”;
const nextConfig = withImages({
experimental: {
appDir: true,
},
images: {
formats: [“image/avif”, “image/webp”],
},
swcMinify: true,
});
export default withPlaiceholder(nextConfig);
“`
이미지 Placeholder 코드 작성하기
다음은 getBase64 함수를 통해 이미지를 읽어와 placeholder를 설정하는 방법입니다.
“`javascript
// utils/getBase64.ts
import fs from “node:fs/promises”;
import path from “node:path”;
import { getPlaiceholder } from “plaiceholder”;
const getBase64 = async (src: string) => {
const buffer = await fs.readFile(path.join(“./public”, src));
const {
metadata: { height, width },
…plaiceholder
} = await getPlaiceholder(buffer, { size: 10 });
return {
…plaiceholder,
img: { src, height, width },
};
};
export default getBase64;
“`
이 함수를 사용하여 이미지의 buffer를 생성하고, placeholder와 이미지의 크기를 반환받을 수 있습니다.
ImgWithPlaceholder 컴포넌트 구현
아래는 placeholder를 적용한 이미지 컴포넌트입니다.
“`javascript
// components/ImgWithPlaceholder.tsx
import getBase64 from “@/utils/getBase64”;
import Image from “next/image”;
async function ImgWithPlaceholder({ src }: { src: string }) {
const { base64, img } = await getBase64(src);
return (
);
}
export default ImgWithPlaceholder;
“`
이제 이 컴포넌트를 다음과 같이 사용할 수 있습니다.
jsx
<ImgWithPlaceholder src={`/media/${data.message}`} />
결과 및 주의사항
테스트 후, 더 이상 layout shift가 발생하지 않고, 이미지가 로딩되기 전 블러 이미지가 잘 보이는 것을 확인했습니다. Lighthouse 결과에서도 Cumulative Layout Shift가 사라진 것을 확인할 수 있었습니다.
하지만 plaiceholder는 서버 측 라이브러리이므로, 클라이언트 측에서 직접 사용할 수 없습니다. 서버 컴포넌트에서만 사용할 수 있다는 점에 유의해야 합니다.
또한, 프로젝트의 상위 컴포넌트들이 클라이언트 컴포넌트일 경우, UnhandledSchemeError와 같은 오류가 발생할 수 있습니다. 이 경우, 클라이언트 컴포넌트의 필요성을 다시 검토하고, 서버 컴포넌트로 변경하는 것이 좋습니다.
자주 묻는 질문
질문1: Next.js에서 layout shift를 방지하는 방법은 무엇인가요?
이미지 컴포넌트에서 width와 height를 명시적으로 설정하거나, placeholder를 사용하여 미리 로딩할 수 있습니다.
질문2: plaiceholder 라이브러리는 어디에서 사용할 수 있나요?
plaiceholder 라이브러리는 서버 컴포넌트에서만 사용할 수 있으며, 클라이언트 컴포넌트에서는 사용할 수 없습니다.
이전 글: 2025 연말정산 기간 및 홈택스 활용법

