검색어를 입력하세요.

웹페이지 캐시 완전 정리, Cache-Control부터 브라우저 캐시, CDN 캐시까지

간지뽕빨리턴님 2026. 5. 23. 15:55
반응형

 

 

웹페이지 캐시, Cache-Control, 브라우저 캐시, CDN 캐시를 한 번에 이해하기 위한 실무 가이드입니다.
웹페이지 캐시는 브라우저 캐시, CDN 캐시, 프록시 캐시, 서비스 워커 캐시가 함께 동작하는 웹 성능 최적화 기술입니다. 이 글에서는 Cache-Control, no-cache와 no-store 차이, ETag, Last-Modified, Vary, Nginx 캐시 설정, fetch 캐시 옵션까지 실무 중심으로 정리합니다.

이 글에서 다루는 내용

이 글은 웹페이지 캐시 설정 방법을 찾는 개발자를 위해 작성했습니다. 단순히 “캐시 삭제하는 법”이 아니라, 브라우저 캐시와 CDN 캐시가 어떻게 동작하는지, Cache-Control 옵션을 어떤 상황에서 어떻게 써야 하는지, 배포 후 오래된 CSS와 JavaScript가 남는 문제를 어떻게 줄일 수 있는지까지 정리합니다.

캐시는 쉽지만, 제대로 쓰기는 어렵다

웹페이지 캐시는 사이트 속도를 빠르게 만들고 서버 비용을 줄이는 핵심 기술입니다. 하지만 Cache-Control, 브라우저 캐시, CDN 캐시, ETag, 서비스 워커 캐시가 섞이면 문제를 찾기 어려워집니다. 특히 CSS를 수정했는데 화면이 바뀌지 않거나, JavaScript 배포 후 일부 사용자에게만 오류가 발생하는 문제는 대부분 캐시 전략과 관련이 있습니다.

웹 개발을 하다 보면 한 번쯤 이런 상황을 만납니다.

  • CSS를 수정했는데 사용자는 여전히 예전 화면을 보고 있다.
  • JavaScript 파일을 배포했는데 일부 사용자에게만 오류가 난다.
  • API 응답은 바뀌었는데 브라우저나 CDN에서 예전 데이터를 보여준다.
  • no-cache를 넣었는데도 캐시가 저장되는 것처럼 보인다.
  • 개발 환경에서는 정상인데 운영 배포 후 특정 사용자만 화면이 깨진다.

캐시는 속도를 올리는 강력한 기술입니다. 하지만 잘못 설정하면 “빠른 성능 최적화”가 아니라 “오래된 오류를 빠르게 재사용하는 장치”가 될 수도 있습니다.

20년 차 개발자 관점에서 캐시를 설명하자면, 캐시는 냉장고와 비슷합니다. 음식을 매번 새로 만들지 않고 보관해두면 빠르고 편합니다. 하지만 유통기한, 보관 위치, 누가 먹어도 되는 음식인지가 정리되어 있지 않으면 문제가 생깁니다.

웹 캐시도 마찬가지입니다. 어떤 파일은 오래 보관해도 되고, 어떤 응답은 매번 확인해야 하며, 어떤 데이터는 아예 저장하면 안 됩니다. 좋은 캐시 전략은 “무조건 캐시하기”도 아니고 “무조건 캐시 막기”도 아닙니다. 중요한 것은 리소스 성격에 맞게 다르게 제어하는 것입니다.

웹 캐시는 어디에 저장될까?

웹페이지 캐시는 한 곳에만 존재하지 않습니다. 실제 운영 환경에서는 여러 계층의 캐시가 동시에 관여합니다.

