본문으로 건너뛰기
김도현

이미지 태그는 크기를 모른다

이미지 Layout Shift

내 블로그에서는 글의 이해를 돕기 위해 이미지를 사용하고 있는데, 페이지 진입 시 혹은 새로고침 시에 Layout Shift가 발생하고 있었다.

이미지 영역을 확보 해놓지 못하는 모습

이미지들을 next/image의 Image 태그로 처리해 놓았기 때문에 렌더링 시 이미지 크기만큼 영역을 잡아놔서 이런 문제가 없을 거로 생각했는데 아니었다.

img.tsx
const Img = (props: ImageProps) => {
const caption = props?.title;
const isUnoptimized = /.gif$/.test(props.src as string);
return (
<figure className="space-y-4">
<Image
{...props}
width={props.width || 0}
height={props.height || 0}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 70vw"
unoptimized={isUnoptimized}
/>
{caption && <figcaption className="text-center text-sm text-muted">{caption}</figcaption>}
</figure>
);
};

당연히 있을 거라 예상했던 props.widthprops.height의 값이 존재하지 않았다.

{
"className": "mx-auto rounded",
"src": "/images/~/0.png",
"alt": "",
"title": ""
}

Next.js 공식 문서의 Image Optimization - Local images를 보면 정적으로 import 한 이미지는 Next.js에서 변환해서 StaticImageData 객체로 만드는데, 이 경우에는 Next.js가 알아서 이미지 사이즈를 읽어서 Layout Shift 문제가 발생하지 않게 된다.

하지만 내 블로그는 MDX에서 작성한 이미지를 직접 가져오기 때문에 Next.js 이미지 변환 파이프라인이 동작하지 않고 위와 같은 정보들만 남게 된다. 그래서 현재 구조를 유지하면서 직접 이미지 사이즈를 넣어주기로 했다.

플러그인으로 해결하기

MDX는 MDAST -> HAST 과정을 거쳐서 JSX로 변환되는데, HAST에 위와 같은 객체 정보가 남아있고, 이때 트리를 순회하면서 src로 이미지를 불러와서 사이즈를 계산 후 추가해 줄 수 있다.

이 과정에 쉽게 개입하기 위해 rehype 플러그인1을 구현했고 이미지 사이즈 계산에는 image-size 라이브러리를 이용했다.

rehype-image-size.mjs
const rehypeImageSize = () => {
return async (tree) => {
const promises = [];
visit(tree, 'element', (node) => {
if (node.tagName !== 'img') return;
const { src, width, height } = node.properties;
if (src && !(width && height)) {
const filePath = path.join(dir, src);
const promise = (async () => {
const buffer = await readFile(filePath);
const dimensions = sizeOf(buffer);
if (dimensions.width && dimensions.height) {
node.properties.width = dimensions.width;
node.properties.height = dimensions.height;
}
})();
promises.push(promise);
}
});
await Promise.all(promises);
};
};

이제 이 플러그인을 적용하면 아래와 같이 props에 이미지의 widthheight가 추가된다.

{
"className": "mx-auto rounded",
"src": "/images/~/0.png",
"alt": "",
"title": "",
"width": "634",
"height": "370"
}

그리고 더 이상 이미지로 인해 Layout Shift가 발생하지 않는 모습도 확인할 수 있다.

렌더링 전 이미지 영역을 확보해 놓는 모습

각주

  1. rehype-plugin Topics

관련 글