"캐시는 나중에 덧붙이는 장식이 아니야. 서버가 헤더로 정확히 설명하면 CDN, 브라우저, 역방향 프록시가 함께 움직이는 프로토콜이지. 규칙이 틀리면 오래된 데이터를 내보내거나, 한 사용자의 인증 응답을 다른 사용자에게 건넬 수도 있어."
캐시를 움직이는 헤더 다섯 가지
Cache-Control— 현대 HTTP 캐시 정책의 중심이야. 저장 허용 범위, 신선도, 재검증 규칙을 지시어로 표현해.ETag— 내용을 직접 해석하지 않고도 버전을 비교할 수 있게 해 주는 검증자야. 조건부 재검증에 사용해.Last-Modified— 수정 시각을 나타내는 검증자야.If-Modified-Since와 함께 사용해.Expires— 절대 시각으로 만료 시점을 나타내는 오래된 방식이야. Cache-Control과 함께 있으면 Cache-Control의 신선도 규칙이 우선해.Vary— 어떤 요청 헤더의 값에 따라 표현이 달라지는지 알려 줘. 캐시는 그 값을 캐시 키 선택에 반영해.
자주 쓰는 Cache-Control 지시어
Cache-Control은 여러 지시어를 쉼표로 조합해. 저장 가능 여부와 신선도는 서로 다른 축이므로 조합의 의미를 정확히 읽어야 해.
max-age=N— 응답이 생성된 뒤 N초 동안 신선하다는 뜻이야. 그동안 캐시는 재검증 없이 응답을 재사용할 수 있어.s-maxage=N— CDN이나 프록시 같은 공유 캐시에만 적용되는 신선도 수명이야. 공유 캐시에서는 max-age를 덮어쓰므로, 브라우저 TTL은 짧게 두고 CDN TTL은 길게 둘 수 있어.public— 공유 캐시를 포함한 캐시가 응답을 저장할 수 있음을 명시해. Authorization이 있는 요청의 응답을 공유 캐시에 저장하려면 특히 신중한 키 설계와 함께 명시적 허용이 필요해.- private — 응답을 개인 캐시에만 저장하도록 제한해. 브라우저 같은 개인 캐시는 저장할 수 있지만 CDN과 공유 프록시는 저장하면 안 돼.
no-cache— 저장은 허용하지만, 저장된 응답을 다시 사용하기 전에 원본 서버에 재검증하라고 요구해. 이름과 달리 “저장하지 마”라는 뜻은 아니야.no-store— 어떤 캐시에도 응답을 저장하지 말라는 지시야. 민감한 데이터나 결제 정보처럼 남아서는 안 되는 응답에 써.must-revalidate— 응답이 오래되면 원본 서버에 재검증해야 하며, 연결에 실패했다는 이유로 오래된 응답을 임의로 내주면 안 된다는 뜻이야.stale-while-revalidate=N— 응답이 오래된 뒤에도 N초 동안은 저장된 응답을 내주면서 백그라운드에서 재검증할 수 있게 해. 지원하는 캐시에서는 대기 시간을 줄이는 데 효과적이야.immutable— 응답이 신선한 동안 내용이 바뀌지 않는다고 알려 불필요한 재검증을 막아. 지문이 붙은 자산 URL(app.abc123.css)처럼 내용이 바뀌면 URL도 바뀌는 자원에 써.
가장 자주 쓰는 세 가지 조합
1. 버전이 붙은 정적 자산(파일 이름에 해시가 있는 CSS, JS, 이미지):
Cache-Control: public, max-age=31536000, immutable
1년 동안 신선하게 재사용해. 내용이 바뀌면 파일 이름과 URL도 바뀌므로, 오래 캐시해도 안전해.
2. 자주 변하는 API 응답(목록, 사용자 관리 화면):
Cache-Control: private, max-age=60, must-revalidate
ETag: "v17-abc"
Vary: Accept-Encoding, Authorization
브라우저의 개인 캐시에서 60초 동안 재사용한 뒤 조건부 요청으로 재검증해. private이 공유 캐시 저장을 막고, ETag는 내용이 그대로일 때 304 응답을 가능하게 해. Vary는 요청 헤더에 따라 표현을 올바르게 구분하도록 도와.
3. 개인 정보나 민감한 데이터(인증 응답, 결제 양식):
Cache-Control: no-store
브라우저와 공유 캐시 모두에 저장을 금지해. 토큰이나 세션 정보처럼 저장 흔적 자체를 피해야 하는 응답에 가장 분명한 선택이야.
명시하지 않은 기본값은 조용히 어긋날 수 있어. Cache-Control이 없는 응답은 상태 코드와 다른 헤더에 따라 캐시가 휴리스틱 신선도를 계산할 수도 있어. 엔드포인트마다 의도를 명시해. 저장 자체를 막으려면
no-store를 쓰고, 개인 캐시에 저장하되 즉시 오래된 것으로 취급하려면 private, max-age=0처럼 목적에 맞게 구분해야 해. 둘은 같은 정책이 아니야.cwkPippa의 캐시 헤더
cwkPippa API 엔드포인트는 사용자별 데이터가 많아 기본적으로
Cache-Control: no-store를 사용해. 해시가 붙은 Vite 정적 자산에는 Cache-Control: public, max-age=31536000, immutable을 적용하고, 배포 진입점인 index.html에는 no-cache를 두어 재사용 전에 새 배포 여부를 확인하게 해. cwk-site 블로그 글의 public, s-maxage=300, stale-while-revalidate=86400은 CDN의 짧은 신선도와 오래된 응답의 제한적 재사용을 결합한 정책이야. 중요한 건 유행하는 조합을 베끼는 게 아니라 데이터의 민감도와 변경 주기에 맞추는 거야.