본문 바로가기

Frontend/성능 최적화

[홈페이지 캐시 최적화, 불필요한 CSS 제거] 웹 성능 최적화까지 해보자-13

캐시 최적화

이때까지 최적화한 서비스를 Lighthouse를 통해 검사해보자.

Diagnostics 섹션의 Serve static assets with an efficient cache policy라는 항목이 있다.

이 항목은 네트워크를 통해 다운로드하는 리소스에 캐시를 적용하라는 의미이다.

 

Network 패널에서 위 리소스 중 하나를 확인해보면 응답 헤더에 캐시에 대한 설정인 Cache-Control이라는 헤더가 없는 것을 알 수 있다.

npm run start로 실행한 서버에는 캐시 설정이 제대로 되어 있지 않은 것이다.

 

캐시의 종류

웹에서 사용하는 캐시는 크게 두 가지로 구분할 수 있다.

메모리 캐시디스크 캐시이다.

  • 메모리 캐시: 메모리에 저장하는 방식이다. 여기서 메모리는 RAM을 의미한다.
  • 디스크 캐시: 파일 형태로 디스크에 저장하는 방식이다.

 

어떤 캐시를 사용할지는 직접 제어할 수는 없다.

Network 패널을 확인해보면 Size 항목에 memory cache 또는 disk cache라고 표시된 것을 볼 수 있다.

이 리소스들은 브라우저에 캐시된 리소스이다.

캐시가 적용된 리소스를 클릭해보면, 응답 헤더에 Cache-Control이라는 헤더가 들어 있는 것을 볼 수 있다.

이 헤더는 서버에서 설정되고, 이를 통해서 브라우저는 해당 리소스를 얼마나 캐시할지 판단한다.

 

Cache-Control

Cache-Control은 리소스의 응답 헤더에 설정되는 것이다.

Cache-Control에 들어가는 값은 다음과 같다.

  • no-cache: 캐시를 사용하기 전 서버에 검사 후 사용
  • no-store: 캐시 사용 안 함
  • public: 모든 환경에서 캐시 사용 가능
  • private: 브라우저 환경에서만 캐시 사용, 외부 캐시 서버에서는 사용 불가
  • max-age: 캐시의 유효 시간

public과 private으로 설정하면 max-age에서 설정한 시간만큼은 서버에 사용 가능 여부를 묻지 않고 캐시된 리소스를 바로 사용한다.

만약 유효 시간이 지났다면 서버에 캐시된 리소스를 사용해도 되는지 다시 체크하고 유효 시간만큼 더 사용한다.

웹 리소스는 브라우저뿐만 아니라 웹 서버와 브라우저 사이를 연결하는 중간 캐시 서버에도 캐시될 수 있다.

중간 서버에서 캐시를 적용하고 싶지 않다면 private 옵션을 사용한다.

만약, Cache-Control: max-age=60이라면, 60초 동안 캐시를 사용하고, private 옵션이 없기 때문에 기본 값인 public으로 설정되어 모든 환경에서 캐시를 한다.

Cache-Control: private, max-age=600이라면, 브라우저 환경에서만 600초 동안 캐시를 사용한다는 뜻이다.

Cache-Control: public, max-age=0이라면, 모든 환경에서 0초 동안 캐시를 사용하는데, 매번 서버에 캐시를 사용해도 되는지 확인하는 상태라고 보면 된다. 즉, no-cache와 동일하다.

 

캐시 적용

응답 헤더는 서버에서 설정해준다.

이제 서버 코드를 한 번 살펴보자.

아래 코드는 응답 헤더를 설정하는 코드이다.

const header = {
    setHeaders: (res, path) => {
        res.setHeader('Cache-Control', 'max-age=10');
    },
}

실행해보면 리소스들이 캐시되는 것을 볼 수 있다.

설정한 시간인 10초가 지나면 브라우저는 기존에 캐시된 리소스를 그대로 사용해도 될지, 아니면 리소스를 새로 다운로드해야 할지 서버에 확인한다.

서비스의 리소스가 변경되지 않아 브라우저에 캐시되어 있는 리소스를 그대로 사용해도 무방하다면 서버에서는 변경되지 않았다는 상태 코드를 응답으로 보낸다.

그리고 브라우저는 캐시를 그대로 사용한다.

