기본 인프라 웹 구조는 이전 포스팅에서 다뤘으니 참고 부탁드립니다.
캐시는 기본적으로 “재사용을 위한 저장” 입니다. 브라우저는 한 번 다운로드한 리소스를 저장해두고, 다음 요청부터 다시 다운로드하지 않습니다.
예를 들어 사용자가 아래 파일을 요청하면:
app.js
logo.png
main.css
브라우저는 로컬에 저장해두고 재사용 합니다. 덕분에 로딩 속도는 빨라지고, 서버 부하는 줄어들며, 네트워크 비용도 감소합니다. 하지만 오래된 파일을 계속 보여주는 문제가 발생할 수 있습니다. 이와 같은 실무에서 자주 볼 수 있는 사례는 아래와 같습니다.
배포 완료
→ 일부 사용자만 이전 화면 사용
→ Chunk Load Error 발생
→ 특정 이미지만 갱신 안 됨
캐시는 성능을 개선하지만, 동시에 배포를 어렵게 만듭니다. 그래서 현대 프론트엔드의 핵심은 '빠르게 캐시하면서도 최신 상태를 유지하는 것'입니다.
현대 프론트엔드의 캐시 전략은 '변하지 않는 파일은 아주 오래 캐시하고, 변경되는 파일은 이름 자체를 바꾼다' 로 정리됩니다. 이 전략의 핵심은 Hash 기반 파일명 입니다.
예전 파일 구조는 보통 아래와 같았습니다.
app.js
style.css
여기서 문제는 파일 내용이 바뀌어도 이름이 같기 때문에 브라우저 입장에서는 '이미 받은 파일인데?' 라고 생각하고 이전 파일을 재사용하는 것입니다. 그래서 현대 빌드 시스템(Webpack, Vite, NextJS)은 보통 아래처럼 빌드합니다.
app.a1b2c3.js
vendor.f8x9q1.js
파일 내용이 변경되면 hash 도 함께 바뀌어 브라우저 입장에서는 '완전히 새로운 파일'로 인식하게 되는 것입니다. 덕분에 정적 리소스에 매우 강한 캐시를 걸 수 있습니다.
Cache-Control: public, max-age=31536000, immutable
위 내용은 1년 동안 다시 요청하지 말라는 건데, 이렇게 긴 캐시는 위험하지만 hash filename 전략과 함꼐라면 파일이 바뀌면 이름 자체가 바뀌기 때문에 안전합니다.
JS/CSS 는 오래 캐시해도 되지만 index.html 은 다르게 관리해야 합니다. 왜냐하면 HTML 이 최신 JS bundle 경로를 들고 있기 때문입니다. 예를 들어 index.html → app.a1.js 참조하고 있는데 새로 배포하여 app.b2.js 가 생성되었다고 가정할 때 브라우저가 이전 HTMl 을 계속 사용하면 존재하지 않는 JS 요청이 발생합니다. 이는 ChunkLoadError 의 대표 원인입니다. 그래서 대부분의 SPA/NextJS 서비스는 아래 전략을 사용합니다.
HTML ==> Cache-Control: no-cache
JS/CSS/Image ==> Cache-Control: public, max-age=31536000, immutable
여기서 no-cache 는 캐시 안 함이 아니라 사용 전에 서버에 최신 여부를 전달하라는 의미가 명확합니다. 즉 저장은 하기 때문에, 저장 금지는 Cache-Control: no-store 로 제어해야 합니다.
보통 인증 응답, 결제 정보, 민감 데이터 등에 사용합니다.
CDN(Content Delivery Network)은 단순히 빠른 서버 정도가 아니라
'전 세계에 분산된 거대한 캐시 서버'에 가깝습니다. 그래서 CDN 캐시 전략은
브라우저 캐시만큼 중요합니다.
실제로 실무에서 마주했던 문제가 배포했는데 이전 이미지가 계속 보이는 문제였습니다.
원인은 CDN 캐시 유지 문제로 버전관리를 하여 해결하였습니다.
즉, 이미지 역시 버전 관리가 필요합니다.
banner.png?v=2 혹은 banner.a1b2.png
CDN/브라우저/프록시 환경에서 더 안정적으로 동작하기 때문에 최근에는 filename hash 방식이 더 일반적으로 쓰입니다.
브라우저는 단순히 '캐시 사용'만 하는 것이 아닙니다. 때때로 서버에게 해당 파일이 아직도 같은 버전인 지 확인을 하는데 이때 사용하는 것이 'ETag' 입니다.
서버 응답이 ETag: "abc123" 이고 브라우저 다음 요청이 If-None-Match: "abc123" 라고 할 때 서버가 파일이 동일하다고 판단하면, 304 Not Modified 를 반환합니다. 즉, 파일 본문은 다시 보내지 않습니다.
덕분에 다운로드 비용 감소, 빠른 로딩, 네트워크 절약 효과가 있습니다. 다만 최근 SPA 환경에서는 Hash Filename + Immutable Cache 전략이 보편화되면서, ETag 의존도는 예전보다 줄어드는 추세입니다.
PWA 환경에서는 Service Worker 가 브라우저 네트워크 앞단에서 동작하기 때문에 캐시가 더 강력해집니다. 구조를 단순화하면 아래와 같습니다.
Browser
↓
Service Worker
↓
Network
즉 개발자가 네트워크 요청 자체를 제어할 수 있습니다. 대표 전략은 다음과 같습니다.
React Query 나 SWR 라이브러리에선 Stale While Revalidate 철학을 기반으로 합니다. 즉, 빠르게 뽀여주고 뒤에서 최신 상태로 갱신하는 방식입니다.
실무 기준으로 가장 일반적이고 안전한 전략은 아래와 같습니다.
| 리소스 | 전략 |
| ------ | ------------- |
| HTML | no-cache |
| JS/CSS | 1년 immutable |
| 이미지 | hash filename |
| API | 상황별 |
| 인증 응답 | no-store |
그리고 가장 중요한 건 '파일 내용이 바뀌면 URL 도 바뀌게 만드는 것' 입니다.
프론트엔드에서 캐시는 단순한 성능 최적화 기술이 아닙니다. 캐시는 배포 전략, 사용자 경험, 장애 대응, 비용 최적화 등 모든 것과 연결됩니다. 특히 현대 프론트엔드에서는 아래 조합이 사실상 표준이 되었습니다.
Hash Filename
+
Immutable Cache
+
짧은 HTML 캐시
이 구조를 이해하면, Chunk Error 원인을 이해하고 배포 장애를 줄이며, 더 빠른 웹 경험을 만들 수 있다고 생각합니다.
좋은 프론트엔드 개발자는 결국 "브라우저가 파일을 저장하고 재사용하는 방식" 을 이해하는 사람이라고 생각합니다.