캐시 위치 설명 주의할 점
브라우저 캐시 사용자 PC나 모바일 브라우저에 저장되는 캐시입니다. 이미지, CSS, JS, 폰트 등이 주로 저장됩니다. 사용자 기기에 저장되므로 서버에서 즉시 삭제하기 어렵습니다.
CDN 캐시 Cloudflare, CloudFront, Fastly 같은 CDN 엣지 서버에 저장되는 캐시입니다. 여러 사용자에게 같은 응답이 재사용될 수 있으므로 개인정보 응답은 매우 조심해야 합니다.
프록시 캐시 회사망, 통신사, 리버스 프록시, Nginx 같은 중간 서버에서 저장될 수 있는 캐시입니다. 응답 헤더가 의도와 다르면 예상하지 못한 공유 캐시가 생길 수 있습니다.
서비스 워커 캐시 PWA나 오프라인 앱에서 JavaScript로 직접 제어하는 캐시입니다. HTTP 캐시보다 더 강하게 개입할 수 있어 업데이트 전략을 잘못 잡으면 오래된 앱이 계속 남을 수 있습니다.
Back/Forward Cache 브라우저의 뒤로가기, 앞으로가기를 빠르게 하기 위해 페이지 상태 자체를 보존하는 최적화입니다. 일반 HTTP 캐시와 다릅니다. 폼 화면, 로그인 화면, 결제 화면에서는 동작을 신중히 확인해야 합니다.
캐시 문제를 해결할 때 가장 먼저 해야 할 일은 “어디에 캐시되어 있는가?”를 찾는 것입니다. 브라우저인지, CDN인지, 서버 프록시인지, 서비스 워커인지에 따라 해결 방법이 완전히 달라집니다.

HTTP 캐시의 핵심 개념

1. Fresh와 Stale

캐시된 응답은 크게 두 상태로 나눌 수 있습니다.

상태 의미 예시
Fresh 아직 유효기간이 남아 있어 서버에 다시 묻지 않고 바로 재사용할 수 있는 상태입니다. Cache-Control: max-age=3600 설정 후 1시간 이내
Stale 유효기간이 지나서 서버에 다시 확인해야 하는 상태입니다. max-age 시간이 지난 CSS, JS, 이미지, API 응답

중요한 점은 stale 상태가 되었다고 해서 캐시가 바로 삭제되는 것은 아니라는 점입니다. 캐시는 남아 있을 수 있고, 서버에 재검증한 뒤 계속 사용할 수도 있습니다.

2. Revalidation

재검증은 브라우저가 서버에게 “내가 가진 파일이 아직 최신인가요?”라고 물어보는 과정입니다. 이때 서버가 변경 없음이라고 판단하면 전체 파일을 다시 내려주지 않고 304 Not Modified를 응답합니다.

브라우저 : /app.js 파일 주세요. 제가 가진 ETag는 "abc123"입니다. 서버 : 아직 그대로입니다. 304 Not Modified 브라우저 : 그러면 기존 캐시 파일을 계속 사용하겠습니다.

이 구조 덕분에 캐시는 빠르면서도 트래픽을 줄일 수 있습니다. 파일 전체를 다시 받지 않고 변경 여부만 확인하기 때문입니다.

3. Cache Key

캐시는 보통 요청 메서드와 URL을 기준으로 응답을 구분합니다. 그래서 아래 주소들은 사람이 보기에는 비슷해도 캐시 입장에서는 서로 다른 리소스로 판단될 수 있습니다.

/assets/app.css /assets/app.css?v=20260523 /assets/app.8fd2a1.css

이 원리를 이용해 파일 내용이 바뀔 때 파일명이나 쿼리스트링을 바꾸는 방식을 캐시 버스팅이라고 부릅니다. 실무에서는 쿼리스트링보다 빌드 결과물에 해시가 들어간 파일명을 쓰는 방식이 더 안정적입니다.

Cache-Control 옵션 정리

캐시 제어의 중심은 Cache-Control 헤더입니다. 여러 옵션을 콤마로 조합해서 사용할 수 있습니다.

