PWA를 안정적이고 설치 가능하며 기능이 뛰어나게 만드는 기술을 최대한 활용하도록 애플리케이션을 설계하려면 애플리케이션과 제약 조건을 이해하고 두 가지 모두에 적합한 아키텍처를 선택하는 것이 가장 중요합니다.
SPA와 MPA 비교
현재 웹 개발에는 단일 페이지 앱(SPA)과 멀티페이지 앱(MPA)이라는 두 가지 기본 아키텍처 패턴이 있습니다.
싱글페이지 앱은 클라이언트 측 JavaScript가 앱에서 가져오거나 제공한 데이터를 기반으로 페이지의 HTML 렌더링의 대부분 또는 전부를 제어하도록 정의됩니다. 앱은 브라우저의 내장 탐색을 재정의하여 라우팅 및 뷰 처리 기능으로 대체합니다.
멀티페이지 앱은 일반적으로 브라우저에 직접 전송되는 사전 렌더링된 HTML을 보유하고 있으며, 브라우저에서 HTML 로드를 완료한 후 클라이언트 측 JavaScript로 향상되는 경우가 많습니다. 또한 브라우저의 내장 탐색 메커니즘을 사용하여 후속 뷰를 표시합니다.
두 아키텍처 모두 PWA를 만드는 데 사용할 수 있습니다.
각 모델마다 장단점이 있으며 사용 사례와 맥락에 적합한 모델을 선택하는 것이 사용자에게 빠르고 안정적인 환경을 제공하는 데 중요합니다.
단일 페이지 앱
- 대부분 원자적 인페이지 업데이트입니다.
- 시작 시 로드되는 클라이언트 측 종속 항목
- 캐시를 사용하기 때문에 후속 로드는 빠릅니다.
- 초기 로드 비용이 높음
- 성능은 기기 하드웨어와 네트워크 연결에 따라 다릅니다.
- 앱 복잡성이 추가로 필요합니다.
다음과 같은 경우 단일 페이지 앱이 적합한 아키텍처입니다.
- 사용자 상호작용은 주로 동일한 페이지에 표시되는 상호 연결된 데이터의 원자적 업데이트를 중심으로 이루어집니다(예: 실시간 데이터 대시보드 또는 동영상 편집 앱).
- 애플리케이션에 클라이언트 측 전용 초기화 종속 항목이 있습니다(예: 시작 비용이 지나치게 높은 서드 파티 인증 제공업체).
- 뷰를 로드하는 데 필요한 데이터는 연결된 하드웨어의 컨트롤 표시와 같은 특정 클라이언트 측 전용 컨텍스트를 사용합니다.
- 앱이 작고 간단하여 크기와 복잡성이 위에 나열된 단점에 영향을 미치지 않습니다.
다음과 같은 경우에는 SPA가 적절한 아키텍처가 아닐 수 있습니다.
- 초기 로드 성능은 필수적입니다. SPA는 일반적으로 로드할 대상과 표시 방법을 결정하기 위해 더 많은 JavaScript를 로드해야 합니다. 이 JavaScript의 파싱 및 실행 시간은 콘텐츠 검색과 결합하여 렌더링된 HTML을 전송하는 것보다 느립니다.
- 앱이 대부분 저전력 또는 중간 전력 기기에서 실행됩니다. SPA는 렌더링에 JavaScript를 사용하므로 사용자 환경은 MPA에서보다 특정 기기의 성능에 훨씬 더 크게 좌우됩니다.
SPA는 브라우저의 내장 탐색을 라우팅으로 대체해야 하므로 현재 뷰를 효율적으로 업데이트하고, 탐색 변경사항을 관리하고, 브라우저에서 처리할 이전 뷰를 정리하는 데 최소한의 복잡성이 필요합니다. 따라서 SPA는 전반적으로 유지 관리가 더 어렵고 사용자 기기에 더 많은 부하를 줍니다.
다중 페이지 앱
- 대부분 전체 페이지 업데이트입니다.
- 초기 렌더링 속도가 중요합니다.
- 클라이언트 측 스크립팅은 개선될 수 있습니다.
- 보조 뷰에는 다른 서버 호출이 필요합니다.
- 컨텍스트는 뷰 간에 전달되지 않습니다.
- 서버 또는 사전 렌더링이 필요합니다.
멀티페이지 앱은 다음과 같은 경우에 적합한 아키텍처입니다.
- 사용자 상호작용은 주로 단일 데이터 보기와 선택적 컨텍스트 기반 데이터(예: 뉴스 또는 전자상거래 앱)를 중심으로 이루어집니다.
- 초기 렌더링 속도가 중요합니다. 이미 렌더링된 HTML을 브라우저로 전송하는 것이 JavaScript 기반 대안을 로드, 파싱, 실행한 후 데이터 요청에서 HTML을 조합하는 것보다 빠르기 때문입니다.
- 클라이언트 측 상호작용 또는 컨텍스트는 예를 들어 렌더링된 페이지에 프로필을 레이어링하거나 클라이언트 측 컨텍스트에 종속된 보조 구성요소를 추가하는 것과 같이 초기 로드 후에 개선사항으로 포함될 수 있습니다.
다음과 같은 경우 MPA는 아키텍처로 적합하지 않을 수 있습니다.
- JavaScript 또는 CSS를 다시 다운로드하고 다시 파싱하고 다시 실행하는 것은 비용이 많이 듭니다. 이 단점은 서비스 워커를 사용하는 PWA에서 완화됩니다.
- 사용자 위치와 같은 클라이언트 측 컨텍스트는 뷰 간에 원활하게 전달되지 않으며 이러한 컨텍스트를 다시 가져오는 데 비용이 많이 들 수 있습니다. 뷰 간에 캡처 및 검색하거나 다시 요청해야 합니다.
개별 뷰는 서버에 의해 동적으로 렌더링되거나 액세스하기 전에 사전 렌더링되어야 하므로 호스팅 또는 데이터 복잡성이 추가될 수 있습니다.
어떤 것을 선택해야 하나요?
이러한 장단점이 있더라도 두 아키텍처 모두 PWA를 만드는 데 유효합니다. 필요에 따라 앱의 여러 부분에 MPA 아키텍처와 SPA 아키텍처를 혼합할 수도 있습니다. 예를 들어 스토어 등록정보는 MPA 아키텍처를 따르고 결제 흐름은 SPA 아키텍처를 따르도록 할 수 있습니다.
선택사항과 관계없이 다음 단계는 서비스 워커를 가장 효과적으로 사용하여 최고의 환경을 제공하는 방법을 이해하는 것입니다.
서비스 워커의 강력한 기능
서비스 워커는 캐시된 응답과 네트워크 응답의 기본 라우팅 및 전송 외에도 많은 기능을 제공합니다. 사용자의 환경과 실적을 개선할 수 있는 복잡한 알고리즘을 만들 수 있습니다.
서비스 워커에 포함됨(SWI)
서비스 워커를 사이트 아키텍처의 필수 부분으로 사용하는 새로운 패턴은 서비스 워커 포함(SWI)입니다. SWI는 개별 애셋(일반적으로 HTML 페이지)을 캐싱 요구사항에 따라 조각으로 나눈 후 서비스 워커에서 다시 꿰매어 일관성, 성능, 안정성을 개선하고 캐시 크기를 줄입니다.
이 이미지는 샘플 웹페이지입니다. 페이지를 다음과 같이 5가지 섹션으로 나눕니다.
- 전체 레이아웃
- 전역 헤더 (상단의 어두운 막대)입니다.
- 콘텐츠 영역(왼쪽 가운데 선 및 이미지)
- 사이드바(오른쪽 가운데에 있는 중간 어두운 색상의 긴 막대)
- 바닥글(어두운 하단 막대)
전체 레이아웃
전체 레이아웃은 자주 변경되지 않으며 종속 항목이 없습니다. 미리 캐싱에 적합합니다.
머리글 및 바닥글
전체 헤더와 바닥글은 상단 메뉴 및 사이트 바닥글과 같은 항목을 포함하며, 특별한 문제가 있습니다. 즉, 페이지를 전체적으로 캐시해야 하는 경우 주어진 페이지가 캐시된 시기에 따라 페이지가 로드되는 중에 변경될 수 있습니다.
이를 분리하고 콘텐츠와 별개로 캐시하면 캐시되는 시점에 관계없이 사용자가 항상 동일한 버전을 가져올 수 있습니다. 자주 업데이트되지 않으므로 미리 캐시하기에도 적합합니다. 하지만 사이트의 CSS 및 JavaScript와 같은 종속 항목이 있습니다.
CSS 및 JavaScript
사전 캐시된 자산과 마찬가지로, 서비스 워커를 업데이트하지 않고도 증분 업데이트를 허용하기 위해 사이트의 CSS와 JavaScript를 오래된 상태로 캐시하는 동시에 유효성을 다시 검사하는 전략을 사용하는 것이 이상적입니다. 하지만 서비스 워커가 새로운 전역 헤더 또는 바닥글로 업데이트될 때마다 최소 버전으로 유지되어야 합니다. 따라서 서비스 워커가 설치될 때 캐시도 최신 버전의 애셋으로 업데이트해야 합니다.
콘텐츠 영역
다음은 콘텐츠 영역입니다. 업데이트 빈도에 따라 네트워크 우선 또는 재검증 중 비활성 상태가 좋은 전략입니다. 이전에 논의한 바와 같이 이미지는 캐시 우선 전략으로 캐시되어야 합니다.
사이드바
마지막으로, 메뉴 콘텐츠에 태그 및 관련 항목과 같은 부차적인 콘텐츠가 포함되어 있다고 가정할 경우 네트워크에서 데이터를 가져오는 것은 중요하지 않습니다. 이 경우 비활성 및 재검증 전략이 효과가 있습니다.
이제 위의 내용을 모두 살펴본 후 섹션별 캐싱은 싱글페이지 앱에서만 할 수 있다고 생각할 수 있습니다. 하지만 일부 고급 서비스 워커 기능을 사용하여 서비스 워커에서 edge side includes 또는 server side includes에서 영감을 받은 패턴을 채택하면 어느 아키텍처에서든 이를 실행할 수 있습니다.
체험해 보기
다음 Codelab에서 서비스 워커를 사용해 볼 수 있습니다.
스트리밍 응답
이전 페이지는 SPA 환경에서 앱 셸 모델을 사용하여 만들 수 있습니다. 여기서 앱 셸은 캐시된 후 제공되고 콘텐츠는 클라이언트 측에서 로드됩니다. Streams API가 도입되고 광범위하게 사용 가능해지면서 앱 셸과 콘텐츠를 모두 서비스 워커에서 결합하고 브라우저로 스트리밍할 수 있게 되었습니다. 따라서 앱 셸의 캐싱 유연성과 MPA의 속도를 모두 활용할 수 있습니다.
이는 다음과 같은 이유 때문입니다.
- 스트림은 비동기식으로 빌드될 수 있으며, 이를 통해 스트림의 여러 부분을 다른 소스에서 가져올 수 있습니다.
- 스트림 요청자는 전체 항목이 완료될 때까지 기다리지 않고 첫 번째 데이터 청크를 사용할 수 있는 즉시 응답 작업을 시작할 수 있습니다.
- 브라우저를 포함하여 스트리밍에 최적화된 파서는 스트림이 완료되기 전에 점진적으로 스트림의 콘텐츠를 표시하여 인지되는 응답 성능을 가속화할 수 있습니다.
스트림의 이러한 세 가지 속성 덕분에 스트리밍을 중심으로 빌드된 아키텍처는 일반적으로 스트리밍을 사용하지 않는 아키텍처보다 인지된 성능이 더 빠릅니다.
Streams API는 복잡하고 하위 수준이므로 작업하기가 어려울 수 있습니다. 다행히 서비스 워커의 스트리밍 응답을 설정하는 데 도움이 되는 Workbox 모듈이 있습니다.
도메인, 출처, PWA 범위
서비스 워커, 스토리지, 설치된 PWA의 창을 비롯한 웹 워커는 모두 웹에서 가장 중요한 보안 메커니즘 중 하나인 동일 출처 정책에 의해 관리됩니다. 동일한 출처 내에서 권한이 부여되고, 데이터를 공유할 수 있으며, 서비스 워커가 다른 클라이언트와 통신할 수 있습니다. 동일한 출처 외부에서는 권한이 자동으로 부여되지 않으며 데이터가 격리되어 서로 다른 출처 간에 액세스할 수 없습니다.
동일 출처 정책
프로토콜, 포트, 호스트가 동일한 경우 두 URL은 정확한 출처가 있는 것으로 정의됩니다.
예를 들어 https://squoosh.app
와 https://squoosh.app/v2
는 출처가 동일하지만 http://squoosh.app
, https://squoosh.com
, https://app.squoosh.app
, https://squoosh.app:8080
는 출처가 다릅니다. 자세한 내용과 예시는 동일 출처 정책 MDN 참조를 확인하세요.
호스트를 변경하는 방법은 하위 도메인을 변경하는 것만이 아닙니다. 각 호스트는 최상위 도메인(TLD), 보조 수준 도메인(SLD), 0개 이상의 라벨(하위 도메인이라고도 함)로 구성되며, 라벨은 점으로 구분되고 URL에서 오른쪽에서 왼쪽으로 읽습니다. 항목을 변경하면 호스트가 달라집니다.
창 관리 모듈에서 사용자가 설치된 PWA와 다른 출처로 이동할 때 인앱 브라우저가 어떻게 표시되는지 이미 살펴봤습니다.
웹사이트의 TLD와 SLD는 동일하지만 라벨이 다르더라도 이 인앱 브라우저가 다른 출처로 간주되므로 표시됩니다.
웹 브라우징 컨텍스트에서 출처의 주요 측면 중 하나는 저장소와 권한이 작동하는 방식입니다. 하나의 출처는 그 안에 있는 모든 콘텐츠와 PWA 간에 다음을 비롯한 많은 기능을 공유합니다.
- 스토리지 할당량 및 데이터(IndexedDB, 쿠키, 웹 저장소, 캐시 저장소)
- 서비스 워커 등록
- 부여되거나 거부된 권한 (예: 웹 푸시, 위치정보, 센서)
- 웹 푸시 등록
한 출처에서 다른 출처로 이동하면 이전의 모든 액세스 권한이 취소되므로 권한을 다시 부여해야 하며 PWA는 저장소에 저장된 모든 데이터에 액세스할 수 없습니다.