서빙

프로그레시브 웹 앱의 핵심은 안정성입니다. 프로그레시브 웹 앱은 애셋을 빠르게 로드하여 네트워크 상태가 좋지 않은 경우에도 사용자의 참여를 유도하고 즉각적인 의견을 제공할 수 있습니다. 어떻게 이런 일이 가능한가요? 서비스 워커 fetch 이벤트 덕분입니다.

Browser Support

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Source

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()를 호출할 수 없습니다(예: 비동기 호출 내에서). 전체 응답을 기다려야 하는 경우 Response로 확인되는 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
      }
    )
  )
})

네트워크 전용

네트워크 전용 전략은 서비스 워커 또는 Cache Storage API 없이 브라우저가 작동하는 방식과 유사합니다. 요청은 네트워크에서 가져올 수 있는 경우에만 리소스를 반환합니다. 이는 온라인 전용 API 요청과 같은 리소스에 유용합니다.

네트워크 전용 전략

캐시만

캐시 전용 전략을 사용하면 요청이 네트워크로 전송되지 않습니다. 모든 수신 요청에는 사전 채워진 캐시 항목으로 응답됩니다. 다음 코드는 캐시 저장소의 match 메서드와 함께 fetch 이벤트 핸들러를 사용하여 캐시만 응답합니다.

self.addEventListener("fetch", event => {
   event.respondWith(caches.match(event.request));
});

캐시 전용 전략입니다.

맞춤 전략

위의 내용은 일반적인 캐싱 전략이지만 서비스 워커와 요청이 처리되는 방식은 개발자가 담당합니다. 이러한 방법이 필요에 맞지 않으면 직접 만드세요.

예를 들어 시간 제한이 있는 네트워크 우선 전략을 사용하여 업데이트된 콘텐츠에 우선순위를 둘 수 있지만, 응답이 설정한 기준점 내에 표시되는 경우에만 가능합니다. 캐시된 응답을 네트워크 응답과 병합하고 서비스 워커에서 복잡한 응답을 빌드할 수도 있습니다.

저작물 업데이트

PWA의 캐시된 애셋을 최신 상태로 유지하는 것은 쉽지 않을 수 있습니다. 비활성 상태에서 재검증 전략을 사용하는 것이 한 가지 방법이지만 유일한 방법은 아닙니다. 업데이트 챕터에서는 앱의 콘텐츠와 애셋을 업데이트하는 다양한 기법을 알아봅니다.

리소스