Cache-Control: public, max-age=31536000, immutable
옵션 의미 추천 상황
max-age=초 지정한 초 동안 캐시를 fresh 상태로 봅니다. 이미지, CSS, JS, 폰트 등 변경 주기가 명확한 정적 파일
s-maxage=초 CDN, 프록시 같은 공유 캐시에 적용되는 유효기간입니다. 브라우저 캐시와 CDN 캐시 시간을 다르게 제어하고 싶을 때
public 브라우저뿐 아니라 CDN 같은 공유 캐시도 저장할 수 있습니다. 모든 사용자에게 동일한 이미지, CSS, JS, 공개 API
private 사용자 브라우저 같은 private cache에만 저장하도록 합니다. 로그인 후 사용자별 화면, 개인화 응답
no-cache 저장은 가능하지만 재사용 전 반드시 서버에 재검증해야 합니다. HTML, 자주 바뀌는 API, 최신성이 중요한 화면
no-store 캐시에 저장하지 말라는 의미입니다. 개인정보, 결제, 인증, 민감한 응답
must-revalidate 만료된 캐시를 사용할 때 반드시 서버 재검증을 요구합니다. max-age와 함께 엄격한 만료 정책이 필요할 때
immutable fresh 상태인 동안 파일이 바뀌지 않는다고 알려줍니다. 파일명에 해시가 붙은 정적 파일
stale-while-revalidate 만료된 캐시를 일단 보여주고 백그라운드에서 새 응답을 갱신할 수 있게 합니다. 약간 오래된 데이터가 보여도 괜찮은 공개 API, 목록, 랭킹
stale-if-error 서버 오류가 날 때 오래된 캐시라도 보여줄 수 있게 합니다. 장애 상황에서도 빈 화면보다 이전 데이터를 보여주는 것이 나은 서비스
no-transform 중간 프록시가 이미지나 응답을 임의로 변환하지 못하게 합니다. 콘텐츠 무결성, 이미지 품질, 압축 정책이 중요한 리소스
중요 : no-cache는 “캐시하지 마라”가 아닙니다. 저장은 될 수 있지만, 다시 사용할 때 서버에 확인하라는 의미입니다. 정말 저장 자체를 막고 싶다면 no-store를 사용해야 합니다.

상황별 추천 캐시 전략

1. HTML 문서

HTML은 보통 웹앱의 진입점입니다. HTML이 오래 캐시되면 새로 배포한 JS, CSS 파일을 참조하지 못해 배포 후 화면이 깨질 수 있습니다.

추천 설정 : HTML은 no-cache로 저장은 허용하되, 매번 서버에 최신 여부를 확인하게 하는 방식이 안전합니다.
Cache-Control: no-cache

관리자 페이지, 결제 페이지, 개인정보 페이지처럼 민감도가 높은 화면이라면 저장 자체를 막는 편이 낫습니다.

Cache-Control: no-store

2. 해시가 붙은 CSS, JS, 이미지

빌드 결과물이 아래처럼 파일명에 해시를 포함한다면 강한 캐시를 걸어도 안전합니다. 파일 내용이 바뀌면 파일명도 바뀌기 때문입니다.

/assets/app.a82f91c3.js /assets/style.0d9a7f21.css /assets/logo.4c11e92a.webp
추천 설정 : 해시가 붙은 정적 파일은 1년 캐시와 immutable 조합이 좋습니다.
Cache-Control: public, max-age=31536000, immutable

단, 이 전략은 “파일명이 바뀐다”는 전제가 있어야 합니다. /app.js처럼 같은 URL의 내용만 계속 바꾸는 파일에 1년 캐시를 걸면 사용자는 오래된 파일을 계속 볼 수 있습니다.

3. 해시가 없는 CSS, JS

빌드 해시를 쓰지 못하고 같은 파일명으로 계속 덮어쓰는 구조라면 긴 max-age는 위험합니다.

Cache-Control: no-cache

또는 아주 짧은 캐시를 줄 수 있습니다.

