한 번 받은 리소스(HTML·CSS·JS·이미지)를 브라우저가 저장해 두었다가 다시 쓰는 기능. 서버가 응답 헤더로 얼마나, 어떻게 캐시할지 알려 주고 브라우저가 그대로 따른다.
두 가지 방식
- 신선한 동안 그냥 쓰기:
Cache-Control: max-age=31536000이면 그 시간 동안 서버에 묻지도 않고 캐시를 쓴다 - 확인하고 쓰기: 유효 기간이 지났거나
no-cache면 서버에 "바뀌었나요?"라고 묻는다. 안 바뀌었으면 서버는 본문 없이304 Not Modified만 보낸다 → ETag와 조건부 요청
flowchart TD
Q[리소스 요청] --> C{캐시에 있고 신선한가?}
C -- 예 --> U[서버에 묻지 않고 캐시 사용]
C -- "아니오 (만료 또는 no-cache)" --> V["서버에 재검증 (If-None-Match 등)"]
V --> S{바뀌었나?}
S -- 아니오 --> N["304 Not Modified, 캐시 사용"]
S -- 예 --> F[200 + 새 본문, 캐시 갱신]
자주 쓰는 지시어(Cache-Control):
no-cache: 저장은 하되 쓸 때마다 확인. "캐시 안 함"이 아니다no-store: 아예 저장하지 않음(민감한 응답)private/public: 브라우저만 / CDN(Content Delivery Network) 같은 공유 캐시도immutable: 유효 기간 동안 절대 안 바뀜
실무 패턴
- 빌드 결과 JS·CSS는 파일 이름에 내용 해시를 넣고(
app.3f9a1c.js, 캐시 버스팅(Cache Busting)) 1년짜리max-age와immutable을 준다. 내용이 바뀌면 이름이 바뀌므로 캐시 무효화가 필요 없다 - HTML은
no-cache로 두어 항상 최신 파일 이름을 가리키게 한다 - 배포 직후 열려 있던 탭이 옛 HTML 기준으로 이미 사라진 청크를 요청하면
Failed to fetch dynamically imported module에러가 난다. 옛 청크를 한동안 남겨 두거나, 에러를 잡아 새로고침한다 → 코드 스플리팅과 동적 임포트
개발 중 옛 파일이 남을 때
개발 서버는 모듈을 자주 새로 만드는데 브라우저가 이전 버전을 캐시에서 꺼내 쓰면 위와 같은 동적 import 실패나 이상 동작이 난다. 시크릿 창에서는 되는데 일반 창에서만 안 된다면 캐시를 의심한다.
- 하드 리프레시(macOS
Cmd+Shift+R)로 캐시를 무시하고 다시 받는다 - DevTools Network 탭의 "Disable cache"를 켜 둔다(DevTools가 열려 있을 때만 적용) → 크롬 개발자 도구 팁
서버 쪽 캐시 전략은 읽기 캐시 전략, Next.js의 여러 캐시 계층은 Next.js 캐시 계층 참고.