--- id: web-http-cache-headers title: HTTP Cache 헤더 실전 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [web, http, caching, performance, vibe-coding] tech_stack: { language: "Any backend / CDN", applicable_to: ["Web", "API"] } applied_in: [] aliases: [Cache-Control, ETag, immutable, stale-while-revalidate] --- # HTTP Cache 헤더 실전 > 캐시 정책 4종 = (1) 정적 자산은 immutable + long max-age, (2) HTML 은 short max-age + revalidate, (3) API GET 은 ETag + private, (4) 절대 캐시 금지는 no-store. 한 응답에 여러 정책 섞으면 사고. ## 📖 핵심 개념 - `Cache-Control: max-age=N` — N초 동안 fresh - `Cache-Control: s-maxage=N` — CDN 만 (브라우저는 무시) - `Cache-Control: immutable` — 만료 전 절대 revalidate 안 함 - `Cache-Control: no-store` — 어디에도 저장 X (보안 응답) - `Cache-Control: no-cache` — 저장은 OK, 매번 revalidate 필요 - `ETag` + `If-None-Match` — 변경 안 됐으면 304 - `stale-while-revalidate=N` — stale 응답 즉시 + 백그라운드 재검증 ## 💻 코드 패턴 ### 1. 정적 자산 (해시 파일명) ```http GET /assets/app.a1b2c3d4.js Cache-Control: public, max-age=31536000, immutable ``` 파일명에 hash 가 있으면 영원히 캐시. 새 deploy = 새 hash = 자동 cache miss. ### 2. HTML ```http GET /index.html Cache-Control: public, max-age=0, must-revalidate ETag: "v123" ``` 매 요청 revalidate. 변경 없으면 304. 새 빌드 즉시 노출. ### 3. API GET (사용자별) ```http GET /api/me Cache-Control: private, max-age=60 ETag: "user-1-v5" ``` private = CDN 캐시 금지. 사용자 브라우저만. ### 4. stale-while-revalidate (popular) ```http GET /api/posts Cache-Control: public, max-age=60, stale-while-revalidate=300 ``` 60초까지 fresh. 60~360초 사이 = stale 즉시 반환 + 백그라운드 fetch. 사용자는 latency 0. ### 5. 절대 캐시 금지 ```http GET /api/transfer Cache-Control: no-store ``` 민감 응답 (잔액, 비밀, 인증 토큰). 프록시도 저장 안 함. ## 🤔 의사결정 기준 | 자원 | 정책 | |---|---| | JS/CSS (hashed) | `public, max-age=1y, immutable` | | 이미지 (hashed) | `public, max-age=1y, immutable` | | index.html | `public, max-age=0, must-revalidate` | | 공개 API (popular) | `public, max-age=60, stale-while-revalidate=300` | | 사용자별 API | `private, max-age=60` + ETag | | 결제 / 잔액 / 토큰 | `no-store` | | 사용자 입력 후 즉시 일관성 | `no-cache` 또는 짧은 max-age | ## ❌ 안티패턴 - **HTML 에 `max-age: 31536000`**: 새 deploy 가 사용자에게 영원히 안 보임. - **API 에 `public` + 사용자별 데이터**: CDN 이 다른 사용자에게 줘 보안 사고. private 명시. - **ETag 없이 max-age 만**: 만료 후 재다운로드. ETag 있으면 304 로 bandwidth 절약. - **`no-cache` 와 `max-age` 의미 혼동**: no-cache = 매번 revalidate, no-store = 저장 금지. - **CDN 과 브라우저 정책 따로 못 둠**: `s-maxage=3600, max-age=60` → CDN 1시간, 브라우저 1분. - **POST 응답에 캐시 헤더**: 일부 프록시가 저장. 의도면 OK 지만 실수면 위험. - **Vary 헤더 잊음**: `Accept-Encoding`, `Authorization` 같은 헤더로 분리 캐시 안 하면 다른 사용자에게 잘못 응답. ## 🤖 LLM 활용 힌트 - 정적 자산 vs HTML vs API 구분 명시. - 사용자별 응답이면 `private` + Vary 검토. ## 🔗 관련 문서 - [[Web_CORS_Practical_Guide]] - [[Optimistic_Concurrency_Control]]