PWA 프로젝트 Lasy Loading 적용기

이규현·2023년 9월 17일

기본적으로 React 애플리케이션이 처음 로딩될 때에는 모든 컴포넌트 코드가 로딩됩니다. 사용자가 메인페이지만 보고 싶다고 해도 다른 모든 페이지가 전부 로딩이 되야 볼 수 있는 것이죠. 그래서 React는 자연스럽게 첫 로딩시간이 오래 걸리게 되는데, 이런 문제를 해결하기 위한 방법 중에는 Lazy Loading이라는 기법이 있습니다.

Lazy Loading은 컴포넌트를 처음부터 로딩하지 않고, 그 컴포넌트가 실제로 필요할 때까지 로딩을 미루는 방식입니다. React에서는 React.lazy() 함수와 Suspense 컴포넌트를 함께 사용하여 Lazy Loading을 구현할 수 있습니다. 따라서 어렵지 않게 Lasy Loading을 적용할 수 있었습니다.

import React, { Suspense } from 'react';

const Component = React.lazy(() => import('./Component'));

function MyComponent() {
 return (
   <div>
     <Suspense fallback={<div>Loading...</div>}>
       <Component />
     </Suspense>
   </div>
 );
}

하지만 다른곳에서 문제가 생겼습니다. 기존 프로젝트는 이미 PWA를 적용중이였는데 Lazy Loading을 적용하고 나니 오프라인 기능에 문제가 생겼죠. 적용하기 이전에는 사용자가 온라인 상태에서 한 번이라도 애플리케이션에 접속하면, 모든 페이지가 캐싱되었습니다. 이후에는 오프라인 상태에서도 캐싱된 페이지들을 자유롭게 이용할 수 있었습니다.

그러나 Lazy Loading을 적용한 후에는 사용자가 접근한 페이지만 로드하기 때문에, 사용자가 접근한 페이지만 캐싱이 되었습니다. 따라서 사용자가 오프라인 상태에서 이전에 방문하지 않았던 페이지에 접근하려고 하면, 해당 페이지는 캐싱되지 않았기 때문에 접근에 문제가 생겼습니다.

이에 해결 방법을 찾던 중 서비스 워커의 메시징을 활용하면 사용자가 페이지에 접근하기 전에 해당 페이지가 캐싱되어 있는지 확인할 수 있다는 것을 알게 되었습니다. 그 해결 방법은 다음과 같습니다.

	if (
     !navigator.onLine &&
     'serviceWorker' in navigator &&
     navigator.serviceWorker.controller
  	 ) {
     const messageChannel = new MessageChannel();

브라우저가 오프라인 상태이고 서비스 워커가 설치되어 있을 경우, MessageChannel을 생성하여 서비스 워커와 메시지를 주고 받습니다.

MessageChannel 인터페이스는 두 개의 채널을 제공하는데, 각각을 port1port2라고 부릅니다. 이 두 포트는 서로 양방향 통신을 할 수 있는 메시지 채널을 형성합니다.

	Promise.resolve().then(() => {
       if (navigator.serviceWorker.controller) {
         navigator.serviceWorker.controller.postMessage(
           { action: 'cache-contains', url: urlToCheck },
           [messageChannel.port2],
         );
       }
     });
  • port2: 이 포트는 보통 메시지를 전송하는데 사용됩니다. 위에서는 서비스 워커로 메시지를 보내기 위해 navigator.serviceWorker.controller.postMessage 함수의 두 번째 매개변수로 전달됩니다.
  • 비동기 코드의 특성 상, 첫 번째 if (navigator.serviceWorker.controller) 검사와 Promise.resolve().then() 내의 검사 사이에 시간적인 간격이 있어, 다시 한번 navigator.serviceWorker.controller를 검사하였습니다.
	messageChannel.port1.onmessage = (event) => {
        if (event.data.hasMatch) {
          navigate(`url`);
        } else {
          alert('오프라인 상태입니다. 네트워크 연결을 확인해주세요.');
        }
      };
		}
  • port1: 이 포트는 보통 메시지를 수신하는 데 사용됩니다. messageChannel.port1.onmessage 콜백 함수는 서비스 워커로부터 메시지를 받아 해당 URL의 캐시 존재 여부를 확인하고, 캐시가 존재한다면 해당 페이지로 이동합니다. 그렇지 않다면, 사용자에게 오프라인 상태임을 알리는 알림을 표시합니다.

    캐시가 존재한다면 접근이 가능하지만 존재하지 않는다면 접근 불가하다.

적용 코드

// 서비스 워커 코드
self.addEventListener('message', (event) => {
  if (event.data.action === 'cache-contains') {
    caches
      .open(CACHE_NAME)
      .then((cache) => cache.match(event.data.url))
      .then((match) => {
        event.ports[0].postMessage({ hasMatch: !!match });
      });
	 }
});
// 페이지 접근 함수
function toURL() {
    const urlToCheck = `url`;

    if (
      !navigator.onLine &&
      'serviceWorker' in navigator &&
      navigator.serviceWorker.controller
    ) {
      const messageChannel = new MessageChannel();

      messageChannel.port1.onmessage = (event) => {
        if (event.data.hasMatch) {
          navigate(`url`);
        } else {
          alert('오프라인 상태입니다. 네트워크 연결을 확인해주세요.');
        }
      };

      Promise.resolve().then(() => {
        if (navigator.serviceWorker.controller) {
          navigator.serviceWorker.controller.postMessage(
            { action: 'cache-contains', url: urlToCheck },
            [messageChannel.port2],
          );
        }
      });
    } else {
      navigate(`url`);
    }
  }
profile
Front-end Engineer를 목표로 달리고 있습니다

0개의 댓글