
이 글에서 다루는 내용
이 글은 웹페이지 캐시 설정 방법을 찾는 개발자를 위해 작성했습니다. 단순히 “캐시 삭제하는 법”이 아니라, 브라우저 캐시와 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 캐시와 다릅니다. 폼 화면, 로그인 화면, 결제 화면에서는 동작을 신중히 확인해야 합니다. |
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 파일을 참조하지 못해 배포 후 화면이 깨질 수 있습니다.
no-cache로 저장은 허용하되, 매번 서버에 최신 여부를 확인하게 하는 방식이 안전합니다.Cache-Control: no-cache관리자 페이지, 결제 페이지, 개인정보 페이지처럼 민감도가 높은 화면이라면 저장 자체를 막는 편이 낫습니다.
Cache-Control: no-store2. 해시가 붙은 CSS, JS, 이미지
빌드 결과물이 아래처럼 파일명에 해시를 포함한다면 강한 캐시를 걸어도 안전합니다. 파일 내용이 바뀌면 파일명도 바뀌기 때문입니다.
/assets/app.a82f91c3.js
/assets/style.0d9a7f21.css
/assets/logo.4c11e92a.webpimmutable 조합이 좋습니다.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-revalidate4. 공개 API
게시글 목록, 상품 목록, 공지사항 목록처럼 모든 사용자에게 거의 동일하게 보이는 API는 짧은 캐시를 활용할 수 있습니다.
Cache-Control: public, max-age=60, stale-while-revalidate=300이 설정은 60초 동안은 캐시를 바로 사용하고, 이후 300초 동안은 오래된 응답을 먼저 보여주면서 백그라운드 갱신을 허용하는 방식입니다. 목록성 데이터, 인기글, 랭킹, 공지 목록처럼 약간의 지연이 허용되는 곳에 적합합니다.
5. 로그인 사용자 API
마이페이지, 내 주문내역, 내 알림, 내 운동 기록처럼 사용자별 데이터는 공유 캐시에 들어가면 안 됩니다.
Cache-Control: private, no-cache개인정보, 인증 토큰, 결제, 민감 데이터라면 더 강하게 막습니다.
Cache-Control: no-store6. CDN을 사용하는 경우
CDN을 사용하면 브라우저 캐시와 CDN 캐시를 분리해서 생각해야 합니다. 예를 들어 브라우저에는 매번 확인하게 하고, CDN에는 10분 정도 저장하게 만들 수 있습니다.
Cache-Control: no-cache, s-maxage=600, stale-while-revalidate=60이 구조는 원본 서버 부하를 줄이면서도 브라우저 쪽에서는 비교적 최신성을 유지하고 싶을 때 사용할 수 있습니다.
서버별 캐시 설정 예시
1. Nginx 예시
Nginx에서는 add_header와 expires 지시어를 이용해 응답 헤더를 제어할 수 있습니다.
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_headers의 Header 지시어로 응답 헤더를 직접 설정할 수 있습니다.
<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 GMT3. 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은 별도의 계층으로 관리해야 합니다. 이 원칙만 지켜도 대부분의 웹 캐시 문제는 훨씬 쉽게 해결할 수 있습니다.
참고자료 : 공식문서
- RFC 9111 : HTTP Caching
HTTP 캐시 동작, 저장 조건, 재검증, Cache-Control 지시어의 표준 기준을 확인할 수 있는 문서입니다. - MDN : Cache-Control header
max-age,s-maxage,public,private,no-cache,no-store,immutable,stale-while-revalidate같은 옵션을 확인할 수 있습니다. - MDN : HTTP caching
브라우저 캐시, 공유 캐시, fresh/stale, 재검증 흐름을 전체적으로 이해하는 데 좋은 문서입니다. - MDN : ETag header
리소스 버전 식별자와 조건부 요청,304 Not Modified흐름을 이해할 때 참고하기 좋습니다. - MDN : Vary header
요청 헤더에 따라 응답이 달라지는 경우 캐시 키를 어떻게 분리해야 하는지 설명합니다. - MDN : Request.cache
fetch요청에서 사용할 수 있는default,no-store,reload,no-cache,force-cache,only-if-cached옵션을 확인할 수 있습니다. - web.dev : Keeping things fresh with stale-while-revalidate
stale-while-revalidate가 어떻게 즉시성과 최신성 사이의 균형을 잡는지 설명하는 Google web.dev 문서입니다. - Chrome DevTools : Network features reference
Network 탭에서 캐시 비활성화, 브라우저 캐시 삭제, 네트워크 요청 분석을 확인할 수 있습니다. - Nginx : ngx_http_headers_module
Nginx에서add_header,expires로Expires와Cache-Control헤더를 제어하는 방법을 확인할 수 있습니다. - Apache : mod_expires
Apache에서Expires와Cache-Control: max-age를 생성하는 방법을 확인할 수 있습니다. - Apache : mod_headers
Apache에서 응답 헤더를 추가, 변경, 제거하는Header지시어를 확인할 수 있습니다. - Google Search Central : SEO Starter Guide
검색엔진이 콘텐츠를 이해하기 쉽게 만드는 기본적인 SEO 원칙을 확인할 수 있습니다. - Google Search Central : Influencing title links
검색 결과에 표시되는 제목 링크를 더 명확하게 만드는 방법을 확인할 수 있습니다. - Google Search Central : Control your snippets in search results
검색 결과 설명문, 즉 메타 디스크립션 작성 기준을 확인할 수 있습니다. - Google Search Central : Creating helpful, reliable, people-first content
검색엔진만을 위한 글이 아니라 독자에게 실제로 도움이 되는 콘텐츠를 만드는 기준을 확인할 수 있습니다.