HTTP 캐시로 불필요한 네트워크 요청 방지

네트워크를 통해 리소스를 가져오는 작업은 느리고 비용이 많이 듭니다.

  • 응답이 많으면 브라우저와 서버 간에 많은 왕복이 필요합니다.
  • 중요한 리소스를 모두 다운로드할 때까지는 페이지가 로드되지 않습니다.
  • 제한된 모바일 데이터 요금제로 사이트에 액세스하는 경우 불필요한 네트워크 요청은 모두 비용 낭비입니다.

불필요한 네트워크 요청을 방지하려면 어떻게 해야 하나요? 브라우저의 HTTP 캐시는 첫 번째 방어선입니다. 반드시 가장 강력하고 유연한 접근 방식은 아니며, 캐시된 응답의 전체 기간에 걸쳐 제한된 제어가 있지만 효과적이고 모든 브라우저에서 지원되고 많은 작업을 필요로 하지 않습니다.

이 가이드에서는 효과적인 HTTP 캐싱 구현의 기본 사항을 보여줍니다.

브라우저 호환성

실제로 HTTP Cache라는 단일 API는 없습니다. 웹 플랫폼 API 컬렉션의 일반적인 이름입니다. 이러한 API는 모든 브라우저에서 지원됩니다.

Cache-Control

브라우저 지원

  • 12

소스

ETag

브라우저 지원

  • 12

소스

Last-Modified

브라우저 지원

  • 12

소스

HTTP 캐시의 작동 방식

브라우저가 수행하는 모든 HTTP 요청은 먼저 요청을 처리하는 데 사용할 수 있는 유효한 캐시된 응답이 있는지 여부를 확인하기 위해 브라우저 캐시로 라우팅됩니다. 일치하는 항목이 있으면 캐시에서 응답을 읽어 전송으로 발생하는 네트워크 지연 시간과 데이터 비용을 모두 제거합니다.

HTTP 캐시의 동작은 요청 헤더응답 헤더의 조합에 의해 제어됩니다. 이상적인 시나리오에서는 웹 애플리케이션의 코드 (요청 헤더 결정)와 웹 서버 구성 (응답 헤더 결정)을 모두 제어할 수 있습니다.

보다 자세한 개념은 MDN의 HTTP 캐싱 문서를 참조하세요.

요청 헤더: 기본값 유지 (일반적으로)

웹 앱의 발신 요청에 포함되어야 하는 중요한 헤더가 여러 개 있지만 브라우저가 요청을 할 때 거의 항상 개발자 대신 헤더를 설정합니다. If-None-MatchIf-Modified-Since와 같이 최신 상태 확인에 영향을 미치는 요청 헤더는 브라우저가 HTTP 캐시의 현재 값을 이해하면 표시됩니다.

이는 좋은 소식입니다. HTML에 <img src="my-image.png">과 같은 태그를 계속 포함할 수 있으며, 브라우저가 추가 작업 없이 HTTP 캐싱을 자동으로 처리합니다.

응답 헤더: 웹 서버 구성

HTTP 캐싱 설정에서 가장 중요한 부분은 웹 서버가 각 발신 응답에 추가하는 헤더입니다. 다음 헤더는 모두 효과적인 캐싱 동작에 고려됩니다.

  • Cache-Control. 서버는 Cache-Control 지시어를 반환하여 브라우저 및 기타 중간 캐시가 개별 응답을 캐시하는 방법과 기간을 지정할 수 있습니다.
  • ETag. 브라우저에서 만료된 캐시된 응답을 발견하면 작은 토큰 (일반적으로 파일 콘텐츠의 해시)을 서버로 전송하여 파일이 변경되었는지 확인할 수 있습니다. 서버가 동일한 토큰을 반환하면 파일이 동일한 것이므로 다시 다운로드할 필요가 없습니다.
  • Last-Modified. 이 헤더는 ETag와 동일한 용도로 사용되지만 ETag의 콘텐츠 기반 전략과 달리 시간 기반 전략을 사용하여 리소스가 변경되었는지 확인합니다.

