현록

HTTP Cache-Control no-cache와 no-store 차이

HTTP 응답의 캐시를 제어할 때 no-cacheno-store를 자주 사용한다.
이름만 보면 둘 다 캐시를 만들지 않는 설정처럼 보이지만 실제 기준은 다르다.

no-cache는 저장을 금지하지 않고 재사용 전에 원본 서버의 검증을 요구한다.
no-store는 요청과 응답을 캐시에 저장하지 말라는 지시다.

두 지시자의 핵심 차이

응답의 Cache-Control: no-cache는 정상적인 캐시 조건을 만족하면 응답을 저장할 수 있게 둔다.
다만 저장한 응답으로 다음 요청을 바로 처리할 수 없고 원본 서버에 전달해 성공적으로 검증해야 한다.

Cache-Control: no-cache

응답의 Cache-Control: no-store는 private cache와 shared cache 모두에 해당 요청과 응답을 저장하지 말라고 지시한다.
저장한 콘텐츠의 재검증이 아니라 저장 자체를 막는 설정이다.

Cache-Control: no-store

no-store는 브라우저의 private cache와 CDN이나 proxy의 shared cache 모두에 적용된다.

no-cache의 재검증 흐름

no-cache의 장점은 매번 최신 상태를 확인하면서 변경되지 않은 응답 본문을 다시 내려받지 않을 수 있다는 점이다.
이 흐름에는 ETagLast-Modified 같은 validator가 필요하다.

서버가 처음 응답할 때 ETag를 함께 보낼 수 있다.

HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "post-42-v3"
Content-Type: application/json

{"id":42,"title":"Cache-Control 정리"}

캐시는 응답을 보관할 수 있지만 다음 요청에서 그대로 재사용하지 않는다.
저장한 ETag를 If-None-Match에 넣어 원본 서버에 조건부 요청을 보낸다.

GET /api/posts/42 HTTP/1.1
Host: api.example.com
If-None-Match: "post-42-v3"

콘텐츠가 바뀌지 않았다면 서버는 본문 없이 304 Not Modified로 응답할 수 있다.

HTTP/1.1 304 Not Modified
Cache-Control: no-cache
ETag: "post-42-v3"

캐시는 검증에 성공한 기존 본문을 다시 사용한다.
본문 전송량은 줄지만 서버까지 검증 요청을 보내는 왕복 시간은 남는다.

validator가 없거나 서버가 조건부 요청을 지원하지 않으면 원본 서버가 매번 200 OK와 전체 본문을 보낼 수 있다.
no-cache만 붙인다고 자동으로 304 응답이 만들어지는 것은 아니다.
304를 포함한 응답 코드의 역할은 HTTP 상태 코드 정리에서 이어서 볼 수 있다.

no-store의 저장 금지 범위

응답의 no-store는 캐시가 바로 전달 중인 요청과 응답의 어떤 부분도 의도적으로 비휘발성 저장소에 보관하지 못하게 한다.

로그인 과정에서 발급한 일회성 코드나 저장 자체를 피해야 하는 민감한 응답에 맞는 기준이다.
저장된 본문이 없으므로 다음 요청에서는 304 재검증으로 본문을 절약하는 흐름을 기대할 수 없다.

no-store는 보안 기능 전체를 대신하지 않는다.
악의적이거나 손상된 캐시가 지시를 무시할 수 있고 네트워크 도청이나 애플리케이션 로그 저장도 막지 못한다.
민감한 응답에는 HTTPS, 인증과 권한 검사, 안전한 로그 정책을 별도로 적용해야 한다.

기존 캐시 삭제에 대한 오해

서버가 새 응답에 no-store를 추가해도 같은 URL의 과거 응답은 자동으로 삭제되지 않으며, 아직 fresh하다면 새 header를 받을 기회도 없다.

이미 배포한 캐시를 즉시 없애려면 CDN의 purge 기능을 사용하거나 정적 asset 파일명에 content hash를 넣어 cache key를 바꿔야 한다.

no-store를 앞으로의 저장 정책과 과거 캐시의 삭제 명령으로 동시에 이해하면 배포 뒤 오래된 콘텐츠가 남는 원인을 찾기 어렵다.
HTTP 캐시 표준에는 중간 캐시의 특정 응답을 일괄 삭제하는 범용 Cache-Control 지시자가 없다.