Cache-Control: public, max-age=60, must-revalidate

4. 공개 API

게시글 목록, 상품 목록, 공지사항 목록처럼 모든 사용자에게 거의 동일하게 보이는 API는 짧은 캐시를 활용할 수 있습니다.

Cache-Control: public, max-age=60, stale-while-revalidate=300

이 설정은 60초 동안은 캐시를 바로 사용하고, 이후 300초 동안은 오래된 응답을 먼저 보여주면서 백그라운드 갱신을 허용하는 방식입니다. 목록성 데이터, 인기글, 랭킹, 공지 목록처럼 약간의 지연이 허용되는 곳에 적합합니다.

5. 로그인 사용자 API

마이페이지, 내 주문내역, 내 알림, 내 운동 기록처럼 사용자별 데이터는 공유 캐시에 들어가면 안 됩니다.

Cache-Control: private, no-cache

개인정보, 인증 토큰, 결제, 민감 데이터라면 더 강하게 막습니다.

Cache-Control: no-store

6. CDN을 사용하는 경우

CDN을 사용하면 브라우저 캐시와 CDN 캐시를 분리해서 생각해야 합니다. 예를 들어 브라우저에는 매번 확인하게 하고, CDN에는 10분 정도 저장하게 만들 수 있습니다.

Cache-Control: no-cache, s-maxage=600, stale-while-revalidate=60

이 구조는 원본 서버 부하를 줄이면서도 브라우저 쪽에서는 비교적 최신성을 유지하고 싶을 때 사용할 수 있습니다.

서버별 캐시 설정 예시

1. Nginx 예시

Nginx에서는 add_headerexpires 지시어를 이용해 응답 헤더를 제어할 수 있습니다.

server { listen 80; server_name example.com; location / { try_files $uri $uri/ /index.html; add_header Cache-Control "no-cache" always; } location ~* \.(?:js|css|png|jpg|jpeg|gif|webp|svg|ico|woff2?)$ { expires 1y; add_header Cache-Control "public, max-age=31536000, immutable" always; access_log off; } }

핵심은 HTML과 정적 리소스를 다르게 보는 것입니다. index.html은 앱의 입구이므로 최신 확인을 하게 하고, 해시가 붙은 JS/CSS/이미지 파일은 오래 캐시합니다.

2. Apache 예시

Apache에서는 mod_expires로 만료 시간을 제어하고, mod_headersHeader 지시어로 응답 헤더를 직접 설정할 수 있습니다.

<IfModule mod_expires.c> ExpiresActive On ExpiresByType text/css "access plus 1 year" ExpiresByType application/javascript "access plus 1 year" ExpiresByType image/png "access plus 1 year" ExpiresByType image/jpeg "access plus 1 year" ExpiresByType image/webp "access plus 1 year" ExpiresByType image/svg+xml "access plus 1 year" </IfModule> <IfModule mod_headers.c> <FilesMatch "\.(css|js|png|jpg|jpeg|webp|svg|woff2?)$"> Header set Cache-Control "public, max-age=31536000, immutable" </FilesMatch> <FilesMatch "\.(html)$"> Header set Cache-Control "no-cache" </FilesMatch> </IfModule>

3. Node.js Express 예시

React, Vue, Vite, Angular 같은 프론트엔드 빌드 결과물을 Express에서 제공한다면 아래처럼 정적 파일과 HTML을 나눠서 처리할 수 있습니다.

import express from "express"; import path from "path"; const app = express(); app.use("/assets", express.static(path.join(process.cwd(), "dist/assets"), { immutable: true, maxAge: "1y" })); app.get("*", (req, res) => { res.setHeader("Cache-Control", "no-cache"); res.sendFile(path.join(process.cwd(), "dist/index.html")); }); app.listen(3000);

4. API 응답 예시