일부 웹 서버는 기본적으로 이러한 헤더를 설정하기 위한 지원 기능이 내장되어 있으며, 다른 웹 서버는 명시적으로 구성하지 않는 한 헤더를 완전히 사용하지 않습니다. 헤더를 구성하는 방법의 구체적인 세부정보는 사용하는 웹 서버에 따라 크게 다르며 가장 정확한 세부정보를 얻으려면 서버의 설명서를 참조하세요.

쉽게 검색할 수 있도록 다음 안내에 따라 많이 사용되는 웹 서버를 구성합니다.

Cache-Control 응답 헤더를 생략해도 HTTP 캐싱이 사용 중지되지 않습니다. 대신 브라우저는 특정 콘텐츠 유형에 어떤 유형의 캐싱 동작이 가장 적합한지 효과적으로 추측합니다. 이러한 제품보다 더 많은 제어 기능을 원할 수 있으므로 시간을 들여 응답 헤더를 구성하세요.

어떤 응답 헤더 값을 사용해야 할까요?

웹 서버의 응답 헤더를 구성할 때 고려해야 할 두 가지 중요한 시나리오가 있습니다.

버전이 지정된 URL의 오래 지속되는 캐싱

버전이 지정된 URL이 캐싱 전략에 도움이 되는 방식
버전이 지정된 URL을 사용하면 캐시된 응답을 더 쉽게 무효화할 수 있으므로 좋은 방법입니다.

서버가 브라우저에 CSS 파일을 1년 동안 캐시하도록 지시했지만 (Cache-Control: max-age=31536000) 디자이너가 즉시 배포해야 하는 긴급 업데이트를 방금 만들었다고 가정해 보겠습니다. '오래된' 캐시된 파일 사본을 업데이트하도록 브라우저에 어떻게 알리나요? 그렇게 할 수 없습니다. 적어도 리소스의 URL을 변경하지 않는다면 말이죠.

브라우저에서 응답을 캐시한 후 캐시된 버전은 max-age 또는 expires에 의해 결정된 대로 더 이상 최신 상태가 아닐 때까지 또는 다른 이유로(예: 사용자가 브라우저 캐시를 지우는 경우) 캐시에서 삭제될 때까지 사용됩니다. 따라서 페이지가 생성될 때 사용자마다 다른 버전의 파일을 사용하게 될 수 있습니다. 리소스를 방금 가져온 사용자는 새 버전을 사용하는 반면, 이전의 (하지만 여전히 유효한) 사본을 캐시한 사용자는 이전 버전의 응답을 사용합니다.

클라이언트 측 캐싱과 빠른 업데이트, 이 두 가지의 장점을 모두 활용하려면 어떻게 해야 할까요? 리소스의 URL을 변경하고 콘텐츠가 변경될 때마다 사용자가 새 응답을 다운로드하도록 합니다. 일반적으로 파일 이름에 파일의 디지털 지문이나 버전 번호를 삽입하여 이를 수행합니다(예: style.x234dff.css).

'지문' 또는 버전 관리 정보가 포함되어 있고 콘텐츠가 절대 변경되지 않는 URL 요청에 응답할 때 Cache-Control: max-age=31536000를 응답에 추가하세요.

이 값을 설정하면 향후 1년 동안 (31,536,000초, 최대 지원 값) 동일한 URL을 로드해야 할 때 웹 서버에 네트워크 요청을 전혀 하지 않고도 즉시 HTTP 캐시의 값을 사용할 수 있음을 브라우저에 알립니다. 훌륭합니다. 네트워크 회피 덕분에 안정성과 속도를 즉시 얻게 되었습니다!

Webpack과 같은 빌드 도구를 사용하면 해시 디지털 지문을 애셋 URL에 할당하는 프로세스를 자동화할 수 있습니다.

버전이 지정되지 않은 URL의 서버 재검증

