[Next.js] 성능 최적화

yesme·2024년 1월 10일
1
post-thumbnail

이번에 완성한 토이프로젝트 'Rolling Cake'의 리팩토링을 진행해보았다.
촉박한 개발 일정으로 스파게티 코드가 되어버린 코드를 한 번 살려보자..!

이 내용은 (현재)1편 - Next.js web 성능 최적화, 2편 - 3D 파일(threejs, react-three-fiber) 최적화 내용으로 이어질 예정입니다.

개요 🤔


어찌저찌 배포는 했는데, UT를 진행하자 느려도 너무 느리다는 말이 많았다..
프로젝트에는 무려 40개가 넘는 이미지 파일과, 20개에 육박하는 glb(3D) 파일이 있다. 올리는 아이템의 개수가 많아질수록, 많은 glb 파일을 불러와야해서 사용자들은 엄청나게 긴 로딩화면을 바라봐야만 했다.

로딩시간이 3초가 넘어가니 이탈 사용자들이 많이 생겼다. 게다가, 3D 로딩을 기다리지 않고 바로 케이크를 생성하는 과정에서 모델 완성모습이 찍히지않아 발생하는 에러도 빈번했다.

이번 기회에 확실히 짚고 넘어가면 좋을 것 같아, 내가 최적화 한 내용을 블로그에 정리해보고자 한다.

우선 3D 파일은 UI 디자이너와 따로 최적화(baked 등)과정이 필요해 이번 성능 최적화에서는 3D 파일을 제외한 내용만 담고있다.

최적화를 진행할 페이지는 아래 두 페이지이다.
1. 메인 페이지 (3D 파일 X)
2. 편지 페이지 (3D 파일 O / 이번엔 제외)

성능 분석 🗑️

성능 분석을 위해 아래 방법들을 사용했다.

Lighthouse

맨 처음 build 한 각 페이지들의 Lighthouse 점수는 다음과 같다.
(모든 페이지의 측정값은 cache 되지 않은 점수를 기준으로 한다)

To get accurate reports from Lighthouse, your application should always be tested with a production build. To run a production build, change into the project directory:

참고로 nextjs 공식문서에서는 빌드한 production 페이지를 lighthouse로 테스트 하는 것을 권장한다.

  1. 메인 페이지


    구글에서는 LCP 0-2.5초를 빠른 속도로, 4초가 넘어가면 느린속도로 인지하는데 내껀 무려 6초나 걸렸다..

  2. 편지 페이지

네트워크 탭 - 로딩시간 확인


요청에서 나온 시간이.. 무려 1.71나 걸렸다.
게다가 초기 로딩시에 transfer 크기가 1.1MB나 됐다. 압축된 크기가 무려 1.1MB라니;; (물론 3D 파일이 9할일 것 같긴하다)

성능 최적화 🧹

메인 페이지

1. 이미지 size 속성 추가

메인 페이지에서 이미지 레이아웃을 잡을때, nextjs <Image /> 속성 중 fill 을 사용해 부모 크기에 맞춰 반응형으로 이미지 크기를 잡아주고 있었다.

또한 메인 페이지에서 이미지 비율을 맞춰 사용하기 위해, object-fit: cover 속성을 사용 중이였다.

로컬에서 작업할 땐, warning을 다 무시했는데 살펴보니 sizes props가 없어서 발생한 경고가 많았다.

📄 sizes
The value of sizes will greatly affect performance for images using fill or which are styled to have a responsive size.

First, the value of sizes is used by the browser to determine which size of the image to download, from next/image's automatically generated srcset

The sizes property allows you to tell the browser that the image will actually be smaller than full screen. If you don't specify a sizes value in an image with the fill property, a default value of 100vw (full screen width) is used.

Second, the sizes property changes the behavior of the automatically generated srcset value.

'sizes' 값은 'fill'을 사용하거나 반응형 스타일의 이미지 성능에 큰 영향을 미칩니다.

첫 번째로, 'sizes' 값은 'next/image'의 자동 생성된 'srcset'에서 브라우저에서 다운로드할 이미지의 크기를 결정하는 데 사용됩니다.

'sizes' 속성을 사용하면 이미지가 실제로 전체 화면보다 작을 것임을 브라우저에 알릴 수 있습니다. 'fill' 속성을 사용하여 이미지에 'sizes' 값을 지정하지 않으면 기본값인 '100vw'(전체 화면 너비)가 사용됩니다.

두 번째로, 'sizes' 속성은 자동으로 생성된 'srcset' 값의 동작을 변경합니다.

쉽게 말하자면 이렇다.

기존 100x100 크기의 이미지를 다운 받아 모바일 화면에서는 20x20 크기로 화면에 뿌려준다고 가정해보자.

sizes의 속성을 지정해주지 않으면 기존의 100x100의 이미지를 다운받아 와서 뿌려주게되는데 이때 성능 저하가 발생하는 것이다.

설정은 아래처럼 할 수 있다.

<img
	...
	 sizes="(min-width: 66em) 33vw,
	  (min-width: 44em) 50vw,
	  100vw"
>

너비가 66em~ 이면 화면의 1/3(33vw), 44~66em 이면 화면의 절반(50vw), ~44em 이면 전체 너비(100vw)로 표시해 준 것이다.

https://cocoder16.tistory.com/37

나는 핸드폰처럼 작은 화면의 경우에는 1/3 정도로, desktop 은 100~110px 정도로 잡으면 될 것 같아 아래처럼 설정해줬다.

