일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Til
- useState
- 백준
- 자료구조
- 프로그래머스
- Next.js
- html
- 스택
- 30daysdowoonchallenge
- 큐
- 해시테이블
- 코드스테이츠
- 회고
- redux
- 카카오
- javascript
- superstarjypnation
- CSS
- React
- UI
- 자바스크립트
- 운영체제
- 프로토타입
- level1
- mysemester
- REST_API
- 생활코딩
- web
- vercel
- UX
- Today
- Total
데굴데굴
[Next.js] Next.js 13 <Image> plaiceholder 라이브러리 적용기 본문
Next.js 13으로 프로젝트를 하던 중 이미지가 로딩될 때 layout shift가 생기는 걸 발견했다.
layout shift의 원인
Next.js에서 제공하는 <Image>
컴포넌트를 사용하려면 무조건 너비와 높이를 지정해서 넘겨줘야 한다.
현재 내 프로젝트에서는 이미지를 동적으로 불러오고 있어 그 크기를 미리 알 수 없었기 때문에 아래처럼 써놓은 상태였다.
<Image
src={src}
alt={src}
width={0}
height={0}
sizes="65vw"
style={{ height: "auto" }}
/>
코드 참고
<Image>
컴포넌트는 width
, height
값으로 이미지 크기를 예상하여 렌더링한다. 위 코드에서는 두 값이 전부 0이기 때문에 실제 이미지가 로딩될 때 밀림 현상이 생기는 건 당연한 것이었다.
이런 밀림 현상도 그렇고 이미지가 로딩될 때 빈 화면이 보이고 있는데 사진이 있다는 표시로 블러 이미지가 미리 표시되면 좋을 것 같아 방법을 찾아보게 되었다.
placeholder
Next.js의 <Image>
컴포넌트는 이미지가 로딩되기 전에 보여줄 placeholder
속성을 기본으로 제공한다.
기본값은 "empty"
이다.
"blur"
옵션을 쓸 때에는 blurDataURL
이 필요하다. blurDataURL
은 블러 이미지의 주소로 생각하면 된다. base64를 기반으로 인코딩된 최대 10픽셀짜리 이미지여야 한다.
이미지를 정적으로 불러오는 경우 blurDataURL
이 자동으로 생성되기 때문에 placeholder="blur"
만 해줘도 알아서 잘 동작한다.
아래는 공식 문서에 링크된 예제 코드이다. github 바로가기
import Image from 'next/image'
import ViewSource from '../components/view-source'
import mountains from '../public/mountains.jpg'
const PlaceholderBlur = () => (
<div>
<ViewSource pathname="pages/placeholder.tsx" />
<h1>Image Component With Placeholder Blur</h1>
<Image
alt="Mountains"
src={mountains}
placeholder="blur"
width={700}
height={475}
style={{
maxWidth: '100%',
height: 'auto',
}}
/>
</div>
)
export default PlaceholderBlur
이미지를 외부 도메인이나 public 폴더에서 동적으로 불러오는 경우에는 blurDataURL
을 필수적으로 작성해줘야 하는데 이 때 공식 문서에서 제안하는 것이 plaiceholder
라이브러리이다.
For dynamic images, you must provide the blurDataURL property. Solutions such as Plaiceholder can help with base64 generation.
plaiceholder
라이브러리
이미지 placeholder를 만들어주는 라이브러리이다. 부가적으로 이미지의 메타데이터 정보도 받아 활용할 수 있다.
설치
sharp 라이브러리를 기반으로 동작하기 때문에 설치되어 있지 않다면 같이 설치해야 한다.
npm install sharp
npm install plaiceholder
Next.js 설정
Next.js에서 plaiceholder
를 쓰려면 몇 가지 단계를 더 거쳐야 한다. 공식 문서
우선, 추가적으로 @plaiceholder/next
패키지를 설치한다.
npm install @plaiceholder/next
이제 config 파일을 변경해야 하는데 이 때 파일의 확장자는 무조건 .mjs
혹은 .ts
여야 한다.
기존 config 파일에 require
문이 있다면 import
로 바꿔주고 export하는 nextConfig
변수를 withPlaiceholder
로 감싸준다.
나는 아래처럼 작성했다.
// next.config.mjs
import withPlaiceholder from "@plaiceholder/next";
import withImages from "next-images";
/** @type {import('next').NextConfig} */
const nextConfig = withImages({
experimental: {
appDir: true,
},
images: {
formats: ["image/avif", "image/webp"],
},
swcMinify: true,
});
export default withPlaiceholder(nextConfig);
적용
기본 사용법
getPlaiceholder(input, options)
input
: 가공되지 않은 Buffer
이미지 주소 [필수]
프로젝트에 적용한 코드
공식 문서에 예제 코드가 잘 나와있다.
나는 아래 링크를 가장 많이 참고했다.
// 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;
node.js의 fs 모듈을 이용해 public
폴더의 이미지를 읽어와 Buffer
를 생성한다.
10픽셀의 base64URL을 만들 것이기 때문에 options
에는 {size: 10}
을 전달했다.
이 함수에서는 base64URL에 더불어 이미지의 높이, 너비 정보도 함께 리턴하고 있기 때문에 이 정보를 <Image>
컴포넌트에 적용하면 layout shift 문제도 함께 해결할 수 있다.
// 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 (
<Image
src={src}
alt={src}
width={img.width}
height={img.height}
sizes="65vw"
style={{ height: "auto" }}
placeholder="blur"
blurDataURL={base64}
/>
);
}
export default ImgWithPlaceholder;
이렇게 만든 컴포넌트는 아래처럼 사용할 수 있다.
<ImgWithPlaceholder src={`/media/${data.message}`} />
결과 ✨
더 이상 밀림 현상도 발생하지 않고 이미지가 완전히 로딩되기 전 블러 이미지가 잘 보이는 것을 확인할 수 있다.
Lighthouse 결과에서 보였던 Cumulative Layout Shift도 사라졌다.
주의할 점
plaiceholder
는 브라우저에서는 동작하지 않는다.
Plaiceholder is a server-side library. It will not work in the browser.
따라서 Next.js 13에서 plaiceholder
라이브러리를 쓰려면 서버 컴포넌트에서 써야 한다.
내가 겪은 시행착오
내 프로젝트에서 plaiceholder
라이브러리를 적용하려 했던 컴포넌트의 상위 컴포넌트가 전부 클라이언트 컴포넌트여서 코드 작성 후에 아래 오류가 뜨면서 앱 실행이 안 됐다.
UnhandledSchemeError: Reading from "node:fs/promises" is not handled by plugins (Unhandled scheme).
이렇게 라이브러리를 적용 못하는건가 싶었는데 아래 기준에 따라 코드를 다시 한 번 살펴보니 상위 컴포넌트는 전부 클라이언트 컴포넌트로 굳이 쓸 필요가 없는 것들이었다.
클라이언트 컴포넌트로 써야 했던 이유는 프로젝트에서 CSS-in-JS 라이브러리로 사용되던 styled-components
때문이었다.styled-components
내부에서 React.createContext()
가 쓰이기 때문에 클라이언트 컴포넌트로 작성하지 않으면 오류가 발생한다. 따라서 스타일링을 위해서 서버 컴포넌트로 쓸 수 있는 것도 클라이언트 컴포넌트로 써야 했다.
관련 글
이렇게 되면 서버 쪽에서만 작동하는 라이브러리 적용도 불가능할뿐더러 Next.js의 이점인 서버 사이드 렌더링을 전혀 활용할 수가 없게 된다.
정말 해결할 수 있는 방법이 없나 찾아봤지만 아래 github 링크에 있는 논의를 보고 CSS 스타일링 방식을 바꿔야겠다는 결정을 내리게 됐다.
다양한 CSS-in-JS 라이브러리가 있었지만 Next.js에서 썼을 때 또 어떤 문제가 발생할지 모르겠어서 우선은 module.css
로 옮겼다. 이유는 가장 가볍고 확실하기 때문이다. (작업하다보니 styled-components
의 중첩에 너무 익숙해졌던 터라 순수 css가 조금 불편하게 느껴져서 SCSS를 같이 써보는 것도 생각해보고 있다.)
이 문제는 다행히 프로젝트 초기 단계에서 발생했어서 옮기는 작업이 비교적 수월했지만 훨씬 더 큰 프로젝트에서 혹은 프로젝트가 막바지에 다다른 상황에서 이런 문제를 만나면 어떻게 해야 할 지 생각해보게 됐다. 이런 문제 상황에서 현명한 의사결정을 내리는 것도 개발자의 역할이라는 걸 배웠다.
참고
'Programming' 카테고리의 다른 글
[Next.js] Next.js 13 배포 시 serverless function 용량 초과 문제 pre-build scripts로 해결하기 (0) | 2023.10.06 |
---|---|
[Next.js] Next.js 13 vercel 배포 시 이미지 로딩 실패 문제 (ENOENT: no such file or directory) (0) | 2023.10.06 |
<Deploy> 배포 과정 자동화하기 (with Github actions) (0) | 2022.12.07 |
<WEB> Lighthouse로 웹사이트 성능 분석하기 (0) | 2022.12.05 |
번들링과 웹팩 (0) | 2022.11.23 |