프로그레시브 웹 앱의 핵심은 안정성입니다. 네트워크 상태가 좋지 않은 경우에도 애셋을 빠르게 로드하여 사용자의 참여를 유도하고 즉시 피드백을 제공할 수 있습니다. 어떻게 이런 일이 가능한가요? 서비스 워커 fetch
이벤트 덕분입니다.
가져오기 이벤트
fetch
이벤트를 사용하면 동일 출처 요청과 교차 출처 요청 모두에 대해 서비스 워커 범위에서 PWA가 만든 모든 네트워크 요청을 가로챌 수 있습니다. 탐색 및 애셋 요청 외에도 설치된 서비스 워커에서 가져오면 사이트의 첫 번째 로드 후 네트워크 호출 없이 페이지 방문을 렌더링할 수 있습니다.
fetch
핸들러는 URL과 HTTP 헤더를 포함한 앱의 모든 요청을 수신하고 앱 개발자가 요청을 처리하는 방법을 결정할 수 있도록 합니다.
서비스 워커는 요청을 네트워크로 전달하거나, 이전에 캐시된 응답으로 응답하거나, 새 응답을 만들 수 있습니다. 선택은 사용자님의 몫입니다. 다음 예를 참조하세요.
self.addEventListener("fetch", event => {
console.log(`URL requested: ${event.request.url}`);
});
요청에 응답
요청이 서비스 워커에 들어오면 두 가지 작업을 할 수 있습니다. 요청을 무시하여 네트워크로 전달되도록 하거나 요청에 응답할 수 있습니다. 서비스 워커 내에서 요청에 응답하면 사용자가 오프라인 상태일 때도 PWA에 반환되는 항목과 방식을 선택할 수 있습니다.
수신 요청에 응답하려면 다음과 같이 fetch
이벤트 핸들러 내에서 event.respondWith()
를 호출하세요.
// fetch event handler in your service worker file
self.addEventListener("fetch", event => {
const response = .... // a response or a Promise of response
event.respondWith(response);
});
respondWith()
를 동기적으로 호출해야 하며 Response 객체를 반환해야 합니다. 하지만 가져오기 이벤트 핸들러가 완료된 후에는 비동기 호출 내에서와 같이 respondWith()
을 호출할 수 없습니다. 전체 응답을 기다려야 하는 경우 응답과 함께 해결되는 Promise를 respondWith()
에 전달하면 됩니다.
대답 만들기
Fetch API를 사용하면 JavaScript 코드에서 HTTP 응답을 만들 수 있으며, 이러한 응답은 Cache Storage API를 사용하여 캐시되고 웹 서버에서 제공되는 것처럼 반환될 수 있습니다.
응답을 만들려면 상태 및 헤더와 같은 본문과 옵션을 설정하여 새 Response
객체를 만듭니다.
const simpleResponse = new Response("Body of the HTTP response");
const options = {
status: 200,
headers: {
'Content-type': 'text/html'
}
};
const htmlResponse = new Response("<b>HTML</b> content", options)
캐시에서 응답
서비스 워커에서 HTTP 응답을 제공하는 방법을 알았으니 이제 캐싱 스토리지 인터페이스를 사용하여 기기에 애셋을 저장할 차례입니다.
캐시 저장소 API를 사용하여 PWA에서 수신한 요청이 캐시에 있는지 확인하고, 있는 경우 respondWith()
에 응답할 수 있습니다.
이렇게 하려면 먼저 캐시 내에서 검색해야 합니다. 최상위 caches
인터페이스에서 사용할 수 있는 match()
함수는 출처의 모든 저장소 또는 열려 있는 단일 캐시 객체를 검색합니다.
match()
함수는 HTTP 요청 또는 URL을 인수로 수신하고 해당 키와 연결된 응답으로 확인되는 프로미스를 반환합니다.
// Global search on all caches in the current origin
caches.match(urlOrRequest).then(response => {
console.log(response ? response : "It's not in the cache");
});
// Cache-specific search
caches.open("pwa-assets").then(cache => {
cache.match(urlOrRequest).then(response => {
console.log(response ? response : "It's not in the cache");
});
});
캐싱 전략
브라우저 캐시에서만 파일을 제공하는 것이 모든 사용 사례에 적합한 것은 아닙니다. 예를 들어 사용자나 브라우저가 캐시를 삭제할 수 있습니다. 따라서 PWA의 애셋을 제공하기 위한 자체 전략을 정의해야 합니다.
하나의 캐싱 전략으로 제한되지 않습니다. URL 패턴마다 다른 항목을 정의할 수 있습니다. 예를 들어 최소 UI 애셋에 대한 전략, API 호출에 대한 전략, 이미지 및 데이터 URL에 대한 전략을 각각 하나씩 사용할 수 있습니다. 이렇게 하려면 ServiceWorkerGlobalScope.onfetch
에서 event.request.url
를 읽고 정규식이나 URL 패턴을 통해 파싱합니다. (작성 시점에는 일부 플랫폼에서 URL 패턴이 지원되지 않습니다.)
가장 일반적인 전략은 다음과 같습니다.
- 캐시 우선
- 먼저 캐시된 응답을 검색하고 찾지 못하면 네트워크로 대체합니다.
- 네트워크 우선
- 먼저 네트워크에서 응답을 요청하고 반환된 응답이 없으면 캐시에서 응답을 확인합니다.
- 재검증 중 오래된 콘텐츠
- 백그라운드에서 최신 버전을 요청하고 다음번에 애셋이 요청될 때를 위해 캐시에 저장하면서 캐시에서 응답을 제공합니다.
- 네트워크 전용
- 항상 네트워크의 응답으로 회신하거나 오류가 발생합니다. 캐시가 참조되지 않습니다.
- 캐시 전용
- 항상 캐시에서 응답하거나 오류가 발생합니다. 네트워크는 절대 참조되지 않습니다. 이 전략을 사용하여 제공될 애셋은 요청되기 전에 캐시에 추가되어야 합니다.
캐시 우선
이 전략을 사용하면 서비스 워커가 캐시에서 일치하는 요청을 찾고 캐시된 경우 해당 응답을 반환합니다. 그렇지 않으면 네트워크에서 응답을 가져옵니다 (선택적으로 향후 호출을 위해 캐시 업데이트). 캐시 응답도 네트워크 응답도 없는 경우 요청에 오류가 발생합니다. 네트워크로 이동하지 않고 애셋을 제공하는 것이 더 빠른 경향이 있으므로 이 전략에서는 최신성보다 성능을 우선시합니다.
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// It can update the cache to serve updated content on the next request
return cachedResponse || fetch(event.request);
}
)
)
});
네트워크 우선
이 전략은 캐시 우선 전략의 미러입니다. 요청이 네트워크에서 처리될 수 있는지 확인하고, 처리될 수 없는 경우 캐시에서 가져오려고 시도합니다. 캐시 우선과 같습니다. 네트워크 응답도 캐시 응답도 없는 경우 요청에 오류가 발생합니다. 네트워크에서 응답을 가져오는 것이 캐시에서 가져오는 것보다 일반적으로 느리므로 이 전략에서는 성능 대신 업데이트된 콘텐츠를 우선시합니다.
self.addEventListener("fetch", event => {
event.respondWith(
fetch(event.request)
.catch(error => {
return caches.match(event.request) ;
})
);
});
재검증하는 동안 오래된 콘텐츠
재검증하는 동안 오래된 콘텐츠 제공 전략은 캐시된 응답을 즉시 반환한 다음 네트워크에서 업데이트를 확인하고 업데이트가 발견되면 캐시된 응답을 대체합니다. 이 전략은 항상 네트워크 요청을 합니다. 캐시된 리소스가 발견되더라도 다음 요청에서 업데이트된 버전을 사용하기 위해 캐시에 있는 항목을 네트워크에서 수신한 항목으로 업데이트하려고 하기 때문입니다. 따라서 이 전략을 사용하면 캐시 우선 전략의 빠른 제공을 활용하고 백그라운드에서 캐시를 업데이트할 수 있습니다.
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
const networkFetch = fetch(event.request).then(response => {
// update the cache with a clone of the network response
const responseClone = response.clone()
caches.open(url.searchParams.get('name')).then(cache => {
cache.put(event.request, responseClone)
})
return response
}).catch(function (reason) {
console.error('ServiceWorker fetch failed: ', reason)
})
// prioritize cached response over network
return cachedResponse || networkFetch
}
)
)
})
네트워크 전용
네트워크 전용 전략은 서비스 워커나 캐시 스토리지 API가 없는 브라우저의 동작과 유사합니다. 요청은 네트워크에서 가져올 수 있는 경우에만 리소스를 반환합니다. 이는 온라인 전용 API 요청과 같은 리소스에 유용할 때가 많습니다.
캐시만
캐시 전용 전략은 요청이 네트워크로 이동하지 않도록 합니다. 모든 수신 요청은 미리 채워진 캐시 항목으로 응답합니다. 다음 코드는 캐시 스토리지의 match
메서드와 함께 fetch
이벤트 핸들러를 사용하여 캐시에만 응답합니다.
self.addEventListener("fetch", event => {
event.respondWith(caches.match(event.request));
});
맞춤 전략
위는 일반적인 캐싱 전략이지만 서비스 워커와 요청 처리 방식은 사용자가 관리합니다. 이러한 방법이 요구사항에 맞지 않으면 직접 만드세요.
예를 들어 업데이트된 콘텐츠를 우선시하기 위해 제한 시간이 있는 네트워크 우선 전략을 사용할 수 있지만, 설정한 기준치 내에 응답이 표시되는 경우에만 가능합니다. 캐시된 응답을 네트워크 응답과 병합하고 서비스 워커에서 복잡한 응답을 빌드할 수도 있습니다.
애셋 업데이트
PWA의 캐시된 애셋을 최신 상태로 유지하는 것은 어려울 수 있습니다. 재검증하는 동안 만료된 콘텐츠를 제공하는 전략이 한 가지 방법이지만, 유일한 방법은 아닙니다. 업데이트 챕터에서는 앱의 콘텐츠와 애셋을 업데이트하는 다양한 기법을 알아봅니다.