<Image
  src={cake.cakeImageUrl}
  className="object-cover"
  sizes="(max-width: 768px) 22vw, (max-width: 1200px) 100px, 110px"
  alt="cake"
  fill
/>

추가적으로 해당 페이지 뿐만 아니라, <img/> 태그를 사용하고 있던 코드를 모두 <Image /> 태그로 변경해주었다.

next.js에서는 <Image /> 태그를 사용했을 때 얻을 수 있는 이점이 많으니까 되도록이면 <Image/> 태그를 사용해주자

2. sharp 패키지 추가

nextjs 프로덕션 모드로 실행시, 아래와 같은 warning이 계속 발생했었다.

설치해주면 이미지 최적화에 좋다고 하니 이것도 설치해주자.

편지 페이지

1. 스타일시트 크기 개선

해당 페이지에서 스타일 크기가 커서 시간이 오래걸려 리소스 로드가 완료된 후에도 LCP 요소가 렌더링 되지않는 이슈가 있었다.

구글에서 권장하는 방법은 아래와 같다.

스타일시트 크기를 줄이기 위한 권장사항

개발을 할 때, 컴포넌트들은 css module로 나머지는 tailwind 로 style을 적용했는데 .. 이때 코드파편화 + 파일 크기가 커진 것 같다.

그래서 css module 로 작업한 부분을 모두 제거해주면서, global css 파일도 개선해줬다.
tailwind의 @layer 속성을 활용해 코드를 작성하면, 빌드 시 필요한 css만 사용된다고 한다.

@layer utilities {
	/* 기존에 css로 사용중이던 코드 @layer 내부로 이동 */
  .rotate-y-180 {
    transform: rotateY(180deg);
  }

  .transform-style-3d {
    transform-style: preserve-3d;
  }

  .backface-hidden {
    backface-visibility: hidden;
  }
	... 

기존 css module 파일에서 사용하던 코드들은 @layer components@apply를 사용해줬다.

@layer components {
  .header-shadow {
    --shadow: #000;

    @apply absolute left-[0.18rem] top-[0.17rem] whitespace-nowrap font-neo text-[1.38rem] text-effect_t;
    color: var(--shadow);
    -webkit-text-stroke: 5px var(--shadow);
  }

  .header {
    @apply whitespace-nowrap font-neo text-effect_t font-normal;
  }
	...


tailwind의 Function & Directives 에 대한 자세한 설명은 이곳에서 확인 가능하며, 간단히 설명하자면 아래와 같다.

  • @layer 스타일 세트 ‘버킷’을 생성한다. base, components, utilities를 사용할 수 있다.
    • base 태그에 적용하는 스타일
    • components 특정 컴포넌트에 적용하는 스타일
    • utilities tailwind 속성 추가
  • @apply tailwind 속성을 css 파일에서 선언 가능

결과적으로 8.3 KiB > 5.8 KiB로 파일 크기를 개선할 수 있었다.

2. FOIT(텍스트 플래시) 개선

neodgm 이라는 폰트파일을 로드하는데 시간이 오래걸려, 폰트가 바뀔때 깜박이는 이슈가 있었다. (이를 FOIT(Flash Of Invisible Text)라고 하며, 브라우저가 글꼴을 다운로드 하기 전까지 글자를 보여주지 않은 것을 의미한다)

해당 페이지 내용을 참고해, font-display: block 코드를 추가해줬다. 이를 사용하면, 텍스트가 시스템 글꼴을 로드했을때 타이틀을 불러와 글자 폰트가 바뀔때 깜박이는 이슈를 제거해준다.

이는 각자 상황에 맞춰 보여주는게 좋을 것 같다.

LCP 개선을 하고싶으면, 글씨체 로드전부터 보여주는 swap 속성을 사용하면 된다.

3. TTFB(Time to First Byte) 개선

TTFB란 사용자가 페이지 로드를 시작한 시점부터 브라우저가 HTML 문서 응답의 첫 바이트를 수신할 때까지의 시간을 말한다.

css 파일 크기를 개선하고 나니, 이번엔 TTFB 요청 시간이 매우 크게 잡혔다.

공식 문서에서는 다음과 같이 설명한다.

“하지만 이 모든 옵션을 최적화해야 한다는 사실을 명심해야 합니다. 한 부분에 최적화를 적용해도 LCP가 개선되지 않는 경우도 있습니다. 다른 부분에는 절약된 시간이 전환될 뿐입니다. … 이렇게 되는 이유는 이 페이지에서 JavaScript 코드 로드가 완료될 때까지 LCP 요소가 숨겨지고 이후 모든 항목이 한꺼번에 표시되기 때문입니다.”

해당 이미지 파일이 LCP 요소인데 지연이 발생한 것을 확인해, 해당 요소에 priority 설정을 추가해줬다.

추가적으로, 혹시나 배경 색상이 변경될까봐 모든 이미지 파일을 png 로 저장하고 있었는데.. jpg 파일 확장자를 사용해 파일 크기를 줄여주는 과정도 진행했다.

결과 ✨

메인 페이지

편지 페이지

기존 75 -> 92점으로, 85 -> 97점으로 올릴 수 있었다!
그렇게 변경한 부분이 없는 것처럼 느껴지는데, 꽤나 높은 점수로 최적화를 완료했다.
사실.. 무엇보다 3D 개선이 중요한 프로젝트라 다음번에 3D 파일 개선 게시글을 열심히 작성해보고자 한다!

profile
코드 깎는 개발자..

0개의 댓글

관련 채용 정보