이벤트 리스트 페이지 성능 개선기

이벤트 리스트 페이지 성능 개선기

생성일
2023년 05월 22일
태그
Next.js
LightHouse CI
Web Vital

Accessibility

aria-label

접근성 측면에서 button 태그가 접근 가능한 이름을 가지고 있지 않다는 경고를 받았다. 해당 버튼은 각 이벤트를 sns로 공유하는 기능을 지닌 버튼이다.
aria-label 속성은 해당 태그에 이름표를 붙혀준다고 생각할 수 있겠다. 스크린 리더는 aria-label의 값을 기반으로 사용자에게 내용을 전달해준다.
sns 공유 버튼이기 때문에 다음과 같이 속성을 추가하였다.
<button aria-label="sns-share"></button>
 
적용 결과
notion image
notion image
다음과 같이 접근성이 향상되었다. (87 → 95)

lang attribute

접근성 향상을 위해 document의 주요 언어를 html 태그의 lang 속성에 넣어주라는 경고를 받았다. 한국 서비스인만큼 _document.tsx 파일에 lang 속성을 추가해주었다.
<Html lang="ko"> <Head> <CompanyJsonLd /> <WebsiteJsonLd /> </Head> <body> <Main /> <NextScript /> </body> </Html>
 
적용 결과
notion image
 
notion image

Performance

Skeleton UI

이벤트 리스트 페이지는 20개의 이벤트들을 서버에서 불러온다. 불러오는 시점에는 높이가 존재하지 않다가 이벤트들이 생겨나면 높이가 생긴다. 이 때, 사용자가 Layout 변경으로 서비스에서 의도치 않은 액션을 취할 수 있다. Layout이 바뀌는 정도를 lighthouse에서는 Cumulative Layout Shift 라고 정의하는데, 이 수치는 0에 수렴할수록 좋다.
따라서, Layout 변경을 최소화하기 위해 서버에서 이벤트 리스트를 불러올 때, 앞으로 불러와질 이벤트 리스트 크기만큼의 높이를 주고, Skeleton UI를 통해 사용자에게 이러한 모양으로 이벤트 리스트들이 제공될 것이라는 정보를 줌으로써 사용자 경험을 개션시켰다.
notion image
 
다음과 같은 스켈레톤 UI는 성능과 사용자 경험을 모두 개선시킬 수 있다. CLS 수치를 0.015까지 줄일 수 있었다.

Largest Contentful Paint

이벤트 리스트 페이지에서는 한 번에 최대 20개의 이벤트 이미지들을 제공한다. Lighthouse로 성능을 측정할 때마다 랜덤한 이미지에서 LCP가 발생하였다.
Lighthouse는 이미지들의 크기가 모두 같고, 로딩되는 순서가 명시되지 않으면 성능을 측정할 때마다 우선순위를 다르게 부여하여 랜덤하게 이미지에서 LCP가 발생한다고 한다.
LCP 이슈를 해결할 수 있는 방법은 여러가지가 존재했다.
  • 중요한 자원에 preload 혹은 prefetch 속성 부여하기
→ 모두 똑같은 시점에 이벤트 리스트들이 제공되기를 원했기 때문에 패스
  • Lazy Loading
→ 사용자의 첫 뷰포트에서 보이는 이벤트 이미지들만 먼저 제공하고, 나머지 이미지들은 스크롤이 발생할 때 제공하는 방식이다. 즉, 이미지를 로딩하는 시점을 필요할 때까지 지연시키는 것이다. Next.js는 이를 자동으로 해주는 아주 강력한 기능을 가지고 있다.
 
또한, 구글에서 제공하는 LCP 측정은 4단계로 이루어진다.
  1. Time to First Byte (TTFB)
      • CDN 사용
      • 서버 하드웨어 업그레이드
  1. Resource load delay
      • LCP 요소 pre-loading
      • 같은 origin에서 리소스 제공하여 네트워크 딜레이 줄이기
  1. Resource load time → next/image 가 자체적으로 해줌
      • 이미지 크기 및 포맷 최적화
      • 캐시 사용
      • CDN 사용
  1. Element render delay
      • JS 파일 사이즈 최적화, dynamic import
      • SSR, SSG를 통해 LCP 요소를 서버 측에서 렌더링
notion image
 
이벤트 페이지는 SSR 방식으로 렌더링되기 때문에 이벤트 이미지들을 server-side에서 prefetching 하는 방법을 사용해보았다.
export const prefetchEventList = async ({ searchType }: EventListProps) => { let error; const queryClient = new QueryClient(); try { await queryClient.fetchInfiniteQuery(['get-eventlist', searchType], () => getEventListWithPageInfo({ searchType }), ); } catch (e: any) { error = e.response.data; Sentry.captureException(error); } return { queryClient, error }; };
export async function getServerSideProps(context: GetServerSidePropsContext) { const { query: { searchType }, } = context; const { queryClient, error } = await prefetchEventList({ searchType }); return { props: { searchType, dehydratedState: dehydrate(queryClient), error, }, };
 
적용 결과
notion image
notion image
LCP 수치가 1초 줄었다.
 
추가적으로, dynamic import를 활용하였다.
const EventListPageHeader = dynamic( () => import(/* "EventListPageHeader" */ '@components/event/EventListPageHeader'), ); const EventList = dynamic( () => import(/* "EventList" */ '@components/event/EventList'), );
notion image
 
배포 결과 LCP를 2.7초에서 0.6초까지 줄일 수 있었다.
하지만, dynamic import를 하면서 CLS 문제가 발생하였다.

Cumulative Layout Shift

CLS 점수는 사용자 경험에서 매우 중요하다. 이벤트 리스트들을 dynamic하게 import 했더니, import 시점에 이벤트 리스트 높이만큼 푸터가 올라와서 CLS가 발생하였다.
따라서, 이를 개선하기 위해 최소 높이를 명시해주었다.
<div className-"lg:min-h-[950px]"></div>
 
notion image
notion image

References

댓글

guest