배포했는데 메뉴가 안 보임 — HTML 에 Cache-Control 이 없었다
새 메뉴를 배포했는데 이미 와 본 브라우저에서만 안 보였다. HTML 이 Cache-Control 없이 나가서 브라우저가 휴리스틱 캐시로 옛 사본을 쓰고 있었다. no-cache 한 줄로 끝낸 기록.
사이트에 제품 탭을 새로 달아 배포했다. 배포는 성공했다는데 평소 쓰던 브라우저에는 새 메뉴가 안 보였다.
사파리로 여니 거기엔 보인다. 같은 주소가 브라우저마다 다르게 나오고 있었다.
답부터 적는다. HTML 이 Cache-Control 헤더 없이 나가고 있었다. 이 헤더가 없으면 브라우저는 서버에 묻지 않고 옛 사본을 쓰는 휴리스틱 캐시로 떨어진다. nginx 서버 블록에 Cache-Control: no-cache 한 줄을 더해 해결했다.
증상
서버가 틀렸는지부터 확인했다. 캐시를 끼지 않고 직접 받아 보니 새 HTML 이 내려온다. 서버는 옳았다. 브라우저가 묻지 않은 것이다.
응답 헤더를 보니 HTML 에는 Cache-Control 이 아예 없고 Last-Modified 만 달려 있었다. 파일명에 해시가 박힌 /_astro/ 쪽에는 1년 캐시를 정성껏 달아 두고서, 정작 그 파일명이 바뀔 때마다 새로 가리켜야 하는 HTML 은 그냥 두고 있었다.
사파리에서 보인 건 그쪽엔 묵은 사본이 없어서였던 모양이다.
원인
Cache-Control 이 없으면 브라우저가 캐시를 안 쓸 것 같지만 반대다. 지시가 없으면 브라우저는 신선도를 스스로 추정한다. HTTP 규격(RFC 9111)이 허용하는 휴리스틱 캐싱이고, 흔한 구현이 「Last-Modified 이후 지난 시간의 10%」 동안 묻지 않고 쓴다.
고약한 건 기울기다. 마지막 수정이 오래된 페이지일수록 그 10% 창이 길어진다. 오래 손대지 않은 페이지일수록 모처럼 배포한 변경이 다시 오는 방문자에게 오래 안 보인다는 뜻이다. 자주 고치는 동안에는 멀쩡하다가 사이트가 안정될수록 심해진다.
새 브라우저나 시크릿 창에서는 늘 멀쩡하다는 점도 한몫한다. 캐시가 없는 곳에서는 재현이 안 되니 「내 컴퓨터만 이상한가」 하고 넘기기 쉽다.
해결
-
nginx 의 HTML 서빙 블록에 한 줄을 더했다.
location / { add_header Cache-Control "no-cache"; try_files $uri $uri/ $uri/index.html =404; } -
no-cache는 이름과 달리 「받지 마라」가 아니라 「캐시해도 되는데 쓰기 전에 서버에 확인해라」다. 받지 말라는 쪽은no-store고, 여기엔 과하다. -
확인해서 안 바뀌었으면 서버가
304 Not Modified로 답하고 본문은 안 보낸다. 트래픽은 거의 그대로고, 배포한 것은 다음 방문에 바로 보인다.$ curl -sI https://rlidea.app/ko/ | grep -i cache-control cache-control: no-cache $ curl -s -o /dev/null -w 'status=%{http_code} size=%{size_download}\n' \ -H "If-Modified-Since: Mon, 12 Oct 2026 07:29:15 GMT" https://rlidea.app/ko/ status=304 size=0 -
해시가 박힌
/_astro/는 1년 캐시(public, max-age=31536000, immutable) 그대로 둔다. 내용이 바뀌면 파일명이 바뀌고, 바뀐 파일명은 매번 새로 확인하는 HTML 이 물어 오니 서로 맞물린다.
덤 — 같은 점검에서 나온 것
헤더를 훑는 김에 다른 블록도 봤다. expires 와 add_header Cache-Control 을 겹쳐 쓴 자리에서 Cache-Control 이 두 줄로 나가고 있었다. 규격상 여러 줄은 쉼표로 이은 것과 같게 읽히지만 첫 줄만 읽는 파서는 뒷줄의 immutable 을 놓친다. 하나로 합쳤다.
이 중복은 한동안 안 보였다. 클라우드플레어가 나가는 길에 두 줄을 한 줄로 합쳐 주고 있어서다. 캐시 헤더는 엣지를 거치지 말고 오리진에 직접 물어서 재야 한다는 것까지 같은 날 배웠다.
참고: