Curt Poem

프론트 엔드 공부와 지식 나눔을 위한 블로그

개발/기타

웹성능은 어떻게 개선할수 있을까요?

Dovelop 2024. 9. 2. 16:28

많은 개발자들의 성능 개선을 위해 자바스크립트의 코드를 개선하는 데 집중합니다. 하지만 웹 페이지의 성능에는 자바스크립트 뿐만 아니라 HTML, CSS, 파일의 크기, 네트워크 등 다양한 요소가 영향을 미칩니다. 물론 자바스크립트의 성능에 끼치는 영향이 다른 요소에 비해 큰 경우가 많지만 CSS 또한 분명히 성능에 영향을 끼칩니다. 오늘은 자바스크립트를 제외한 웹 성능을 개선할 때 신경 쓸만한 요소를 살펴보겠습니다.

HTML 구조

Dom의 깊이

HTML 문서의 DOM(Document Object Model) 구조가 복잡하고 깊이가 깊을수록 브라우저가 문서를 렌더링하는 데 더 많은 시간을 소요하게 됩니다. 단순히 DOM에 포함된 HTML 요소의 개수도 영향을 끼치지만 그보다는 어떻게 배치되어 있는 지가 더 큰 영향을 줍니다. 같은 개수의 요소가 넓게 펼쳐져 깊이가 얕은 DOM보다는 트리의 깊이가 깊은 DOM의 성능이 더 나쁩니다. 다음 두 개의 코드에서 2번째 코드가 성능이 더 나쁩니다. 여에 따르면 트리가 넓어질 때보다 깊어질 때 로딩 시간이 훨씬 빠르게 증가합니다. 만약 복잡한 CSS 선택자가 추가된다면 차이는 더 심해질 것입니다.

<html>
  <body>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <!-- ... -->
    <div>5000번째 div.</div>
  </body>
</html>
<html>
  <body>
    <div>
      <div>
        <div>
          <div>
            <!-- ... -->
            <div>5000번째 div.</div>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

DOM의 깊이가 깊으면 HTML을 파싱한 뒤, 브라우저가 요소 간의 관계를 파악하고 스타일을 적용하는 데 더 많은 계산을 해야 합니다. 그 결과 HTML 문서의 초기 로딩 시간을 증가시키고 깊은 DOM은 그대로 리렌더링의 비용이 되어 성능을 악화시킵니다. DOM의 깊이를 줄이기 위해서는 불필요한 요소를 제거하고 CSS와 JavaScript를 통해 재사용 가능한 구성 요소를 사용하는 것이 좋습니다.

CSS와 성능

CSS 선택자

CSS 선택자는 HTML 요소에 스타일을 적용하기 위한 도구입니다. 그러나 선택자가 복잡할수록 브라우저가 스타일을 적용하는 데 더 많은 시간을 들이게 됩니다. 예를 들어, 후손 선택자(div ul li a)나 속성 선택자(input[type="text"])와 같이 구체적인 조건을 사용하는 선택자는 브라우저가 모든 요소를 탐색하며 조건에 맞는 요소를 검사하도록 만듭니다. 필요한 경우에는 사용하는 것이 좋지만 보다 간단한 선택자인 ID 선택자, 클래스 선택자, 태그 선택자와 같이 단순한 선택자를 사용하는 것이 성능에 유리합니다. 특히 후손 선택자는 DOM의 깊이가 깊고 후손이 많을 수록 성능에 악영향을 주므로 자식 선택자(>)나 형제 선택자(+, ~)를 사용해 범위를 좁히는 것이 좋습니다.

CSS 애니메이션과 트랜지션

CSS 애니메이션이나 트랜지션을 사용할 때, 부적절한 사용은 페이지 성능을 저하시킬 수 있습니다. 특히, 애니메이션이 레이아웃을 변경하게 되면 브라우저는 Reflow와 Repaint를 반복하게 되어 성능이 저하됩니다. 대신, transform, opacity와 같은 속성은 GPU 가속을 활용할 수 있어 성능 저하를 최소화할 수 있습니다. 예를 들어, 위치를 변경하는 애니메이션에서 top이나 left 대신 transform: translate()를 사용하는 것이 좋습니다. 애니메이션의 경우 화면에 보일 경우에만 애니메이션이 적용되도록 하여 성능을 개선할 수 있습니다.

레이아웃 변동

