빽빽한 업데이트 플랜으로 피처 개발만 하다 드디어 성능 개선을 할 수 있는 일정이 잡혔다. 유저가 가장 많이 유입되는 페이지인 '검색 상세 페이지'부터 성능 개선을 시작해보려 한다.
light house 성능 측면에서도 점수가 낮으며 실제로 렌더링되는 모습만 봐도 문제가 꽤 많다.
바쁜 일정으로 한 페이지 내에서 팀원들끼리 컴포넌트를 나눠서 작업해야했고, 한 컴포넌트를 여러 명이 수정하기도 했던 상황이라 렌더링 이슈가 많다. 그리고 Next.js 프레임워크에서 개발하고 있었으나 서버사이드 렌더링을 거의 사용하지 않고 있었다.
화면이 렌더링되면서 스크롤이 없는 상태에서 컨텐츠가 길어지면 세로 스크롤 영역이 오른쪽에 생기는데 이때 화면폭이 줄어들면서 화면 전체가 움직인다. 특히 컨텐츠를 검색할 때 실시간으로 결과가 보이기 때문에 결과가 짧았다 길어졌다 반복되면 이 화면폭 움직임은 더 크게 느껴진다.
그래서 overflow-y:scroll
속성을 통해 세로 스크롤 영역을 고정으로 유지하고 스크롤바 디자인을 커스텀하였다.
body {
overflow-y: scroll;
}
body::-webkit-scrollbar {
width: 7px;
}
body::-webkit-scrollbar-thumb {
background-color: #555555;
border-radius: 10px;
}
간단한 css로 화면이 렌더링될 때, 검색할 때 화면폭이 움직이지 않고 고정돼서 훨씬 깔끔한 느낌이 들었다.
검색 상세 페이지의 경우 보여주는 데이터가 매우 많다. 부르는 API만 메타성 8개, 컨텐츠 API 7개로 API 조합 및 재가공해서 보여주는 영역도 많다. 그렇다보니 이 모든 API를 서버사이드에서 작업하기에는 부하가 클 것이고 그만큼 속도도 느려질 것이다.
보여주는 데이터가 많은 타 서비스들의 페이지 렌더링을 참고해보니 모든 영역을 SSR로 보여주는게 아닌 페이지의 일정 부분만 SSR으로 보여주고, 나머지는 스켈레톤 UI로 처리하는 것을 알 수 있었다. 그래서 개선할 페이지에서도 아래와 같이 영역을 나누어 보았다.
CSR로 구현시 데이터를 가져오는 동안 보여줘야했던 전체 화면 로딩이 있었다. 노란 컴포넌트 영역은 서버사이드에서 완성된 상태로 가져올 것이기 때문에 불필요한 전체 로딩 UI를 제거했다.
검색 결과에 따라 다른 정보를 보여줘야하며, 최신 데이터를 제공해야하기 때문에 SSR, SSG, ISR 중 SSR을 선택했다. 노란 컴포넌트의 데이터를 가져올 수 있는 API를 getServerSideProps에서 처리해서 initialData로 가져와서 사용했다.
서버사이드에서 initialData를 미리 가져왔으나 여전히 깜빡거리는 UI들이 있었다. 바로 노란 컴포넌트 내에서 추가 API 호출 후 생성되는 UI들이었다.
이러한 UI들은 렌더링되는 영역을 css로 정확하게 잡아주어야 감싸고 있는 레이아웃이 움직이는 문제가 안생기며 스켈레톤 UI로 대체해주었다.
기존에는 이미지 cdn 주소를 가져오기 위해 API를 2개 사용하고 있었다.
url 하나만 있으면 되는데 이 때문에 렌더링 속도가 특히 느렸다. 그래서 백엔드팀과 함께 의논하여 공통 cdn 주소와 key값만으로 이미지 url을 생성할 수 있도록 수정하였다.
이렇게 개선하고 나니 한가지 눈에 띄는 문제가 있었다. 페이지가 전환될 때 잠깐의 딜레이가 있는데 유저 입장에서는 페이지가 잠깐 멈춘 느낌을 받을 수도 있다. 그래서 고민하던 중 타 서비스들을 참고해보니 사이트 상단에 작은 페이지 로딩 프로그레스 바를 넣어서 “페이지가 이동중이다”라는 느낌을 받게 하고 있었다.
progress에 따라 로딩바 애니메이션을 만들기 위해 react-top-loading-bar
라는 라이브러리를 사용했다.
const [progress, setProgress] = useState(0);
useEffect(() => {
const start = () => {
setProgress(40);
};
const end = () => {
setProgress(100);
};
Router.events.on('routeChangeStart', start);
Router.events.on('routeChangeComplete', end);
Router.events.on('routeChangeError', end);
return () => {
Router.events.off('routeChangeStart', start);
Router.events.off('routeChangeComplete', end);
Router.events.off('routeChangeError', end);
};
}, []);
return (
<LoadingBar color={COLOR.POINT.MAIN} progress={progress} />
)
시각적으로 보이는 이슈들부터 해결했는데 점수가 20점 가량 올랐다.
CSR로 렌더링되고 있던 부분들을 SSR로 바꾸면서 LCP, TBT 시간이 50%이상 감소했으며
CLS은 기존에도 시간은 적게 걸렸으나 눈으로 확인되던 Layout shift를 제거했으며 시간도 80%이상 감소되었다.
2차 개선 계속…