HTTP 캐싱 완벽 가이드: Cache-Control, ETag로 네트워크 비용 줄이기

크리스가 운영하는 웹 서비스가 대박이 났다. 트래픽이 폭주하자 서버 비용 명세서도 함께 폭발했다.
분석해보니 사용자가 새로고침을 할 때마다 5MB짜리 배경 이미지와 무거운 자바스크립트 파일을 매번 다시 다운로드하고 있었다.
"어차피 똑같은 파일인데, 브라우저가 좀 알아서 저장해두면 안 되나?"
이런 비효율을 막기 위해 존재하는 기술이 바로 HTTP 캐싱(Caching)이다. 브라우저에게 "이 파일은 내 컴퓨터에 저장해두고 재사용해"라고 알려주는 규칙, 즉 HTTP 헤더를 제대로 설정하는 것만으로도 서버 비용을 획기적으로 줄이고 로딩 속도를 높일 수 있다.
1. 캐시의 유효기간: Cache-Control
가장 기본이 되는 헤더는 Cache-Control이다. 브라우저에게 이 자원을 얼마나 오래 기억할지 명령한다.
// server-response.ts
// 1. "1년 동안 나를 찾지 마. 저장된 거 써." (변하지 않는 정적 파일)
res.setHeader('Cache-Control', 'max-age=31536000');
// 2. "저장은 하되, 쓸 때마다 나한테 물어봐." (HTML 등 자주 바뀌는 파일)
res.setHeader('Cache-Control', 'no-cache');
// 3. "절대 저장하지 마. 보안 문서야." (개인정보 등)
res.setHeader('Cache-Control', 'no-store');여기서 가장 많이 오해하는 것이 no-cache다.
이름만 보면 "캐시 하지 마" 같지만, 실제 뜻은 "캐시는 하되, 사용할 때마다 서버에 유효한지 확인(Validation)해라"이다. 아예 저장조차 하지 말아야 한다면 no-store를 써야 한다.
2. 유효성 검사: ETag와 304 Not Modified
max-age가 만료되었다고 해서 무조건 파일을 다시 다운로드하는 것은 낭비다. 파일 내용이 여전히 똑같을 수도 있기 때문이다.
이때 사용하는 것이 ETag다.
ETag: 파일의 지문(Fingerprint)
서버는 파일을 보낼 때 내용물을 해싱한 고유 값을 ETag 헤더에 붙여서 보낸다.
이때 서버는 파일 본문(Body)을 보내지 않고 헤더만 보낸다. 데이터 전송량이 확 줄어드는 마법 같은 순간이다.
3. 실전 캐싱 전략 (Best Practice)
그렇다면 리액트 프로젝트에서는 어떤 전략을 써야 할까? 크리스의 팀이 채택한 전략은 다음과 같다.
전략 1: HTML은 no-cache
HTML 파일은 진입점이다. 여기서 JS, CSS 파일의 경로를 불러온다. HTML이 옛날 버전으로 캐시 되어 있으면, 사용자는 영원히 최신 배포를 못 볼 수도 있다.
// nginx.conf or server.ts
// index.html 요청 시
res.setHeader('Cache-Control', 'no-cache');
// "항상 서버에 물어보고 가져가!"전략 2: JS/CSS/Image는 max-age 길게 + 파일명 해싱
빌드된 JS 파일(main.a1b2c.js)은 내용이 바뀌면 파일명(해시)도 바뀐다. 즉, 파일 이름이 같다면 내용은 100% 같다는 뜻이다.
그러므로 브라우저에게 "이건 절대 안 바뀌니 1년 동안 저장해"라고 해도 안전하다.
// static-files.ts
// .js, .css, .png 요청 시
res.setHeader('Cache-Control', 'max-age=31536000, immutable');만약 배포를 새로 하면?
HTML 파일(no-cache)이 갱신되어 새로운 파일명(main.d4e5f.js)을 가리킬 것이고, 브라우저는 새로운 파일을 다운로드한다. 이것이 완벽한 캐시 무효화 전략이다.
4. 캐싱 동작 확인하기
개발자 도구(Network 탭)를 열어보면 캐시가 잘 동작하는지 확인할 수 있다.
핵심 정리
이것으로 Part 1. 언어와 웹의 본질 파트가 끝났다.
기초 체력을 다졌으니, 이제 본격적으로 리액트의 심장을 열어볼 차례다.
다음 글 예고:
리액트 컴포넌트는 어떻게 태어나고, 변하고, 사라질까?
"리액트 렌더링의 2단계: Render Phase와 Commit Phase의 차이점" 편에서 Part 2가 시작된다.