// 공개 목록 API res.setHeader("Cache-Control", "public, max-age=60, stale-while-revalidate=300"); // 로그인 사용자 API res.setHeader("Cache-Control", "private, no-cache"); // 결제, 인증, 개인정보 API res.setHeader("Cache-Control", "no-store");

프론트엔드 fetch 옵션으로 캐시 제어하기

서버 응답 헤더가 캐시 정책의 중심이지만, 프론트엔드에서도 fetch 옵션으로 요청 캐시 동작을 어느 정도 제어할 수 있습니다.

fetch cache 옵션 의미 사용 예시
default 브라우저 기본 HTTP 캐시 정책을 따릅니다. 일반적인 요청
no-store 캐시를 보지 않고, 받은 응답도 캐시에 저장하지 않습니다. 개인정보, 결제, 인증 확인
reload 캐시를 보지 않고 서버에서 새로 받되, 받은 응답은 캐시에 저장할 수 있습니다. 강제 새로고침과 유사한 요청
no-cache 캐시가 있으면 서버에 재검증한 뒤 사용합니다. 최신 여부 확인이 필요한 데이터
force-cache 캐시가 있으면 우선 사용합니다. 오래된 데이터가 보여도 되는 참고성 데이터
only-if-cached 캐시에 있는 응답만 사용합니다. 특수한 오프라인 또는 캐시 우선 전략
// 사용자별 민감 데이터 const me = await fetch("/api/me", { cache: "no-store" }); // 최신 여부를 확인하고 싶은 데이터 const notice = await fetch("/api/notice", { cache: "no-cache" }); // 캐시가 있으면 우선 사용해도 되는 데이터 const guide = await fetch("/api/guide", { cache: "force-cache" });
주의 : 프레임워크마다 fetch 캐시 해석이 다를 수 있습니다. 특히 Next.js 같은 프레임워크는 브라우저 HTTP 캐시, 서버 데이터 캐시, 라우트 캐시, 렌더링 캐시가 별도로 존재할 수 있으므로 공식 문서를 기준으로 확인해야 합니다.

ETag, Last-Modified, Vary 이해하기

1. ETag

ETag는 특정 리소스 버전을 식별하는 값입니다. 서버는 응답에 ETag를 내려주고, 브라우저는 다음 요청에서 이 값을 이용해 “내가 가진 리소스가 아직 최신인지” 확인할 수 있습니다.

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

이후 브라우저는 조건부 요청을 보낼 수 있습니다.

If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

서버가 변경 없음으로 판단하면 304 Not Modified를 응답하고, 브라우저는 기존 캐시를 재사용합니다.

2. Last-Modified

Last-Modified는 리소스가 마지막으로 수정된 시간을 나타냅니다. 정적 파일 서버에서는 파일 수정 시간을 기준으로 자동 설정되는 경우가 많습니다.

Last-Modified: Wed, 22 May 2026 10:00:00 GMT

3. Vary

Vary는 같은 URL이라도 어떤 요청 헤더에 따라 응답이 달라지는지 캐시에 알려주는 헤더입니다.

Vary: Accept-Encoding Vary: Accept-Language Vary: User-Agent

예를 들어 Vary: Accept-Language가 있으면 같은 URL이라도 한국어 요청과 영어 요청을 다른 응답으로 캐시할 수 있습니다. 다만 Vary를 너무 넓게 사용하면 캐시 조각이 많아져 캐시 효율이 떨어질 수 있습니다.

개발 중 캐시 문제를 확인하는 방법

1. Chrome DevTools Network 탭 확인

크롬 개발자 도구의 Network 탭에서 각 요청을 클릭하면 응답 헤더를 확인할 수 있습니다. 다음 항목을 특히 봐야 합니다.

  • Cache-Control : 캐시 정책
  • ETag : 파일 버전 식별자
  • Last-Modified : 마지막 수정 시간
  • Age : 공유 캐시에서 얼마나 오래 있었는지
  • CF-Cache-Status, X-Cache : CDN 캐시 HIT/MISS 여부
  • Status Code : 200인지 304인지
  • Size : from disk cache, from memory cache 여부