레이아웃 변동(Layout Shift)은 페이지가 사용자 인터페이스를 표시한 후에도 요소들이 이동하는 현상을 말합니다. CSS에서 크기, 위치 등의 속성이 동적으로 변경되면, 브라우저는 레이아웃과 페인트 과정을 반복하게 됩니다. 이러한 변동은 브라우저가 Reflow(레이아웃 계산)와 Repaint(화면 갱신)를 반복하게 만들어 성능에 악영향을 미칩니다. 특히 Reflow는 더 큰 비용이 과정으로 요소의 크기 변경과 위치의 변경은 색상 변경이나 border의 추가/삭제보다 성능에 더 나쁜 영향을 미칩니다.

Reflow
페이지의 레이아웃을 계산하는 과정으로 DOM 요소의 크기와 위치를 결정
텍스트 내용 변경, 새로운 요소 추가, `display` 속성 변경, 크기 변경 등의 상황에서 발생
Repaint
페이지의 시각적 표현을 업데이트하는 과정으로 요소의 외관(색상, 배경)을 결정
요소의 색상, 배경색, 테두리 등을 변경하는 경우에 발생

 

 

요소의 크기나 위치의 변경을 줄이기 위해, 이미지나 비디오같은 요소의 크기를 명시적으로 지정할 수 있습니다. 요소가 예상치 못하게 이동하는 것은 성능뿐만 아니라 사용자의 경험에도 악영향을 미치기 때문에 주요 컨텐츠를 표시하는 요소의 크기를 지정하여 레이아웃의 변동을 최소화하는 것이 좋습니다.

이미지

이미지는 웹 페이지 성능에 큰 영향을 미치는 요소 중 하나입니다. 이미지 최적화를 통해 페이지 로딩 시간을 단축하고, 전반적인 성능을 개선할 수 있습니다. 이미지와 관련하여 성능을 개선하기 가장 쉬운 방법 중 하나는 WebP와 같은 형식의 이미지 파일을 사용하는 것입니다. WebP는 웹에 최적화된 이미지 파일로 작은 크기를 가지고 있어 다른 이미지 형식에 비해 더 빠르게 로딩할 수 있습니다.

 

또 다른 간단한 방법은 이미지 태그에 loading="lazy" 속성을 추가하여 화면에 보일 때 이미지를 로딩하도록 하여 성능을 개선할 수도 있습니다.

 

화면 크기에 따라 다른 이미지 제공

다양한 화면 크기와 해상도에 맞춰 적절한 크기의 이미지를 제공함으로써 성능을 최적화할 수 있습니다. 실제로 필요한 크기로 조정된 이미지만을 전송하여 모바일 환경에서의 불필요하게 큰 사이즈의 이미지를 가져올 필요가 없습니다. 단, 각 크기에 맞는 이미지를 미리 가지고 있어야 합니다.

<img src="image-small.jpg"
     srcset="image-small.jpg 480w, image-medium.jpg 800w, image-large.jpg 1200w"
     sizes="(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px"
     alt="Responsive example image">

 

이미지 스프라이트
여러 개의 작은 이미지를 하나의 큰 이미지 파일로 결합한 후, CSS를 사용해 해당 이미지의 일부만을 표시하는 기법으로 HTTP 요청 수를 줄여 페이지 로딩 속도를 개선하는 데 유용합니다.하지만 새로운 아이콘이나 이미지가 추가될 때마다 스프라이트 이미지를 재생성해야 하므로 유지보수가 번거로울 수 있습니다. 하나의 이미지를 가져온 뒤 필요한 부분만 사용합니다.

.icon {
    background-image: url('sprite.png');
    background-repeat: no-repeat;
}

.icon-home {
    width: 32px;
    height: 32px;
    background-position: 0 0; /* 스프라이트 이미지의 왼쪽 상단 */
}

.icon-user {
    width: 32px;
    height: 32px;
    background-position: -32px 0; /* 스프라이트 이미지에서 두 번째 아이콘 */
}

네트워크 관련

파일의 크기

