-
오프라인 파일 하나가 404인데 왜 설치 전체가 실패할까Programming 2026. 10. 4. 00:49728x90반응형

install의 waitUntil에 addAll을 연결한 경우의 가상 예시입니다. 선택 자산의 실패 정책은 별도로 정합니다. 서비스 워커를 업데이트했는데 새 버전이 설치되지 않습니다. 내려받을 파일 목록에는 HTML, JavaScript, 그리고 없어도 화면을 쓸 수 있는 안내 이미지가 함께 들어 있습니다. 그 이미지 하나가 404라면
cache.addAll()에 넘긴 전체 목록과install이벤트의 Promise 연결부터 확인하세요. 필수 화면과 선택 이미지를 같은 실패 조건으로 묶었을 수 있습니다.404가 설치 실패로 이어지는 경로
addAll()은 URL 목록의 응답을 받아 캐시에 넣습니다. 정상 응답이 아닌 항목이 있으면 반환한 Promise가 거부됩니다. 404도 여기에 해당합니다. 이 Promise를install이벤트의waitUntil()에 넘겼다면, 실패가 설치 단계까지 전달됩니다. Cache.addAll(), 설치 단계의 waitUntil()다음은 실패 관계를 설명하기 위한 코드입니다.
/poster.png가 404를 반환한다고 가정합니다.self.addEventListener( "install", (event) => { event.waitUntil( caches.open("offline-v2") .then((cache) => { return cache.addAll([ "/index.html", "/app.js", "/poster.png", ]); }) ); } );이 구성은 “세 파일이 모두 준비돼야 설치 성공”이라는 조건을 만듭니다. 안내 이미지가 없어도 되는지 여부는 API가 판단하지 않습니다. 목록을 작성한 쪽에서 그 중요도를 정한 셈입니다.
같은
addAll()호출은 여러 개의 독립적인 성공을 모으는 방식으로 보지 않아야 합니다. 명세는 응답을 모은 뒤 캐시 변경을 배치로 처리합니다. 다만 앞선 실행이나 다른 코드가 이미 저장한 항목까지 전부 지워진다는 뜻은 아닙니다. 실패 후 캐시 이름이 보이거나 예전 파일이 남아 있어도 이번 호출 전체가 성공했다고 판단할 수 없습니다. Service Workers 명세의 addAll 알고리즘먼저 요청 URL을 봅니다
개발자 도구에서 서비스 워커의 설치 상태와 Network 요청을 함께 확인합니다. 예시의
/poster.png가 실제 배포 위치에 없는지, 경로를 잘못 썼는지, 인증 페이지로 보내지는지부터 구분합니다.특히
/poster.png는 서비스 워커 파일 옆의 이미지가 아니라 origin 루트 경로입니다. 반면poster.png같은 상대 URL은 worker의 위치를 기준으로 해석됩니다. 앱이 하위 경로에 배포됐다면 두 URL이 다른 파일을 가리킬 수 있습니다. addAll 요청 URL의 기준응답 상태가 200이어도 내용이 원하는 파일인지 확인해야 합니다. 없는 JavaScript 경로에 서버가 앱의 HTML을 돌려주는 구성이라면, HTTP 상태만으로는 올바른 스크립트를 받았다고 볼 수 없습니다. Network의 응답 본문과 Content-Type을 보고, 배포 산출물의 경로와 맞춰 보세요. 이것은 캐시에 넣을 파일 자체가 맞는지 확인하는 단계입니다.
외부 이미지라면 404만 찾다가 놓칠 수도 있습니다.
no-cors로 받은 불투명 응답은 상태가 0으로 노출되고addAll()의 성공 조건에 맞지 않습니다. CORS 설정이 필요한 파일을no-cors로 바꿔 해결하려고 하면 설치 실패 원인을 다른 형태로 남길 수 있습니다.없어도 되는 이미지를 별도로 처리합니다
앱이 HTML과 JavaScript만으로 기본 기능을 제공하고, 안내 이미지는 없어도 되는 경우를 생각해 보겠습니다. 이때 필수 목록을 먼저 저장하고, 선택 이미지의 실패는 기록한 뒤 설치를 계속하도록 구성할 수 있습니다.
const REQUIRED = [ "/index.html", "/app.js", ]; const OPTIONAL = ["/poster.png"]; self.addEventListener( "install", (event) => { event.waitUntil(prepare()); } ); async function prepare() { const cache = await caches.open( "offline-v2" ); await cache.addAll(REQUIRED); const requests = OPTIONAL.map( (url) => cache.add(url) ); const results = await Promise .allSettled(requests); results.forEach((result, i) => { const failed = result.status === "rejected"; if (failed) { console.warn( "Optional asset failed:", OPTIONAL[i] ); } }); }필수 목록의 실패는 그대로 바깥 Promise를 거부합니다. 선택 목록만
allSettled()로 각각의 성공과 실패를 받습니다. 이 예시 역시 브라우저에서 실행한 결과가 아니라, 실패를 어디까지 전달할지 보여 주는 구조입니다. Cache.add(), Promise.allSettled()선택이라고 분리했다면 화면에서도 그 파일이 없을 때의 처리가 있어야 합니다. 이미지를 생략해도 안내 문구와 버튼을 사용할 수 있는지 봅니다. 반대로 CSS나 폰트가 빠지면 화면을 읽기 어려운 앱이라면 그것을 편의상 선택 항목으로 내려서는 안 됩니다. 선택 여부는 확장자가 아니라 실제 화면의 동작으로 결정합니다.
오류를 삼켜서 설치시키면 무엇을 잃을까
처음 코드 맨 뒤에
.catch(() => {})를 붙이면 거부된 Promise를 성공으로 바꾸기 쉽습니다. 그러면 설치는 넘어가더라도 필수 파일이 준비됐다는 조건이 사라집니다. 온라인에서 첫 화면이 열려 문제가 해결된 것처럼 보이다가, 오프라인 재방문에서 빈 화면을 만날 수 있습니다.오류를 처리하려면 무엇을 실패로 남길지 먼저 정해야 합니다. 필수 파일 누락은 설치 실패로 유지하고, 선택 파일 누락은 URL을 기록하는 식입니다. 설치를 무조건 통과시키는 코드는 실패 원인을 고치는 작업과 다릅니다.
마지막 확인은 새 worker의 설치 표시만 보지 말고, 해당 버전이 제어하는 페이지에서 오프라인으로 기본 동작을 수행하는 것입니다. 기존 worker가 있는 업데이트 상황과 처음 방문한 상황을 나누고, 누락시킨 선택 이미지 없이도 내용을 읽을 수 있는지 확인하세요. 이 글의 캐시 코드는 설치 부분만 다루므로, 저장한 응답을 실제 요청에 반환하는
fetch처리도 앱에 갖춰져 있어야 합니다.자료 확인: 2026년 9월 28일. 먼저 실패 URL을 고치고, 다음으로 그 파일이 설치를 막아야 하는 필수 항목인지 판단하면 됩니다.
728x90반응형'Programming' 카테고리의 다른 글
Google Takeout 파일을 받으면 계정을 그대로 복원할 수 있을까 (0) 2026.10.06 웹앱을 배포했는데 열린 탭만 옛날 버전인 이유 (0) 2026.10.05 다른 탭에 변경을 알렸는데 새로 연 탭은 모르는 이유 (0) 2026.10.04 탭 두 개가 같은 작업을 동시에 할 때: Web Locks의 범위 (0) 2026.10.04 복사 버튼은 눌렀는데 클립보드가 안 바뀌는 이유 (0) 2026.10.04