요청과 응답 지시자의 방향

Cache-Control은 요청과 응답 양쪽에 들어갈 수 있다.
같은 이름이라도 누가 이후 동작을 지시하는지 구분해야 한다.

요청의 no-cache는 클라이언트가 저장된 응답을 원본 서버의 성공적인 검증 없이 사용하지 않기를 원한다는 뜻이다.
브라우저의 새로고침 과정에서 비슷한 요청 지시자를 볼 수 있다.

응답의 no-cache는 서버가 이 응답의 이후 재사용 조건을 캐시에 전달한다.
일반적인 웹 애플리케이션에서 캐시 정책을 정할 때는 서버가 보내는 응답 header가 중심이다.

요청의 no-store는 캐시가 해당 요청이나 그에 대한 응답을 저장하지 않도록 지시한다.
그러나 이미 저장된 응답을 지우는 명령은 아니며 no-store만으로 과거 응답의 재사용까지 금지하는 뜻도 아니다.

max-age와 must-revalidate의 차이

max-age=0은 응답을 저장할 수 있지만 즉시 stale 상태가 되게 한다.
반면 no-cache는 응답이 fresh로 계산될 수 있는 상황에서도 검증 없이 다른 요청에 사용하지 못하게 한다.

Cache-Control: max-age=0, must-revalidate

must-revalidate는 stale 응답을 원본 서버의 성공적인 검증 없이 재사용하지 못하게 한다.
따라서 max-age=0, must-revalidate 조합은 일반적인 목적에서 no-cache와 비슷한 결과를 만든다.
현재 환경이라면 의도가 더 직접적인 no-cache를 우선 사용할 수 있다.

no-store, no-cache, max-age=0, must-revalidate처럼 관련 지시자를 모두 붙이는 설정은 의미를 더 명확하게 만들지 않는다.
새 응답의 저장을 금지한 상태에서는 저장된 응답의 freshness와 재검증 규칙을 함께 지정할 실익이 없다.

private와 캐시 계층

private는 응답을 전혀 저장하지 말라는 뜻이 아니다.
shared cache의 저장은 금지하지만 브라우저 같은 private cache의 저장은 허용한다.

사용자별 HTML을 브라우저에 저장해도 되지만 매번 최신 상태를 확인해야 한다면 다음 조합을 사용할 수 있다.

Cache-Control: private, no-cache

민감해서 브라우저에도 남기지 않아야 한다면 no-store가 더 맞다.
반대로 hash가 포함된 JavaScript나 CSS처럼 URL이 콘텐츠 버전을 나타내는 파일에는 두 지시자보다 긴 freshness가 어울린다.

Cache-Control: public, max-age=31536000, immutable

HTTP 캐시와 framework의 데이터 캐시는 같은 계층이 아닐 수 있다.
예를 들어 Next.js의 서버 렌더링 캐시 기준은 Next.js 16 Cache Components와 use cache 정리처럼 framework 설정을 별도로 확인해야 한다.

선택 기준

응답을 저장해도 되고 사용할 때마다 최신 상태를 확인해야 한다면 no-cache를 선택한다.
ETag나 Last-Modified를 함께 제공하면 변경되지 않은 본문의 전송을 줄일 수 있다.

요청과 응답이 캐시에 남는 것 자체를 피해야 한다면 no-store를 선택한다.
다만 기존 캐시 삭제와 통신 보안까지 해결한다고 기대하지 않는다.

사용자별 응답을 브라우저에만 저장하려면 private를 먼저 기준으로 삼고 freshness와 재검증 정책을 조합한다.
모든 응답에 습관적으로 no-store를 붙이기보다 데이터의 민감도, 재사용 가능성, 최신성 요구를 나눠 결정하는 편이 낫다.

참고 자료

