플랫폼 메인에 노출되는 페이지 작업 후에 자체적으로 성능 개선을 시작해보았다.
참고할 수치와 지표는 Pagespeed Insights와 Lighthouse를 참고하여 진행했고, Pagespeed Insights가 점수가 더 낮게 나와서 낮은 점수를 더 끌어올려보자! 해서 Speed Insights를 주로 참고했다.
Pagespeed Insights vs Lighthouse
차이점은 크게 없습니다.
다만, Pagespeed Insights는 Lighthouse에서 측정한 값을 토대로 모아서 사이트 성능에 대한 보고서를 제공한다고 합니다. (왜 점수가 그렇게 차이나는지는 아직도 의문)
작업 전 점수
페이지는 서비스별로 1개씩 총 2개를 측정하였다.


만족할만한 점수는 아니었다. 딴건 몰라도 성능 점수가 너무 안나왔었다..
그래서 여러 레퍼런스를 찾아보며 내가 무엇을 할 수 있을까를 찾아보았고, 서치한 내용들을 정리하고자 한다.
성능 점수는 크게 6가지의 평가 항목으로 구성된다.
평가 항목
- FCP (First Contentful Paint, 콘텐츠가 포함된 첫 페인트) - 10%
- 페이지가 로드되기 시작한 시점부터 페이지 콘텐츠의 일부가 화면에 렌더링될 때까지의 시간을 측정합니다.
- 콘텐츠는 텍스트, 이미지(배경 포함) svg또는 흰 색이 아닌 canvas요소입니다.
- 좋은 FCP는 1.8초 이하입니다.
- SI (Speed Index, 속도 색인) - 10%
- 페이지 로드 중에 콘텐츠가 시각적으로 표시되는 속도를 측정합니다.
- 브라우저에서 로드되는 페이지의 비디오를 캡처하고 프레임 간의 시각적 진행을 계산합니다.
- 메인 스레드 작업 최소화, JavaScript 실행 시간 단축, 웹폰트 로드 중 기본 폰트 노출 등으로 개선이 가능합니다.
- LCP (Largest Contentful Paint, 콘텐츠가 포함된 최대 페인트) - 25%
- 페이지에서 가장 용량이 큰 컨텐츠를 렌더링하는 시간입니다.
- FCP의 경우 페이지에 스플래시 화면이 표시되거나 로딩중 아이콘이 표시되는 경우도 포함하기 때문에 사용자와 그다지 관련이 없습니다.
- 아래 사진을 보면 이해하실 수 있습니다.

- TTI ( Time to Interactive, 상호 작용까지의 시간) - 10%
- 사용자가 페이지와 완전하게 상호작용할 수 있을 때까지 걸리는 시간입니다.
- 단순히 시각적으로 보여지는 것 뿐만 아니라, 사용자가 페이지와 소통이 가능한 상태까지 렌더링 되어야 합니다.
- 예를 들면, 이벤트 핸들러가 가장 눈에 띄는 페이지 요소에 등록되는 시점에는 사용자와 사이트가 상호작용이 가능해졌다고 볼 수 있습니다.
- 코드 스플리팅, JavaScript 실행 시간 단축 등으로 개선이 가능합니다.
- TBT (Total Blocking Time, 총 차단 시간) - 30%
- 마우스 클릭, 화면 탭 또는 키보드 누름과 같은 사용자 입력으로부터 페이지가 응답하지 못하도록 차단된 총 시간을 측정합니다.
- FCP와 TTI 사이의 모든 긴 작업의 차단 시간을 추가하여 계산합니다.
- 50ms 이상 실행되는 모든 작업은 긴 작업입니다. 50ms 이후에 시간이 차단 시간으로 계산됩니다.
- 불필요한 코드, Javascript 제거, 코드 스플리팅 등으로 개선이 가능합니다.
- CLS ( Cumulative Layout Shift, 누적 레이아웃 이동) - 15%
- 페이지가 로드되기 시작하는 시점과 lifecycle 상태가 숨김으로 변경되는 시점 사이에 발생하는 모든 예기치 않은 레이아웃 이동의 누적 점수입니다.
- 일반적으로 리소스가 비동기적으로 로드되거나 DOM 요소가 기존 컨텐츠가 있는 페이지에 동적으로 추가될 때 발생합니다.
- 이미지나 동영상 요소에 크기를 미리 지정하거나 임시 박스같은 것으로 필요한 공간을 확보(width, height 지정) , 사용자 interactive 응답 외에 기존 컨텐츠 위에 컨텐츠를 넣지 않기, layout 변경을 트리거하는 css property보다 transform 애니메이션을 사용을 통해 개선이 가능합니다.
개선은 어떻게?
-
차세대 형식을 사용해 이미지 제공하기
- 메인에서 첫 화면에 대문짝만하게 이미지가 로드되는데 이 배경 이미지의 포맷을 변경합니다.
- png -> webp로 변경 (1440px 기준으로 1.2MB -> 54KB로 감소)
- 크기는 줄었지만 화질은 크게 차이 안남 (10퍼센트정도의 차이라고 하고, 압축률도 설정이 가능해서 화질에 대한 부분도 어느정도 챙길 수 있다고 함)
- 구글에서 강력하게 추천하고 있음
- 크롬, 엣지, 사파리 등 거의 모든 브라우저에서 지원하는 형식
-
꼭 필요한 이미지만 로드하기
-
반응형으로 구성하다보니 미리 5가지 Break point에 맞는 이미지를 미리 로드하고 있어서, 이 부분을 변경함.
-
const [image, setImage] = useState(null)
useEffect(() => {
const result = [
{ type: 'mobile', value: isMobile },
{ type: 'tablet', value: isTablet },
{ type: 'laptop', value: isLaptop },
{ type: 'desktop', value: isDesktop },
{ type: 'wide', value: isWide },
]
setImage(
result.find((value) => value.value)
? `/image/icon/section-1-background-${
result.find((value) => value.value)?.type
}.webp`
: null,
)
}, [isTablet, isMobile, isLaptop, isDesktop, isWide])
{image !== null && (
<Image
src={image}
layout="fill"
objectFit="cover"
objectPosition="center"
priority={true}
alt="background"
/>
)}
12월 1일 추가 사항
와 를 사용하면 좀 더 깔끔하게 할 수 있지 않을까 싶음.
-
이미지 width, height 지정해주기
- 이미지의 width, height가 100퍼센트로 잡혀있던 부분을 고정 크기의 컴포넌트로 잡아주고 그 안에 위치하게 하여 이미지 로드 전에 미리 공간을 잡아둘 수 있게 변경했다. -> CLS 개선
-
이미지 Cache TTL 설정 참고
-
next/image를 사용하면서 따로 로더를 사용중인데, 옵션으로 cacheTTL을 설정해서 http 캐싱을 하게 했다.
images: {
loader: 'akamai',
path: ["*********"],
domains: ["*********"],
images: {
minimumCacheTTL: 3600,
},
},
내용이 생각보다 길어져서 끊어서 적어야 할 것 같아서 이만 마무리하고 다음 포스트에 이어서..