Cloudflare 배포는 성공했는데 화면이 안 바뀌었다 — 원인이 두 번 달랐다 (2026)
Cloudflare 대시보드의 Deployments 탭은 매번 초록색 “성공”을 찍고 있었다. 그런데 사이트의 특정 경로 하나만 몇 번을 다시 배포해도 화면이 그대로였다. 8번 연속 배포가 이 경로 하나만 빼고 전부 반영됐다.
같은 저장소에서 몇 주 뒤 증상이 똑같은 사고가 다시 났다. 이번엔 “저번에 겪어봤으니 안다”고 생각하고 같은 처방을 썼다가 틀렸다. 두 번의 사고를 다 정리하면 이렇다.
1차 사고 — 빌드 캐시가 특정 경로를 과거에 붙잡아뒀다
증상은 이랬다: 라이트 테마 수정, 배너 수정, 백엔드 교체 — 8번의 서로 다른 커밋을 연속으로 푸시했는데, 사이트의 관리자 콘솔 페이지만 최초 생성 커밋 시점의 내용으로 계속 나왔다. 다른 경로(data/, src/ 기반 페이지)는 매번 정상 최신화됐다.
안 통했던 시도들 (순서대로)
_headers에Cache-Control: no-store추가 — zone 레벨 응답 캐시를 겨냥했지만 무관했다.- Workers 설정에서 해당 경로를 워커가 직접 정적 자산을 서빙하도록 강제 — 여전히 안 풀렸다.
- Cloudflare 대시보드의 Caching → Purge Everything — 이것도 안 풀렸다.
- 콘텐츠를 워커 스크립트 안에 문자열 상수로 아예 내장해서 정적 자산 시스템을 완전히 우회 — 그래도 옛 내용이 나왔다.
4번이 결정적 단서였다. 정적 자산 시스템을 아예 안 거치는데도 옛 내용이 나온다는 건, 문제가 서빙 단계가 아니라 빌드 단계에 있다는 뜻이었다.
진짜 원인 — 빌드 로그로 확정
Cloudflare Workers Build의 빌드 상세 로그를 직접 열어봤다. 매번 “정상 완료”를 찍고 산출물 생성 로그(HTML 83.3KB 생성)도 성공으로 남는데, 그 안 실제 내용이 특정 소스 경로 하나만 최초 커밋 시점에 고정돼 있었다 — 파일 크기까지 매번 정확히 83.3KB로 똑같았다.
로그에 있던 단서는 Restoring from build output cache였다. Cloudflare Workers Build가 빌드 캐시를 복원하는 과정에서, 세션 도중 새로 생긴 디렉터리 하나를 잘못 매칭해 계속 과거 버전으로 복원한 것으로 보인다. (Cloudflare 내부 동작이라 100% 확정할 수는 없다.)
해결
문제의 디렉터리를 소스로 쓰지 않고, 처음부터 계속 정상 갱신되던 다른 경로에서 직접 읽도록 빌드 스크립트를 바꿨다. 다음 배포부터 바로 풀렸다(64초). 문제였던 디렉터리는 완전히 삭제했다.
2차 사고 — 같은 증상, 다른 원인
몇 주 뒤 다른 곳에서 같은 증상을 또 봤다. 이번엔 이미지 파일을 같은 경로에 덮어쓰는 방식으로 갱신했는데, 배포가 끝난 뒤에도 옛 이미지가 계속 내려왔다.
증상이 똑같으니 처음엔 자연스럽게 “1차 사고와 같은 원인”이라고 판단했다. prebuild 단계에 rm -rf dist를 추가해 로컬 빌드 산출물을 강제로 지웠다. 안 풀렸다. 여기서 첫 번째 진단을 그대로 재사용한 게 틀렸다는 게 드러났다.
반증 테스트로 원인을 갈랐다
두 원인(빌드 캐시 vs 엣지 캐시)을 구분하는 방법은 하나였다 — 같은 커밋에만 있는 완전히 새로운 경로를 하나 만들어서 푸시하는 것.
- 그 새 경로가 정상 속도(100초)로 뜬다 → 최신 빌드가 실제로 프로덕션에 도달했다는 뜻 → 빌드는 문제없다.
- 반대로 같은 커밋에서 수정된 기존 파일만 여전히 옛 내용이다 → 엣지 캐시 문제다.
실제로 새 경로는 100초 만에 정상 배포됐는데, 같은 커밋에서 수정된 기존 이미지 파일은 15분이 지나도 옛 버전이었다. cf-cache-status: HIT이었고, 응답 헤더는 max-age=0, must-revalidate인데도 재검증이 일어나지 않았다. 쿼리스트링 캐시 버스터(?v=2)도 무시됐다 — Workers 정적 자산은 캐시 키에서 쿼리스트링을 뺀다.
_headers로 막으려다 — 규칙 자체가 안 먹혔다
엣지 캐시라는 걸 확인했으니 _headers에 해당 경로용 Cache-Control: no-store 규칙을 추가했다. 그런데 실제로는 적용되지 않았다.
결정적 증거는 이거였다: 방금 처음 배포된, 아직 아무 요청도 없었던 새 파일을 확인했더니 cf-cache-status: MISS(엣지 캐시와 무관하게 오리진에서 갓 온 응답)였는데도, no-store가 아니라 기본값인 public, max-age=0, must-revalidate를 받고 있었다. 여러 경로에서 같은 결과였다.
/_headers 파일 자체는 인식된다 — /_headers 경로로 직접 요청하면 404가 나온다(파일이 산출물에 포함됐다는 뜻). 하지만 그 안의 규칙만 무시된다. 공식 문서에는 Workers Static Assets도 _headers를 지원한다고 적혀 있고 별도 compatibility date 요건도 없어서, 정확히 왜 안 먹히는지는 원인 미규명으로 남겼다.
진짜 해결책 — 캐시를 이기려 하지 않았다
캐시 설정으로 이 문제를 풀려는 시도를 접고, 애초에 캐시가 낄 여지를 없애는 쪽으로 갔다. 산출물의 경로 자체에 날짜를 넣어서 매번 새 경로로 만들었다. 같은 slug를 재사용해 덮어쓰지 않으므로, “옛 캐시가 새 파일을 가리는” 상황 자체가 성립하지 않는다.
부수적으로 의미도 맞았다 — 그 산출물은 특정 시점의 가격 스냅샷이었으므로, 경로에 날짜가 붙는 게 오히려 정확한 표현이었다.
그리고 — 고정 경로 HTML은 원래 잘 갱신된다
이 사고들 때문에 “경로가 고정이면 캐시에 붙잡힌다”고 일반화하고 싶어질 수 있는데, 그건 사실이 아니었다. 경로가 고정인 다른 페이지의 텍스트만 바꿔서 푸시했더니 120초 만에 반영됐고 옛 문구는 하나도 안 남았다.
실제로 문제가 됐던 건 이미지 파일이었다 — 텍스트(HTML)는 문제없이 갱신됐다. 캐시 대책을 미리 넣어두려 하지 말고, 먼저 실제로 옛것이 나오는지 측정하는 게 맞다. 안 걸리는 경로에까지 방어 코드를 깔면 나중에 진짜 원인을 찾을 때 오히려 혼란만 늘어난다.
etag를 md5로 착각하지 마라
디버깅 과정에서 하나 더 걸렸다. Cloudflare가 주는 etag를 파일의 md5 해시라고 가정하고 “etag가 같으니 파일이 안 바뀐 것”이라고 판단했다가 틀렸다. Cloudflare의 etag는 md5가 아니다. 실제로 바뀌었는지 확인하려면 파일을 직접 내려받아 내용이나 크기를 대조해야 한다.
정리 — 이런 증상을 다시 보면
- 빌드 로그를 직접 열어봐라. “성공” 표시만 보지 말고, 생성된 파일 크기·타임스탬프가 최신 커밋과 실제로 일치하는지 숫자로 대조한다.
- Zone 캐시 Purge로도 안 풀리고 서빙 경로를 완전히 우회해도 안 풀리면 — 문제는 서빙이 아니라 빌드 단계에 있다.
- 원인이 빌드인지 엣지인지는 반증 테스트로 가른다 — 같은 커밋에 완전히 새로운 경로를 하나 넣어 푸시해서 그것부터 확인한다.
_headers의 Cache-Control을 만능이라고 믿지 마라. 최소한 이 환경에서는 새로 배포된 파일에도 적용 안 되는 걸 실측으로 확인했다.- 캐시를 설정으로 이기려 하기 전에, 경로를 매번 새로 만들 수 있는지부터 검토해라. 그게 되면 이 문제 자체가 사라진다.
자주 묻는 질문
Cloudflare Pages/Workers에서 배포는 성공인데 특정 페이지만 옛 내용이 나오면 뭘 먼저 봐야 하나요?
Deployments 탭의 '성공' 로그를 믿지 말고 빌드 상세 로그를 직접 열어보세요. 생성된 파일 크기·타임스탬프가 최신 커밋과 실제로 맞는지 숫자로 대조하는 게 첫 단계입니다. Zone 캐시를 Purge해도 안 풀리고 ASSETS 바인딩을 완전히 우회해도 옛 내용이 나온다면, 문제는 서빙이 아니라 빌드 단계에 있는 겁니다.
빌드 캐시 문제인지 엣지 캐시 문제인지 어떻게 구분하나요?
같은 커밋 안에 완전히 새로운 경로(그 배포에서 처음 생기는 파일)를 하나 넣어서 푸시해보세요. 그게 정상 속도로 뜨면 최신 빌드가 실제로 프로덕션에 도달한 것이므로 빌드는 문제없는 겁니다. 반대로 기존 경로 중 이번에 수정된 파일만 옛 내용이 나온다면 엣지 캐시입니다.
_headers 파일에 Cache-Control을 넣으면 확실히 적용되나요?
이 글을 쓴 시점 기준으로는 아닙니다. Workers Static Assets 배포에서 /_headers에 넣은 커스텀 Cache-Control 규칙이 실제로 무시되는 걸 실측으로 확인했습니다(방금 배포된 새 파일이 MISS 상태인데도 기본 캐시 헤더를 받음). 공식 문서는 지원한다고 나와 있어서 정확한 원인은 확인하지 못했습니다. 캐시를 확실히 피해야 하면 헤더 설정에 의존하지 말고 경로 자체를 매번 새로 만드는 걸 권합니다.
etag가 같으면 파일이 안 바뀐 건가요?
Cloudflare의 etag를 파일의 md5 해시라고 가정하면 안 됩니다. etag 비교만으로 '내용이 같다'고 판단했다가 오탐이 났습니다. 실제로 바뀌었는지 확인하려면 파일을 내려받아 내용이나 크기를 직접 비교하는 게 안전합니다.