[웹] 압축 알고리즘을 활용하여 웹 성능 최적화 수행하기 (feat. Vite, Nginx, gzip, Brotli)

Woonil·2025년 5월 1일
2

목록 보기
5/6
post-thumbnail

본 포스팅에서는 브라우저와 웹 서버 간 통신에 텍스트 압축 기법을 수행하기 위해 알아야할 개념들을 짚어보고, 기존 프로젝트에 해당 기법을 적용하여 최적화하는 과정과 결과를 살펴본다.

웹 서비스를 lighthouse로 검사했을 때, Performance와 관련하여 'Enable text comporession' 경고를 본 적이 있을 것이다. 빌드된 자바스크립트 코드도 결국 텍스트 기반의 정적 리소스이고, 서버는 네트워크를 통해 이 리소스들을 브라우저에 전송해야 한다. 따라서 이 과정에서 자바스크립트 파일의 용량이 큰 경우, 네트워크 전송이 느려지는 병목 현상이 충분히 발생할 수 있다.

이를 해결하기 위해서는 해당되는 텍스트 기반 리소스를 압축할 필요가 있다. 빌드된 자바스크립트 결과물을 확인하면 알 수 없는 외계어처럼 들여쓰기 되지 않는 코드들을 본 적이 있을 것이다. 이는 Webpack, Vite와 같은 번들링 도구가 1차적으로 사전 처리를 통해 텍스트 기반 애셋의 크기를 줄이기 때문이다. 하지만 여기서 더 나아가 압축 알고리즘을 적용하여 이들을 더 가볍게 할 수 있다. 대표적인 압축 알고리즘으로는 gzip, Brotli가 있으며, 이는 텍스트 기반 asset(HTML, CSS, JavaScript)에서 가장 우수한 성능을 발휘하는 것으로 알려져 있다. 물론 Vercel과 같은 배포 서비스나 CDN 서비스에서 이러한 부분이 자동화되어 있긴 하다.
Vercel 공식문서: How Compression Works for the Edge Network
AWS CloudFront 공식문서: 압축된 파일 제공

🤔개념

Accept-Encoding과 Content-Encoding

Accept-Encoding 요청 HTTP 헤더는 클라이언트가 이해 가능한 컨텐츠 인코딩(보통 압축 알고리즘)이 무엇인지를 알려준다. 모든 최신 브라우저는 gzip, Brotli 압축을 지원하며 요청 헤더에 Accept-Encoding에 두 가지 모두에 대한 지원을 명시한다.

이러한 기본적인 지원에도 웹 서버(이를테면 Nginx)에도 압축을 사용하게끔 구성되어 있어야 한다. 웹 서버가 압축을 적용하게 되면 브라우저의 요청에 대한 응답에 Content-Encoding 헤더를 포함한다. 여기에 명시된 값은 응답 본문에 어떤 추가적인 컨텐츠 인코딩이 적용될지를 나타낸다. 값으로는 gzip, br 등이 있다.

예를 들어, 브라우저(클라이언트)의 요청의 Accept-Encoding이 gzip, deflate, br, zstd 이고, 응답의 Content-Encoding 헤더 값이 gzip이이며 Cotent-Type 헤더 값이 application/javascript(자바스크립트 파일)라고 하자. 이 상황을 웹 상에서의 HTTP 요청과 응답에 비유하자면 다음과 같다.

브라우저(클라이언트): "나는 gzip, deflate, br, zstd 압축(인코딩)을 지원하니까, 이 중에서 좀 부탁해."
서버: '너가 요청한대로 자바스크립트를 gzip으로 인코딩하여 보냈으니까, 너는 그거에 맞게 해석(디코딩)하면 돼.'

압축

압축 기법

gzip

리눅스를 다뤄봤으면 익히 들어본 용어일 것이다. gzip은 Deflate 알고리즘을 기반으로 동작한다.

Brotli

범용 무손실 압축 알고리즘으로, LZ77 알고리즘의 최신 변형, 허프만 코딩 및 2차 컨텍스트 모델링을 결합하여 데이터를 압축한다. Brotli는 gzip보다 더 나은 압축 비율을 제공하며 압축하는 데 드는 속도는 비슷하지만, Brotli 압축은 Gzip 압축보다 느리므로 상황에 따라 적절한 기법을 선택해야 한다.

압축이 작동하는 모습 확인하는 방법

압축이 잘 작동했는지 쉽게 확인하기 위해서는 Chrome 개발자 도구의 네트워크 패널 하단부를 확인하면 된다.