안타깝게도 로드하는 모든 URL에 버전이 관리되는 것은 아닙니다. 웹 앱을 배포하기 전에 빌드 단계를 포함하지 못하기 때문에 애셋 URL에 해시를 추가할 수 없을 수도 있습니다. 그리고 모든 웹 애플리케이션에는 HTML 파일이 필요하지 않습니다. 이러한 파일에는 거의 버전 정보가 포함되지 않습니다. 방문할 URL이 https://example.com/index.34def12.html임을 기억해야 하는 경우 웹 앱을 사용하는 데에는 전혀 문제가 없습니다. 그렇다면 이러한 URL에는 어떻게 해야 할까요?

패배를 인정해야 하는 시나리오입니다 HTTP 캐싱만으로는 네트워크를 완전히 회피할 만큼 강력하지 않습니다. 걱정하지 마세요. 서비스 워커에 대해 곧 알아볼 수 있습니다. 서비스 워커는 개발자에게 유리한 전투를 하는 데 필요한 지원을 제공합니다. 그러나 몇 가지 단계를 통해 네트워크 요청을 최대한 빠르고 효율적으로 처리할 수 있습니다.

다음 Cache-Control 값을 사용하면 버전이 지정되지 않은 URL이 캐시되는 위치와 방법을 세부적으로 조정할 수 있습니다.

  • no-cache. 이 메서드는 캐시된 버전의 URL을 사용하기 전에 매번 서버에서 재확인해야 한다고 브라우저에 지시합니다.
  • no-store. 이렇게 하면 브라우저 및 다른 중간 캐시 (예: CDN)에 어떤 버전의 파일도 저장하지 않도록 지시합니다.
  • private. 브라우저는 파일을 캐시할 수 있지만 중간 캐시는 캐시할 수 없습니다.
  • public. 응답은 모든 캐시에 저장될 수 있습니다.

사용할 Cache-Control 값을 결정하는 프로세스를 시각화하려면 부록: Cache-Control 플로우 차트를 참고하세요. Cache-Control는 쉼표로 구분된 명령어 목록도 허용할 수 있습니다. 부록: Cache-Control 예시를 참고하세요.

ETag 또는 Last-Modified를 설정하는 것도 도움이 됩니다. 응답 헤더에서 언급했듯이 ETagLast-Modified는 동일한 목적으로 사용됩니다. 즉, 브라우저가 만료된 캐시된 파일을 다시 다운로드해야 하는지 여부를 결정합니다. 더 정확하므로 ETag를 사용하는 것이 좋습니다.

처음 가져온 후 120초가 지났고 브라우저가 동일한 리소스에 대해 새 요청을 시작했다고 가정해 보겠습니다. 먼저, 브라우저는 HTTP 캐시를 확인하고 이전 응답을 찾습니다. 안타깝지만 응답은 이제 만료되었으므로 브라우저에서 이전 응답을 사용할 수 없습니다. 이 시점에서 브라우저는 새 요청을 발송하고 새로운 전체 응답을 가져올 수 있습니다. 그러나 리소스가 변경되지 않은 경우 이미 캐시에 있는 동일한 정보를 다운로드할 이유가 없으므로 비효율적입니다.

이것이 바로 ETag 헤더에 지정된 유효성 검사 토큰이 해결하도록 설계된 문제입니다. 서버는 일반적으로 파일 콘텐츠의 해시 또는 기타 디지털 지문인 임의의 토큰을 생성하고 반환합니다. 브라우저는 지문이 생성되는 방식을 알 필요가 없으며, 다음 요청 시 지문을 서버에 전송하기만 하면 됩니다. 디지털 지문이 여전히 동일하다면 리소스가 변경되지 않았으며 브라우저에서 다운로드를 건너뛸 수 있습니다.

ETag 또는 Last-Modified를 설정하면 요청 헤더에 언급된 If-Modified-Since 또는 If-None-Match 요청 헤더를 트리거하여 유효성 재검사 요청을 훨씬 더 효율적으로 만들 수 있습니다.