웹 페이지의 성능을 높이기 위해 파일 크기를 줄이는 것은 매우 중요합니다. 특히, HTML, CSS, JavaScript 파일의 크기를 줄이는 방법을 살펴봅니다. CSS와 JavaScript 파일에서 불필요한 공백, 주석, 줄바꿈을 제거하면 하고 변수명을 짧게 줄여 파일 크기를 줄일 수 있습니다. WebPack과 같은 번들러에는 이런 기능이 기본적으로 내장되어 있어 쉽게 적용할 수 있습니다. 그 외에도 파일 자체를 압축하거나 코드 스플리팅을 사용할 수 있습니다. 자세한 내용은 웹사이트 성능 개선 - 파일 크기 줄이기(코드스플리팅과 압축)를 참고하세요.

HTTP 요청의 개수

HTTP 요청이 많아지면 브라우저와 서버 간의 통신이 빈번해져 성능이 저하될 수 있습니다. 초기의 HTTP 요청은 최대 연결 개수가 6개로 정해져있어 한 페이지에서 HTTP 요청이 6개를 넘어가면 다음 요청은 대기하여야 했습니다. 그래서 너무 많은 HTTP 요청은 로딩 시간을 크게 늘릴 수 있었습니다. 하지만 HTTP/2 버전 이후로는 이 제한이 크게 줄었습니다. 그럼에도 불구하고 HTTP 요청은 여러 비용이 발생합니다.

 

먼저 최초 연결시 TCP 연결(3-way 핸드셰이크, TCP Slow Start)에서 비용이 발생합니다. 물론 HTTP/2 및 HTTP/3에서는 단일 연결에서 다중 요청을 처리할 수 있으므로, 동일한 도메인에 대한 여러 HTTP 요청이 있을 때 추가적인 TCP 연결 설정 오버헤드가 발생하지 않습니다. DNS 조회도 마찬가지입니다. 도메인 주소를 브라우저가 캐싱하므로 최초의 요청에만 DNS 조회 비용이 더 클 것입니다. 여기까지의 비용은 고정 비용이므로 HTTP 요청의 개수가 영향을 미칠 것 같지는 않습니다.

 

하지만 HTTP 요청에는 변동 비용 또한 존재합니다. 변동 비용은 각 요청마다 발생하며, HTTP 요청의 개수만큼 증가하는 비용입니다. 모든 HTTP 요청은 헤더가 존재합니다. 이 헤더를 작성하고 압축하는 것에 대한 비용은 HTTP 요청이 많아질 수록 같이 증가합니다. 그리고 여러 요청이 같은 본문(body)를 가지면 불필요한 중복 비용이 발생하는 것입니다. 여기에 더해 HTTPS 요청에서 발생하는 암호화도 분명히 존재하는 비용입니다. 이런 요소들이 엄청나게 큰 비용은 아닐지라도 어렵지 않게 줄일 수 있는 비용이기에 성능 개선에서 생각해 볼 만한 요소입니다. 그리고 요청의 개수는 웹 페이지의 성능 뿐만 아니라 서버의 성능에도 영향을 줄 수 있습니다. 요청의 개수가 줄어들면 브라우저의 오버헤드 뿐만 아니라 서버가 각 요청을 받고 응답을 보내는 데에 존재하는 오버헤드 역시 줄어들게 됩니다.

Google Lighthouse로 성능 측정해보기

이제까지 성능 개선을 위한 일반적이고 널리 알려진 방법을 살펴보았습니다. 성능을 개선하는 만큼 중요한 것은 성능이 얼마나 개선되었는지 확인하는 것입니다. 

perfomance 탭의 측 정후 Summary 보기 - Naver 메인화면의 측정 결과

크롬의 개발자 도구의 Perfomance 탭과 Mermory을 이용하여 페이지의 로딩 시간과 메모리 사용량을 확인할 수도 있지만 전반적인 개선점까지 제안해주는 Lighthouse를 참고하는 것도 좋습니다. 특히 Lighthouse는 접근성과 SEO 등 성능을 제외한 웹 사이트 평가 지표도 가지고 있습니다.

Lighthouse가 평가한 개선이 필요한 사항들 - Naver 메인 화면의 측정 결과
Lighthouse로 측정한 웹사이트 점수, 모든 항목에서 만점을 맞으면 폭죽이 터진다. - 프로젝트로 진행했던 웹페이지 측정 결과

참고 자료

MDN, Perceived performance, 웹 성능 측정하기, HTML performance optimization, CSS performance optimization
stack overflow, If requests are asynchronous, why does reducing the number of http requests a page needs to load improve performance?What's the difference between reflow and repaint?

[번역] 당신의 DOM은 얼마나 깊은가요