2. Disable cache

Chrome DevTools의 Network 탭에는 Disable cache 옵션이 있습니다. 개발 중에는 이 옵션을 켜고 테스트하면 브라우저 캐시 영향을 줄일 수 있습니다. 단, 이 옵션은 DevTools가 열려 있을 때의 테스트 용도라는 점을 기억해야 합니다.

3. Hard Reload와 Empty Cache and Hard Reload

개발자 도구를 연 상태에서 새로고침 버튼을 길게 누르거나 우클릭하면 다음 옵션을 볼 수 있습니다.

옵션 의미
Normal Reload 일반 새로고침입니다. 캐시 정책에 따라 재사용 또는 재검증합니다.
Hard Reload 페이지 로드에 필요한 리소스를 강제로 다시 요청합니다.
Empty Cache and Hard Reload 해당 페이지 관련 캐시를 비우고 강제로 다시 요청합니다.

4. 그래도 예전 파일이 보인다면

  • 브라우저 캐시가 아니라 CDN 캐시일 수 있습니다.
  • 서비스 워커가 응답을 가로채고 있을 수 있습니다.
  • DevTools Local Overrides가 켜져 있을 수 있습니다.
  • 서버 앞단의 Nginx, 프록시, WAS 캐시가 남아 있을 수 있습니다.
  • 파일명 해시가 바뀌지 않아 브라우저가 같은 파일로 판단하고 있을 수 있습니다.

실무에서 많이 하는 캐시 실수

실수 1. 모든 파일에 no-store를 넣는다

이렇게 하면 최신성 문제는 줄어들 수 있지만 성능은 크게 떨어집니다. 이미지, 폰트, JS, CSS를 매번 다시 받으면 사용자 경험도 나빠지고 서버 비용도 증가합니다.

# 좋지 않은 예시 Cache-Control: no-store

정적 파일은 가능하면 파일명 해시를 붙이고 강하게 캐시하는 것이 좋습니다.

# 좋은 예시 Cache-Control: public, max-age=31536000, immutable

실수 2. HTML까지 1년 캐시한다

SPA에서 index.html을 오래 캐시하면 새 배포 후에도 사용자가 예전 JS 파일을 계속 참조할 수 있습니다. 이 경우 일부 사용자에게만 흰 화면, JS 오류, CSS 깨짐이 나타날 수 있습니다.

# HTML에는 보통 이쪽이 안전합니다. Cache-Control: no-cache

실수 3. no-cache를 캐시 금지로 오해한다

no-cache는 저장을 막지 않습니다. 재사용 전 서버에 확인하라는 의미입니다. 저장 자체를 막으려면 no-store가 필요합니다.

실수 4. CDN 캐시와 브라우저 캐시를 같은 것으로 본다

브라우저 캐시는 사용자 기기에 있고, CDN 캐시는 사용자와 서버 사이의 엣지 서버에 있습니다. CDN은 여러 사용자에게 같은 캐시를 제공할 수 있으므로 개인정보 응답이 들어가면 큰 사고가 됩니다.

실수 5. 서비스 워커 캐시를 잊는다

PWA를 적용한 사이트라면 서비스 워커가 네트워크 요청을 가로챌 수 있습니다. 서버 헤더를 아무리 바꿔도 서비스 워커가 예전 응답을 반환하고 있으면 사용자는 계속 오래된 화면을 보게 됩니다.

상황별 캐시 정책 추천표

