HTTP cache는 같은 리소스를 매번 새로 다운로드하지 않기 위해 응답을 저장하고 재사용하는 방식입니다. Cache-Control: max-age가 남아 있으면 브라우저는 서버에 요청하지 않고 캐시를 사용할 수 있습니다.
만료된 뒤에는 ETag나 Last-Modified를 보내 서버에 바뀌었는지 확인하고, 바뀌지 않았다면 304 Not Modified로 본문 없이 재사용할 수 있습니다.
꼬리질문
조금 더 깊게 물어본다면
답변 뒤에 이어질 수 있는 질문들을 하나씩 열어볼 수 있어요.
no-cache는 캐시하지 말라는 뜻인가요?
이름 때문에 헷갈리지만, no-cache는 저장하지 말라는 뜻이 아닙니다. 저장은 할 수 있지만 사용하기 전에 서버에 재검증하라는 뜻입니다. 정말 저장하지 않게 하려면 no-store를 사용해야 합니다.
ETag와 Last-Modified는 어떤 차이가 있나요?
Last-Modified는 수정 시간을 기준으로 하고, ETag는 리소스 버전을 나타내는 식별자에 가깝습니다. 시간 단위로는 잡기 어려운 변경이나 서버가 더 정교하게 판단해야 하는 경우에는 ETag가 더 유연합니다.
304 응답은 성능상 어떤 이점이 있나요?
본문을 다시 내려받지 않아도 되기 때문에 network payload를 줄일 수 있습니다. 다만 서버 왕복 자체는 발생합니다. 그래서 아예 fresh cache로 서버 요청을 생략하는 것과, 재검증 후 304를 받는 것은 성능 특성이 다릅니다.
index.html과 hashed asset은 캐시 전략을 다르게 가져가야 하나요?
보통 다르게 가져갑니다. main.abc123.js처럼 hash가 붙은 asset은 긴 cache를 줄 수 있습니다. 파일 내용이 바뀌면 URL도 바뀌기 때문입니다. 반면 index.html은 새 asset URL을 알려주는 진입점이라 너무 오래 캐시하면 배포 후에도 예전 번들을 가리킬 수 있습니다.
강력한 캐시가 오히려 장애를 만들 수 있는 경우는 언제인가요?
잘못된 파일에 긴 cache를 주면 사용자가 오래된 리소스를 계속 사용할 수 있습니다. 특히 HTML이나 설정 JSON처럼 최신 상태가 중요한 파일을 오래 캐시하면 새 배포가 반영되지 않는 문제가 생길 수 있습니다.
부가 설명
캐시는 크게 두 단계로 생각하면 편합니다. 먼저 “아직 신선한가?”를 봅니다. max-age 안에 있으면 fresh cache라서 바로 재사용할 수 있습니다.
기간이 지나 stale 상태가 되면 서버에 확인합니다. 이때 If-None-Match나 If-Modified-Since 같은 조건부 요청을 보내고, 서버가 바뀌지 않았다고 판단하면 304를 내려줍니다. 그러면 브라우저는 기존 캐시 본문을 다시 씁니다.
한 줄 정리
HTTP cache는 “바로 재사용할 수 있는 fresh cache”와 “서버에 바뀌었는지 물어보고 재사용하는 validation”을 나눠서 설명하면 됩니다.