본문 바로가기
시작/TIL(Today I Learned)

웹페이지 성능 최적화 - 웹사이트 로딩시간을 6.3초에서 0.6초로 줄이기

by 백씨네 2024. 4. 19.

목차

1. 문제

2. 분석 및 해결

3. 결과

 

 

문제

프론트엔드 개발에서 사용자 경험은 매우 중요하다.
이를 향상시키기 위해서 'Lighthouse'라는 도구를 활용할 수 있다. 이 도구는 현재 구현 중인 웹사이트를 분석하여 개선이 필요한 부분을 알려주고, 어떤 점을 개선해야 할지 알려준다.

 

(성능 참.. 처참하다...)

결론을 먼저 보여주자면

 

 

단순히 숫자로 보면 성능점수가 2배로 증가했다.
다른 것보다 LCP가 6.3초에서 0.6초로 끌어올렸다.

 

 

분석 및 해결

사실 앞의 문제와 결론을 보면 약간의 어그로성이 있다..^^
누군가는 나랑 비슷한 상황이 있을 것 같아서, 이런 방법을 초기에 생각하지 못해서 도움이 될까 블로그에 작성했다.

환경

  • Next v13.5.6
  • tailwind
  • S3 - 이미지 관리

위에 문제를 보면 내 웹사이트는 LCP에서 가장 큰 문제가 있었다.

LCP는 웹페이지 로딩 성능을 측정하는 지표이고, 사용자가 페이지를 방문했을 때 가장 큰 컨텐츠 요소가 화면에 완전히 표시되기까지 걸리는 시간을 나타낸다.

화면에 완전히 표시되는 것을 기준으로 하기 때문에 사용자 경험에 직접적인 영향을 미친다.

이를 해결하기 위해서 가장 먼저 생각했던 방법은 이미지의 크기를 줄이는 것이라 생각했다.

1. 이미지 크기 줄이기

사용자가 사이트를 방문했을 때 이미지를 요청해서 다운받는 과정이 있다. 이미지가 크면 그 이미지를 다운로드하고 보여주기까지 느릴 수밖에 없다고 생각했고 이미지 크기를 줄이기로 했다.

 

1-1. 업로드하는 S3에 avif, webp 확장자로 올리기

 

이미지를 png로 올리는 것이 아니라 직접 avif 확장자로 변환해서 업로드하는 방식으로 바꿔봤다.
기본적으로 불러오는 이미지의 크기가 작기 때문에 속도에 개선은 됐지만 좀 미미한 것 같았다.

 

1-2. Next.js에서 제공하는 최적화

Next.js는 이미지에서 최적화 기능이 제공된다.   

직접 이미지를 avif나 webp 방식으로 변환해서 올리는 과정은 중간에 변환하는 과정이 있어서 불편하다... 

 

하지만 next에서 기본제공하는 것이 있었다..

<Image
    // unoptimized // default : false
/>

"unoptimized" 기능은 기본적으로 꺼져있다. 메인화면의 Visual(banner)를 압축하지 않고 보여주겠다며 작성했었는데.. 너무 안 좋은 선택이었다. ( 가만히 두면 될걸.....ㅋㅋ)

 

그래서 unoptimized = {false} 돌려놨다. ( 정확히는 해당 속성을 지웠다.)

그리고 next.config.js를 수정하였다.

//next.config.js

images: {
    formats: ['image/avif', 'image/webp'],
},

이미지의 포맷을 avif와 webp로 지정하였다.

이렇게 했을 때 기존보다 약간의 개선은 있었지만 드라마틱한 변화는 없었다.

 

2. Visual 컴포넌트 수정

 

이 부분에서 좋은 결과를 얻었는데, 웹 성능을 크게 고려하지 않고 화면을 만들다 보니 신경을 쓰지 못했던 부분이었다.

핵심은 클라이언트 사이드 -> 서버사이드 컴포넌트로 변경했다.