상황 추천 헤더 이유
일반 HTML Cache-Control: no-cache 저장은 가능하지만 매번 최신 여부를 확인하게 합니다.
관리자 HTML Cache-Control: no-store 민감 정보나 권한 화면은 저장 자체를 막는 편이 안전합니다.
해시가 붙은 JS/CSS Cache-Control: public, max-age=31536000, immutable 파일명 변경으로 새 버전을 구분할 수 있으므로 오래 캐시해도 됩니다.
해시 없는 JS/CSS Cache-Control: no-cache 같은 URL의 내용이 바뀔 수 있으므로 재검증이 필요합니다.
공개 이미지 Cache-Control: public, max-age=86400 이미지는 비교적 변경 빈도가 낮고 캐시 효과가 큽니다.
공개 API Cache-Control: public, max-age=60, stale-while-revalidate=300 짧게 캐시하고 갱신 지연을 숨길 수 있습니다.
사용자별 API Cache-Control: private, no-cache 공유 캐시 저장은 막고, 사용자 브라우저에서는 재검증하게 합니다.
결제, 인증, 개인정보 Cache-Control: no-store 저장 자체를 피해야 합니다.
CDN 캐시 우선 Cache-Control: no-cache, s-maxage=600 브라우저와 CDN의 캐시 시간을 분리할 수 있습니다.
장애 시 이전 데이터 허용 Cache-Control: max-age=60, stale-if-error=86400 서버 오류가 나도 이전 응답을 보여줄 수 있습니다.

자주 묻는 질문

Q1. no-cache와 no-store는 같은 의미인가요?

아닙니다. no-cache는 캐시에 저장될 수 있지만 재사용하기 전에 서버에 확인하라는 의미입니다. 반면 no-store는 응답을 캐시에 저장하지 말라는 의미입니다. 개인정보, 인증, 결제 관련 응답에는 no-store가 더 적합합니다.

Q2. CSS와 JavaScript가 배포 후에도 예전 파일로 보이는 이유는 무엇인가요?

보통 브라우저 캐시, CDN 캐시, 서비스 워커 캐시 중 하나가 오래된 파일을 반환하고 있기 때문입니다. 가장 안정적인 해결책은 빌드할 때 파일명에 해시를 붙이고, HTML은 no-cache, 해시가 붙은 정적 파일은 긴 캐시를 적용하는 방식입니다.

Q3. 정적 파일은 무조건 오래 캐시해도 되나요?

파일명에 해시가 붙어 있고, 내용이 바뀌면 URL도 바뀌는 구조라면 오래 캐시해도 괜찮습니다. 하지만 /app.js, /style.css처럼 같은 URL의 내용을 계속 덮어쓰는 구조라면 긴 캐시는 위험합니다.

Q4. CDN 캐시와 브라우저 캐시는 어떻게 다르게 봐야 하나요?

브라우저 캐시는 사용자 기기에 저장되는 캐시이고, CDN 캐시는 서버와 사용자 사이의 엣지 서버에 저장되는 캐시입니다. CDN 캐시는 여러 사용자에게 공유될 수 있으므로 사용자별 응답이나 개인정보 응답은 절대 공유 캐시에 저장되지 않도록 해야 합니다.

Q5. 웹페이지 캐시 문제를 가장 빠르게 확인하는 방법은 무엇인가요?

Chrome DevTools의 Network 탭에서 Cache-Control, ETag, Last-Modified, Age, CF-Cache-Status, 상태 코드 200/304를 확인하는 것이 좋습니다. 브라우저 캐시가 아니라 CDN이나 서비스 워커가 원인일 수도 있으므로 한 계층씩 확인해야 합니다.

좋은 캐시 전략은 “무조건 캐시”도 “무조건 금지”도 아니다

캐시는 웹 성능 최적화에서 가장 기본적이면서도 가장 실수하기 쉬운 영역입니다. 초보 개발자는 캐시 때문에 화면이 안 바뀌면 “캐시를 다 꺼야겠다”고 생각하기 쉽습니다. 하지만 실무에서는 반대로 접근해야 합니다.

  • 바뀌지 않는 파일은 강하게 캐시합니다.
  • 바뀔 수 있는 HTML은 매번 재검증합니다.
  • 개인정보는 공유 캐시에 절대 넣지 않습니다.
  • CDN 캐시와 브라우저 캐시를 분리해서 봅니다.
  • 파일명 해시, ETag, Last-Modified, Cache-Control을 함께 설계합니다.