관련 포스트
Git reset과 revert 차이 thumbnail
Git reset과 revert 차이
Git reset과 revert의 차이를 커밋 이력, staging area와 working tree, 공유 브랜치의 안전한 되돌리기 기준으로 정리합니다.
HTML disabled와 readonly 차이 thumbnail
HTML disabled와 readonly 차이
HTML disabled와 readonly의 차이를 사용자 입력, 포커스, 폼 제출, 유효성 검사, 적용 가능한 요소를 기준으로 정리합니다.
Git fetch와 pull 차이 thumbnail
Git fetch와 pull 차이
Git fetch와 pull이 원격 변경을 가져오고 현재 브랜치에 통합하는 방식을 정리합니다. remote-tracking branch, upstream, fast-forward, merge와 rebase 선택 기준을 함께 봅니다.
HTTP Content-Type과 Accept 차이 thumbnail
HTTP Content-Type과 Accept 차이
HTTP Content-Type과 Accept 헤더의 방향, JSON 요청과 응답 예제, 콘텐츠 협상과 406·415 상태 코드의 차이를 정리합니다.
HTTP PUT과 PATCH 차이 thumbnail
HTTP PUT과 PATCH 차이
HTTP PUT과 PATCH의 차이를 리소스 교체와 변경 명령, 멱등성, JSON Patch와 JSON Merge Patch, ETag를 이용한 동시 수정 방지 기준으로 정리합니다.
HTML label과 input 연결 방법 thumbnail
HTML label과 input 연결 방법
HTML label과 input을 for와 id로 연결하는 방법, 암시적 연결, 폼 그룹과 보조 설명, 접근성 오류 패턴을 정리합니다.
HTML section과 article 차이 thumbnail
HTML section과 article 차이
HTML section과 article의 의미, 독립성 판단 기준, 중첩 구조, 제목과 접근성을 고려한 선택 방법을 정리합니다.
HTML div와 span 차이 thumbnail
HTML div와 span 차이
HTML div와 span의 차이를 기본 display, 담을 수 있는 내용, 시맨틱 태그, CSS와 JavaScript에서 그룹화하는 기준으로 정리합니다.
HTTP 상태 코드 정리 thumbnail
HTTP 상태 코드 정리
HTTP 상태 코드의 2xx, 3xx, 4xx, 5xx 의미와 200, 201, 204, 301, 304, 400, 401, 403, 404, 500, 502, 503 차이를 정리합니다.
HTML id와 class 차이 thumbnail
HTML id와 class 차이
HTML id와 class의 차이를 문서 내 유일성, 여러 값 사용, CSS 선택자, JavaScript 탐색, fragment 링크와 이름 작성 기준으로 정리합니다.
HTML button과 a 태그 차이 thumbnail
HTML button과 a 태그 차이
HTML button과 a 태그의 차이를 이동과 동작의 의미, href, type 속성, 폼 제출, 키보드 접근성, 잘못된 사용 패턴으로 정리합니다.
HTTP GET과 POST 차이 thumbnail
HTTP GET과 POST 차이
HTTP GET과 POST의 차이를 초보자 기준으로 정리합니다. 조회와 변경, query string과 request body, 캐시, safe, idempotent, form과 fetch 사용 기준을 함께 봅니다.
npm outdated와 npm update 차이 thumbnail
npm outdated와 npm update 차이
npm outdated와 npm update의 차이를 초보자 기준으로 정리합니다. Current, Wanted, Latest의 의미와 semver 범위 안에서 업데이트되는 방식, package-lock.json 변화까지 함께 봅니다.
npm install -g와 npx 차이 thumbnail
npm install -g와 npx 차이
npm install -g와 npx의 차이를 초보자 기준으로 정리합니다. 전역 설치, 로컬 설치, 일회성 실행, 프로젝트 scripts에 넣는 기준까지 함께 봅니다.
package.json에서 ^와 ~ 차이 thumbnail
package.json에서 ^와 ~ 차이
package.json 의존성 버전 앞에 붙는 ^와 ~의 차이를 초보자 기준으로 정리합니다. semantic versioning, 버전 범위, 0.x 예외, package-lock.json과의 관계까지 함께 봅니다.
Git merge와 rebase 차이 thumbnail
Git merge와 rebase 차이
Git merge와 rebase가 커밋 그래프를 어떻게 바꾸는지 정리합니다. fast-forward, merge commit, rebase의 커밋 ID 변경, 충돌 처리, 공유 브랜치에서의 안전한 사용 기준까지 살펴봅니다.
npx와 npm exec 차이 thumbnail
npx와 npm exec 차이
npx와 npm exec가 어떤 명령어인지 초보자 기준으로 정리합니다. 로컬 패키지 실행, 원격 패키지 임시 실행, --package 옵션, -- 인자 전달 차이까지 함께 봅니다.
npm scripts와 npm run 정리 thumbnail
npm scripts와 npm run 정리
npm scripts란 무엇인지, package.json scripts와 npm run의 관계를 초보자 기준으로 정리합니다. npm run dev, npm start, npm test, node_modules/.bin, -- 인자 전달 방식까지 함께 봅니다.
dependencies와 devDependencies 차이 thumbnail
dependencies와 devDependencies 차이
package.json의 dependencies와 devDependencies 차이를 초보자 기준으로 정리합니다. npm install과 npm install -D, 배포 환경 설치, package-lock.json과의 관계까지 함께 봅니다.
npm ci와 npm install 차이 thumbnail
npm ci와 npm install 차이
npm ci와 npm install의 차이를 초보자 기준으로 정리합니다. clean install의 의미, package-lock.json 조건, CI에서 npm ci를 쓰는 이유, .npmrc 플래그 주의점까지 함께 봅니다.
.npmrc 파일이란? thumbnail
.npmrc 파일이란?
storybook을 사용해보려고 하다 마주한 이슈의 해결법을 알아보다가 등장한 .npmrc라는 파일에 대해 공부해보았다. .npmrc 파일이란? .npmrc 파일은 npm에 대한 config 파일이다. (npm에 대한 rc 파일) 프로젝트별 registry, install 옵션, 인증 토큰처럼 npm CLI가 읽는 설정을 관리할 때 사용한다.
스토리북이란? thumbnail
스토리북이란?
Storybook은 UI 컴포넌트를 독립적으로 개발하고, 문서화하고, 테스트하기 위한 프론트엔드 워크샵입니다. Storybook 10.4 기준 설치 흐름과 stories, 문서화, 테스트 활용 방식을 정리합니다.
TTV와 TTI 차이 thumbnail
TTV와 TTI 차이
TTV와 TTI의 차이를 초보자 기준으로 정리합니다. 사용자가 화면을 보는 시점, 상호작용 가능한 시점, FCP, LCP, INP, Core Web Vitals와의 관계까지 함께 봅니다.
Maria DB 외부 접속 설정하기 thumbnail
Maria DB 외부 접속 설정하기
안녕하세요. 오늘은 Maria DB 초기 세팅 시, 외부에서 접속이 안될 때 매뉴얼을 작성해보겠습니다. dotenv 패키지를 통해서 환경변수로 관리한다면, 별도의 추가 작업을 할 일이 없으실 겁니다.
package-lock.json 파일의 역할 thumbnail
package-lock.json 파일의 역할
안녕하세요. 오늘은 node 환경의 개발자라면 한번쯤 궁금했을만한 package-lock.json의 역할에 대해 알아보겠습니다. 우리는 node 환경에서 개발을 할 때 다양한 패키지들을 설치하여 활용하곤 합니다. 우리가 설치하는 패키지 또한 다른 npm 패키지를 활용하여 만든 패키지들이고 이들 또한 설치를 하게 됩니다. 이렇게 직간접적으로 설치된 패키지들은 대부분 호환성을 "^1.1.5"와 같이 표현하여, 범위로 지정해두고 있습니다.
Linux 환경 배포 자동화 체험해보기 thumbnail
Linux 환경 배포 자동화 체험해보기
안녕하세요. 요즘 포트폴리오를 만들면서 서버 상에 자주 반영할 일이 생겼는데, 매번 명령어들을 타이핑하는 것이 비효율적이라 생각이 들어 배포 자동화를 생각해보게 되었습니다. 현재 레벨에서는 단순히 명령어들만 단축시켜도 효율적이라 생각이 들어 간단한 쉘 스크립트만 작성하였습니다. 정말 간단하니 여러분도 도전해보시길 바랍니다.
협업 필수품. Prettier thumbnail
협업 필수품. Prettier
안녕하세요. 오늘은 Prettier이라는 도구에 대해 알려드리고자 합니다. 개발자는 각자의 코딩스타일이 존재합니다. 그러다보니 같은 프로젝트에서도 작성하는 소스마다 스타일이 제각기 다르기 일쑤입니다. 그럴 때 도입하면 좋은 것이 Prettier입니다. 프로젝트 root 폴더에 .prettierrc 라는 파일을 생성한 뒤, 위 예시와 같이 원하는 옵션을 JSON 형식으로 작성해주면 됩니다.