현재 만든 웹사이트는 반응형을 고려하여 작업을 하다 보니 View port에 따라서 맞는 이미지를 보여주는 식으로 작업을 했다. 그래서 클라이언트 사이드에서 viewport를 체크하고 브레이크 포인트에 따라서 조건부렌더링을 이용해서 작업했는데, 이 부분이 LCP 성능 저하의 큰 영향이 있었다.

그래서 서버사이드에서 HTML을 생성해서 브라우저에서 받아 화면이 그려지고 클라이언트 측에서 뷰포트를 측정하고 맞는 이미지를 가져와서 사용자에게 보여주는! 이 과정으로 인해 느렸던 것이다.

그래서 클라이언트 사이드에서 서버사이드로 변경하였다.

기존

// 기존

const Visual = () =>{
    const [client, setClient] = useState(false)
    const renderSize = useRecoilValue(SettingState) // 상태로 가지고 있었음..
    useEffect(() => {
        setClient(true)
    }, [])
    if (client)
        return (
         <div className="relative h-[260px] w-full justify-center overflow-hidden md:h-[480px] pc:h-[400px]">
            {renderSize === 'mobile' ? (
                <Image
                    src={s3.path/main_mobile}
                    alt={'banner'}
                    fill
                    sizes="100%"
                    priority
                />
            ) : renderSize === 'pc' ? (
                <Image                
                    src={s3.path/main_pc}
                    alt={'banner'}
                    fill
                    sizes="100%"
                    priority
                />
            ) : (
                <Image
                    src={s3.path/main_tablet}
                    alt={'banner'}
                    fill
                    sizes="100%"
                    priority
                />
            )}
        </div>
    )
}

변경


const Visual = () =>{
    return (
        <div className="relative h-[260px] w-full justify-center overflow-hidden md:h-[480px] pc:h-[400px]">
            <Image
                className="block md:hidden"
                src={s3.path/main_mobile}
                alt={'banner'}
                fill
                sizes="100%"
                priority
            />

            <Image
                className="hidden md:block pc:hidden"
                src={s3.path/main_tablet}
                alt={'banner'}
                fill
                sizes="100%"
                priority
            />

            <Image
                className="hidden pc:block"
                src={s3.path/main_pc}
                alt={'banner'}
                fill
                sizes="100%"
                priority
            />
        </div>
    )
}

이런 식으로 모든 필요 이미지를 처음에 만들고 CSS의 Display:none/block과 미디어쿼리를 이용해서 사용자에게 보일 요소와 안보일 요소를 지정한다.

 

 

결과

결과는 처음에 보여준 사진처럼 6.3초에서 0.6초로 초기 화면을 보여주는데 많은 이득을 챙길 수 있었다. 모든 페이지에 이렇게 해야 한다는 것은 아니지만 적어도 처음 들어오는 메인화면의 Visual 영역은 이렇게 해서 웹사이트에서 이탈을 막을 수 있으면 더 좋을 것 같다.

고려해야 할 점

1. 이미지 최적화

처음 진입할 때 이미지를 S3에서 요청해서 가져온다. 이 부분에서 이미지의 크기가 컸다면 아마 시간은 더 걸렸을 것이다. 하지만 위에서 첫 번째 해결방법으로 이미지를 최적화했던 방식을 같이 생각해 보면 기존엔 300KB의 이미지를 불러왔지만 20KB 대 3개(각 디바이스)의 이미지로 가져온 것으로 단순 산술로도 충분히 이득인 작업이었다고 생각한다.

2. 슬라이드

아직 이 부분은 구현하기 전이지만 슬라이드 배너일 경우 5장의 슬라이더면 15개의 이미지가 나올 것 같다.. none/block 방식으로 모든 이미지를 미리 요청하고 화면을 보여주는 것이 맞는 방법일지는 확실하지 않지만 나쁘지 않았던 방식이라 생각한다. 다른 웹 몇 군데에서도 이렇게 하는 것 같으니... 일단 더 찾아봐야 할 것 같다.

댓글