이때 캐시를 사용해도 되는지 확인하기 위해 네트워크 요청을 보내고 응답을 받기 때문에 리소스의 Size가 따로 기록된다.

 

적절한 캐시 유효 시간

앞서 적용한 방식은 모든 리소스에 동일하게 캐시 설정이 적용돼서 효율적이지 않을 것 같다.

리소스마다 사용이나 변경 빈도가 달라서 캐시의 유효 시간도 달라져야 하기 때문이다.

 

일반적으로 HTML 파일에는 no-cache 설정을 적용하는데 이는 항상 최신 버전의 웹 서비스를 제공하기 위해서이다.

HTML이 캐시되면 캐시된 HTML에서 이전 버전의 JS나 CSS를 로드해서 캐시 시간 동안 최신 버전의 웹 서비스를 제공하지 못한다.

그래서, 항상 최신 버전의 리소스를 제공하면서도 변경 사항이 없을 때만 캐시를 사용하는 no-cache 설정을 적용한다.

 

JS와 CSS의 경우 파일명에 해시를 함께 가지고 있어, 코드가 변경되면 해시도 변경되기 때문에 완전히 다른 파일이 된다.

HTML만 최신 상태라면 JS나 CSS 파일은 당연히 최신 리소스를 로드할 것이고, 이미지도 그렇다.

 

다음과 같이 코드를 수정하자.

HTML 파일에는 no-cache를, JS와 CSS, WebP 파일에는 캐시를 적용하고 있다.

그 외 파일들은 no-store로 캐시를 적용하지 않는다.

const header = {
  setHeaders: (res, path) => {
    if (path.endsWith(".html")) {
      res.setHeader("Cache-Control", "no-cache");
    } else if (
      path.endsWith(".js") ||
      path.endsWith(".css") ||
      path.endsWith(".webp")
    ) {
      res.setHeader("Cache-Control", "public, max-age=31536000");
    } else {
      res.setHeader("Cache-Control", "no-store");
    }
  },
};

 

불필요한 CSS 제거

불필요한 CSS를 제거하기 전에 Lighthouse로 검사해보자.

그리고 Oppotunities 섹션을 보면 Reduce unused CSS 항목이 있다.

세부 내용을 살펴보면 main.chunk.css가 620KiB인데, 사용하지 않는 코드를 제거하면 616KiB를 줄일 수 있다고 한다.

 

조금 더 자세히 살펴보기 위해서 Coverage 패널을 한 번 살펴보자.

Coverage 패널은 페이지에서 사용하는 JS 및 CSS 리소스에서 실제로 실행하는 코드가 얼마나 되는지 알려주고 그 비율을 표시해준다.

JS의 경우, if문 같은 조건이 걸려 있어 분기되는 코드가 많아서 JS 코드의 커버리지는 어느 정도 감안해야 한다.

하지만 CSS는 JS와 다르게 별다른 분기가 있지 않아서 리소스를 많이 사용하지 않도록 최적화해야 한다.

 

PurgeCSS

이번에 사용하지 않는 CSS 코드를 제거하기 위해서 PurgeCSS라는 툴을 사용해볼 것이다.

PurgeCSS는 파일에 들어 있는 모든 키워드를 추출해서 해당 키워드를 이름으로 갖는 CSS 클래스만 보존하고 나머지 매칭되지 않은 클래스는 모두 지우는 방식으로 CSS 파일을 최적화한다.

 

npm을 이용해서 툴을 설치해준 후 키워드를 추출하고자 하는 파일과 불필요한 클래스를 제거할 CSS 파일을 지정하면 된다.

npm install --save-dev purgecss

purgecss -css ./build/static/css/*.css --output ./build/static/css/--content .build/index.html .build/static.js/*.js

불필요한 클래스를 제거할 CSS(—css)로 빌드된 CSS 파일을 선택하고 아웃풋으로 동일한 위치를 지정해서 기존 CSS 파일을 덮어쓰도록 했다.

키워드로 추출할 파일(—content)로는 빌드된 HTML과 JS 파일을 넣어주었는데, 빌드된 HTML과 JS 파일의 텍스트 키워드를 모두 추출해서 빌드된 CSS 파일의 클래스와 비교하고 최적화하게 된다.

 

이후 npm run purge를 실행하면 된다.