각 필드가 의미하는 바는 다음과 같다.

  • 현재 로드된 요청 수 / 총 요청 수: 웹페이지를 로드하는 동안 서버에 보낸 HTTP 요청의 수로, HTML, CSS, JavaScript, 이미지, 폰트, API 호출 등 모든 네트워크 요청을 포함한다.
  • 실제 전송된 데이터 크기 / 총 전송해야 할 데이터 크기: 첫 번째 값(예시에서는 226 kB)은 네트워크를 통해 실제로 전송된 데이터의 압축된 크기이며, 이 값은 HTTP 헤더와 압축된 본문(gzip, brotli 등)을 포함한 실제 네트워크 트래픽을 나타낸다.
  • 현재 로드된 리소스 크기 / 총 리소스 크기: 첫 번째 값(예시에서는 763 kB)은 압축 해제 후 실제 사용 가능한 리소스의 크기이며, 브라우저가 메모리에서 사용하는 실제 크기를 나타낸다.

😎실습

이전에 개발했던 동아리 홈페이지 프로젝트는 동아리방에 위치한 온프레미스 서버에서 서비스되고 있다. 클라우드 CDN 서비스를 따로 사용하지 않고 있고, 웹서버인 Nginx를 포함한 도커 환경에서 애플리케이션들이 돌아가고 있는 상태이다. 따라서 번들링 도구와 웹 서버 설정을 통해 압축을 직접 시도해야 했으며, 이 과정과 관련한 내용을 담고 있음을 알린다.

  • 실습 구성 환경
    • 웹서버: Nginx(컨테이너)
    • 번들링 도구: Vite
    • 서버: Docker(Alpine Linux)

우선 압축을 적용하지 않은 상태에서 nginx 컨테이너를 띄워서 네트워크 탭 내의 요청과 응답 헤더를 확인해본다. 요청의 Accept-Encoding 헤더에는 gzip, deflate, br, zstd가 명시되어 있지만, 응답의 Content-Encoding 헤더는 존재하지 않는다.

아래는 네트워크 탭에서 JS만 필터링해서 Size 순으로 정렬한 결과이며, 이후 압축 후의 이것과 비교해보자.

Vite의 플러그인으로 빌드 시 압축 진행하기

가장 먼저 React에서 작성한 자바스크립트 등의 프로젝트를 빌드할 때 번들링 도구인 Vite가 압축을 진행하게끔 해야 한다. 이와 관련한 플러그인인 vite-plugin-compression2 를 설치한 후, vite 설정 파일에 반영한다.

yarn add vite-plugin-compression2 -D
or
npm install vite-plugin-compression2 -D

이후 yarn build로 빌드를 수행하면, 빌드 결과물에 하나의 파일당 js, br, gz 확장자를 가진 세 개가 생성되어 있을 것이다.

실제로 용량이 줄었는지 빌드 결과물에서 zustand 라이브러리를 찾아보자. 용량이 5.4K에서 1.9K로 줄어있는 것을 확인할 수 있다.

Nginx에서 압축 관련 설정 명시하기

앞서도 설명했듯, Vite 설정만 한다고 해서 실제 운영 서버에서 해당 압축 파일을 제공할 수 있는 것이 아니다. 웹 서버에서도 클라이언트(브라우저)에 압축파일을 제공할 것을 약속해야 한다. 이를 위해서 nginx.conf에 다음과 같이 명시한다.

Dockerfile에서 압축 관련 모듈 설치하기

nginx.conf 최상단에서 모듈을 불러왔는데, 이는 실제 운영 서버에서 해당 모듈을 다운을 받았다는 전제가 있다. 나의 경우 도커로 배포했기 때문에 Dockerfile 실행시에 모듈을 설치해야 한다.

🤓결과

응답 헤더 확인

압축 적용 전에 수행한 것처럼, nginx 컨테이너를 띄운 후 네트워크 탭 내의 요청과 응답 헤더를 다시 확인해본다. 응답 헤더에서 Content-Encoding: br 을 확인할 수 있다.

리소스 크기 확인

아래는 JS 리소스에 대해 Size 순으로 정렬한 것이다. 용량이 확연히 줄어든 것을 확인할 수 있다.

네트워크 전송량 확인

메인 페이지를 기준으로 텍스트 압축 전후의 네트워크 전송량을 비교해보자.

  • 적용 전 네트워크 탭 확인
  • 적용 후 네트워크 탭 확인

참고자료
Add Brotli and Gzip compression to nginx in Docker
web.dev 텍스트 기반 애셋의 인코딩 및 전송 크기 최적화

profile
프론트 개발과 클라우드 환경에 관심이 많습니다:)

0개의 댓글