캐시를 잘 다루면 사이트는 훨씬 빨라지고 서버 비용은 줄어듭니다. 반대로 캐시를 잘못 다루면 일부 사용자만 오래된 파일을 보고, 일부 사용자만 오류가 나며, 배포 후 원인 추적이 어려워집니다.

결국 좋은 캐시 전략은 “빠르게 보여줄 것”과 “정확하게 보여줄 것”을 구분하는 일입니다. 정적 리소스는 믿고 오래 보관하고, HTML과 민감 데이터는 조심스럽게 확인하고, CDN은 별도의 계층으로 관리해야 합니다. 이 원칙만 지켜도 대부분의 웹 캐시 문제는 훨씬 쉽게 해결할 수 있습니다.

참고자료 : 공식문서

  1. RFC 9111 : HTTP Caching
    HTTP 캐시 동작, 저장 조건, 재검증, Cache-Control 지시어의 표준 기준을 확인할 수 있는 문서입니다.
  2. MDN : Cache-Control header
    max-age, s-maxage, public, private, no-cache, no-store, immutable, stale-while-revalidate 같은 옵션을 확인할 수 있습니다.
  3. MDN : HTTP caching
    브라우저 캐시, 공유 캐시, fresh/stale, 재검증 흐름을 전체적으로 이해하는 데 좋은 문서입니다.
  4. MDN : ETag header
    리소스 버전 식별자와 조건부 요청, 304 Not Modified 흐름을 이해할 때 참고하기 좋습니다.
  5. MDN : Vary header
    요청 헤더에 따라 응답이 달라지는 경우 캐시 키를 어떻게 분리해야 하는지 설명합니다.
  6. MDN : Request.cache
    fetch 요청에서 사용할 수 있는 default, no-store, reload, no-cache, force-cache, only-if-cached 옵션을 확인할 수 있습니다.
  7. web.dev : Keeping things fresh with stale-while-revalidate
    stale-while-revalidate가 어떻게 즉시성과 최신성 사이의 균형을 잡는지 설명하는 Google web.dev 문서입니다.
  8. Chrome DevTools : Network features reference
    Network 탭에서 캐시 비활성화, 브라우저 캐시 삭제, 네트워크 요청 분석을 확인할 수 있습니다.
  9. Nginx : ngx_http_headers_module
    Nginx에서 add_header, expiresExpiresCache-Control 헤더를 제어하는 방법을 확인할 수 있습니다.
  10. Apache : mod_expires
    Apache에서 ExpiresCache-Control: max-age를 생성하는 방법을 확인할 수 있습니다.
  11. Apache : mod_headers
    Apache에서 응답 헤더를 추가, 변경, 제거하는 Header 지시어를 확인할 수 있습니다.
  12. Google Search Central : SEO Starter Guide
    검색엔진이 콘텐츠를 이해하기 쉽게 만드는 기본적인 SEO 원칙을 확인할 수 있습니다.
  13. Google Search Central : Influencing title links
    검색 결과에 표시되는 제목 링크를 더 명확하게 만드는 방법을 확인할 수 있습니다.
  14. Google Search Central : Control your snippets in search results
    검색 결과 설명문, 즉 메타 디스크립션 작성 기준을 확인할 수 있습니다.
  15. Google Search Central : Creating helpful, reliable, people-first content
    검색엔진만을 위한 글이 아니라 독자에게 실제로 도움이 되는 콘텐츠를 만드는 기준을 확인할 수 있습니다.

커피 한 잔의 힘

이 글이 도움이 되셨다면, 커피 한 잔으로 응원해주세요!
여러분의 작은 후원이 더 좋은 콘텐츠를 만드는 큰 힘이 됩니다.