제대로 구성된 웹 서버가 들어오는 요청 헤더를 확인하면 브라우저가 이미 HTTP 캐시에 가지고 있는 리소스 버전이 웹 서버의 최신 버전과 일치하는지 확인할 수 있습니다. 일치하는 항목이 있으면 서버는 304 Not Modified HTTP 응답으로 응답할 수 있습니다. 이 응답은 'Hey, 계속 사용해 주세요!'와 같습니다. 이러한 유형의 응답을 보낼 때는 전송할 데이터가 거의 없으므로, 일반적으로는 요청 중인 실제 리소스의 사본을 실제로 다시 보내야 하는 것보다 훨씬 빠릅니다.

리소스를 요청하는 클라이언트와 304 헤더로 응답하는 서버를 시각화한 것입니다.
브라우저가 서버에 /file를 요청하고 서버에 있는 파일의 ETag가 브라우저의 If-None-Match 값과 일치하지 않는 경우에만 전체 파일을 반환하도록 서버에 지시하는 If-None-Match 헤더를 포함합니다. 여기서는 두 값이 일치했으므로 서버는 파일을 캐시해야 하는 기간에 관한 지침 (Cache-Control: max-age=120)과 함께 304 Not Modified 응답을 반환합니다.

요약

HTTP 캐시는 불필요한 네트워크 요청을 줄여주므로 로드 성능을 향상시키는 효과적인 방법입니다. 모든 브라우저에서 지원되며 설정하는 데 많은 작업이 필요하지 않습니다.

처음에는 다음과 같은 Cache-Control 구성을 사용하는 것이 좋습니다.

  • Cache-Control: no-cache: 사용할 때마다 서버에서 재검증해야 하는 리소스
  • 캐시해서는 안 되는 리소스의 경우 Cache-Control: no-store
  • Cache-Control: max-age=31536000: 버전이 지정된 리소스의 경우

ETag 또는 Last-Modified 헤더를 사용하면 만료된 캐시 리소스를 더 효율적으로 재검증할 수 있습니다.

자세히 알아보기

Cache-Control 헤더 사용의 기본사항보다 더 자세히 알아보려면 Jake Archibald의 캐싱 권장사항 및 max-age 팁 가이드를 참조하세요.

재방문 방문자의 캐시 사용을 최적화하는 방법에 대한 안내는 캐시 사용하기를 참고하세요.

부록: 추가 도움말

시간이 더 있다면 다음과 같이 HTTP 캐시 사용을 최적화할 수 있습니다.

  • 일관된 URL을 사용합니다. 여러 URL로 동일한 콘텐츠를 제공하는 경우 해당 콘텐츠를 여러 번 가져와 저장합니다.
  • 앱 제거를 최소화합니다. 리소스의 일부 (예: CSS 파일)는 자주 업데이트되지만 나머지 파일 (예: 라이브러리 코드)은 자주 업데이트되지 않는 경우 자주 업데이트되는 코드를 별도의 파일로 분할하고 자주 업데이트되는 코드에는 짧은 기간 캐싱 전략을 사용하고 자주 변경되지 않는 코드에 대해서는 긴 캐싱 기간 전략을 사용하는 것이 좋습니다.
  • Cache-Control 정책에서 어느 정도의 비활성이 허용되면 새로운 stale-while-revalidate 지시문을 확인하세요.

부록: Cache-Control 플로우 차트

플로차트
Cache-Control 헤더 설정을 위한 결정 프로세스입니다.

부록: Cache-Control 예시

Cache-Control 설명
max-age=86400 응답은 최대 1일 (60초 x 60분 x 24시간) 동안 브라우저 및 중간 캐시에 의해 캐시될 수 있습니다.
private, max-age=600 응답은 최대 10분(60초 x 10분) 동안 브라우저에서 캐시할 수 있지만 중간 캐시는 캐시할 수 없습니다.
public, max-age=31536000 응답은 1년 동안 모든 캐시에 저장될 수 있습니다.
no-store 응답은 캐시될 수 없으며 모든 요청에서 전체로 가져와야 합니다.