애플리케이션 셸 아키텍처를 사용한 웹 앱 즉시 로드

아디 오스마니
애디 오스마니
맷 곤트

애플리케이션 셸은 사용자 인터페이스를 지원하는 최소한의 HTML, CSS, 자바스크립트입니다. 애플리케이션 셸은 다음과 같아야 합니다.

  • 빠르게 로드
  • 캐시될 수 있음
  • 콘텐츠를 동적으로 표시

안정적으로 우수한 성능을 낼 수 있는 비결은 애플리케이션 셸입니다. 앱의 셸은 네이티브 앱을 빌드할 때 앱 스토어에 게시하는 코드 번들과 같다고 생각하면 됩니다. 이는 시작하는 데 필요한 부하이지만 전체가 아닐 수도 있습니다. UI를 로컬에 유지하고 API를 통해 콘텐츠를 동적으로 가져옵니다.

앱 셸에서 HTML, JS, CSS 셸과 HTML 콘텐츠 분리

배경

Alex Russell의 프로그레시브 웹 앱 도움말에서는 웹 앱을 사용 및 사용자 동의를 통해 점진적으로 변경하여 오프라인 지원, 푸시 알림, 홈 화면에 추가되는 기능을 갖춘 네이티브 앱과 유사한 환경을 제공하는 방법을 설명합니다. 그 차이는 서비스 워커의 기능 및 성능 이점과 캐싱 능력에 따라 크게 달라집니다. 따라서 속도에 집중할 수 있으므로 기본 애플리케이션에서 볼 수 있었던 것과 동일한 즉시 로드 및 정기적인 업데이트가 웹 앱에 제공될 수 있습니다.

이러한 기능을 최대한 활용하려면 웹사이트에 대한 새로운 사고 방식, 즉 애플리케이션 셸 아키텍처가 필요합니다.

서비스 워커 증강 애플리케이션 셸 아키텍처를 사용하여 앱을 구조화하는 방법을 자세히 알아보겠습니다. 클라이언트 측 렌더링과 서버 측 렌더링을 모두 살펴보고 지금 시도해 볼 수 있는 엔드 투 엔드 샘플을 공유하겠습니다.

요점을 강조하기 위해 아래 예에서는 이 아키텍처를 사용하는 앱의 첫 번째 로드를 보여줍니다. 화면 하단에 '앱을 오프라인으로 사용할 준비가 되었습니다'라는 토스트 메시지가 표시됩니다. 나중에 셸 업데이트를 사용할 수 있게 되면 사용자에게 새 버전으로 새로고침하도록 알릴 수 있습니다.

애플리케이션 셸용 DevTools에서 실행 중인 서비스 워커 이미지

서비스 워커란 무엇일까요?

서비스 워커는 웹페이지와는 별도로 백그라운드에서 실행되는 스크립트입니다. 서비스하는 페이지에서 생성된 네트워크 요청 및 서버의 푸시 알림을 비롯한 이벤트에 응답합니다. 서비스 워커의 수명은 의도적으로 짧습니다. 이벤트가 발생하면 절전 모드가 해제되어 이벤트를 처리해야 하는 동안에만 실행됩니다.

또한 서비스 워커에는 일반 브라우징 컨텍스트의 자바스크립트와 비교할 때 제한된 API 집합이 있습니다. 이는 웹의 작업자를 위한 표준입니다. 서비스 워커는 DOM에 액세스할 수 없지만 Cache API 등에 액세스할 수 있으며 Fetch API를 사용하여 네트워크 요청을 수행할 수 있습니다. IndexedDB APIpostMessage()는 서비스 워커와 서비스 워커가 제어하는 페이지 간의 데이터 지속성과 메시징에도 사용할 수 있습니다. 서버에서 전송된 푸시 이벤트를 통해 Notification API를 호출하여 사용자 참여를 늘릴 수 있습니다.

서비스 워커는 페이지에서 생성된 네트워크 요청을 가로채서 (서비스 워커에서 가져오기 이벤트를 트리거함) 네트워크에서 가져온 응답이나 로컬 캐시에서 검색하거나 프로그래밍 방식으로 생성된 응답을 반환할 수 있습니다. 사실상 브라우저에서 프로그래밍 가능한 프록시입니다. 좋은 점은 응답의 출처와 관계없이 서비스 워커의 개입이 없는 것처럼 웹페이지를 본다는 것입니다.

서비스 워커에 대해 자세히 알아보려면 서비스 워커 소개를 읽어보세요.

성능 이점

서비스 워커는 오프라인 캐싱에 효과적이지만 사이트 또는 웹 앱의 반복적 방문을 위한 즉각적인 로딩의 형태로 상당한 성능상 이점을 제공합니다. 애플리케이션 셸을 캐시하면 오프라인에서 작동하고 JavaScript를 사용하여 콘텐츠를 채울 수 있습니다.

재방문 시 네트워크 사용 없이도 화면에 의미 있는 픽셀을 표시할 수 있으며, 이는 콘텐츠의 출처가 네트워크입니다. 툴바와 카드를 즉시 표시한 다음 나머지 콘텐츠를 점진적으로 로드한다고 생각하면 됩니다.

실제 기기에서 이 아키텍처를 테스트하기 위해 WebPageTest.org에서 WebPageTest.org을 실행하여 아래와 같은 결과를 표시했습니다.

테스트 1: Chrome 개발자 도구를 사용하여 Nexus 5로 케이블에서 테스트

앱의 첫 번째 뷰는 네트워크의 모든 리소스를 가져와야 하며, 1.2초가 경과할 때까지 의미 있는 페인트를 달성하지 못합니다. 서비스 워커 캐싱 덕분에 반복 방문은 의미 있는 페인트를 달성하고 0.5초 안에 로드를 완전히 완료합니다.

케이블 연결을 위한 웹페이지 테스트 페인트 다이어그램

테스트 2: Chrome 개발자 버전의 Nexus 5로 3G에서 테스트

약간 느린 3G 연결로 샘플을 테스트할 수도 있습니다. 이번에는 유의미한 첫 페인트를 처음 방문할 때 2.5초가 걸립니다. 페이지가 완전히 로드되는 데 7.1초가 걸립니다. 서비스 워커 캐싱을 사용하면 반복 방문에서 의미 있는 페인트를 달성하고 0.8초 내에 로드를 완전히 완료합니다.

3G 연결용 웹페이지 테스트 페인트 다이어그램

다른 보기에서도 비슷한 사례가 표시됩니다. 애플리케이션 셸에서 유의미한 첫 페인트를 달성하는 데 걸리는 3초를 비교해 보세요.

웹페이지 테스트에서 첫 번째 보기의 페인트 타임라인

서비스 워커 캐시에서 동일한 페이지가 로드될 때 걸리는 0.9초입니다. 최종 사용자의 작업 시간을 2초 이상 줄일 수 있습니다.

웹페이지 테스트에서 반복 보기를 위한 페인트 타임라인

애플리케이션 셸 아키텍처를 사용하는 자체 애플리케이션에서도 비슷하고 안정적인 성능 이점을 얻을 수 있습니다.

서비스 워커로 인해 앱 구조화 방식을 재고해야 할까요?

서비스 워커는 애플리케이션 아키텍처에 약간의 미묘한 변화를 줍니다. 모든 애플리케이션을 HTML 문자열로 묶는 대신 AJAX 스타일로 작업을 처리하는 것이 좋습니다. 여기에서 항상 캐시되며 항상 네트워크 없이 부팅될 수 있는 셸과 정기적으로 새로고침되고 별도로 관리되는 콘텐츠가 있습니다.

이 분할의 영향은 큽니다. 처음 방문할 때 서버의 콘텐츠를 렌더링하고 클라이언트에 서비스 워커를 설치할 수 있습니다. 이후 방문에서는 데이터만 요청하면 됩니다.

점진적 개선은 어떤가요?

서비스 워커가 현재 모든 브라우저에서 지원되는 것은 아니지만, 애플리케이션 콘텐츠 셸 아키텍처는 모든 사용자가 콘텐츠에 액세스할 수 있도록 점진적 개선을 사용합니다. 샘플 프로젝트를 예로 들어보겠습니다.

아래에서 Chrome, Firefox Nightly 및 Safari에서 렌더링된 전체 버전을 확인할 수 있습니다. 제일 왼쪽을 보면 콘텐츠가 서비스 워커 없이 서버에서 렌더링되는 Safari 버전을 볼 수 있습니다. 오른쪽을 보면 서비스 워커에 의해 구동되는 Chrome 및 Firefox Nightly 버전이 보입니다.

Safari, Chrome, Firefox에 로드된 Application Shell의 이미지

이 아키텍처를 사용하는 것이 적당한 시기는 언제인가요?

애플리케이션 셸 아키텍처는 동적인 앱과 사이트에 가장 적합합니다. 사이트가 작고 정적인 경우 애플리케이션 셸이 필요하지 않으며 서비스 워커 oninstall 단계에서 전체 사이트를 간단히 캐시할 수 있습니다. 프로젝트에 가장 적합한 접근 방식을 사용하세요. 많은 JavaScript 프레임워크에서 이미 콘텐츠에서 애플리케이션 로직을 분할하도록 권장하고 있어 이 패턴을 더 간단하게 적용할 수 있습니다.

아직 이 패턴을 사용하는 프로덕션 앱이 있나요?

애플리케이션 셸 아키텍처는 전체 애플리케이션의 UI를 몇 가지만 변경하여 사용할 수 있으며 Google의 I/O 2015 프로그레시브 웹 앱 및 Google의 받은편지함과 같은 대규모 사이트에서 원활하게 작동했습니다.

Google 받은편지함 로드 이미지 서비스 워커를 사용하는 받은편지함을 보여줍니다.

오프라인 애플리케이션 셸은 성능이 크게 향상되었으며 제이크 아치볼드의 오프라인 위키백과 앱Flipkart Lite의 프로그레시브 웹 앱에서도 잘 시연되고 있습니다.

제이크 아치볼드의 Wikipedia 데모 스크린샷.

아키텍처 설명

최초 로드 경험의 목표는 사용자 화면에 의미 있는 콘텐츠를 최대한 빨리 표시하는 것입니다.

다른 페이지 처음 로드 및 로드

앱 셸을 사용한 첫 번째 로드 다이어그램

일반적으로 애플리케이션 셸 아키텍처는 다음과 같은 역할을 합니다.

  • 초기 로드의 우선순위를 지정하되, 서비스 워커가 애플리케이션 셸을 캐시하여 재방문 시 네트워크에서 셸을 다시 가져올 필요가 없도록 합니다.

  • 그 외 모든 항목은 지연 로드 또는 백그라운드로 로드합니다. 한 가지 좋은 옵션은 동적 콘텐츠에 리드스루 캐싱을 사용하는 것입니다.

  • 예를 들어 sw-precache와 같은 서비스 워커 도구를 사용하면 정적 콘텐츠를 관리하는 서비스 워커를 안정적으로 캐시하고 업데이트할 수 있습니다. (sw-precache에 대해서는 나중에 자세히 알아보겠습니다.)

목표 달성:

  • 서버는 클라이언트가 렌더링할 수 있는 HTML 콘텐츠를 전송하고 서비스 워커가 지원되지 않는 브라우저를 고려하여 향후 HTTP 캐시 만료 헤더를 사용합니다. 해시를 사용하여 파일 이름을 제공하여 애플리케이션 수명 주기 후반에 '버전 관리'와 간편한 업데이트를 모두 지원합니다.

  • 페이지는 문서 <head> 내의 <style> 태그에 인라인 CSS 스타일을 포함하여 애플리케이션 셸의 첫 번째 빠른 페인트를 제공합니다. 각 페이지는 현재 보기에 필요한 자바스크립트를 비동기식으로 로드합니다. CSS는 비동기식으로 로드될 수 없기 때문에 JavaScript가 파서 기반 및 동기식이 아니라 비동기식이므로 JavaScript를 사용하여 스타일을 요청할 수 있습니다. 또한 requestAnimationFrame()를 활용하여 빠른 캐시 적중이 발생하여 스타일이 우연히 주요 렌더링 경로의 일부가 되는 상황을 피할 수 있습니다. requestAnimationFrame()는 스타일이 로드되기 전에 첫 번째 프레임을 강제로 페인팅합니다. 또 다른 옵션은 Filament Group의 loadCSS와 같은 프로젝트를 사용하여 JavaScript를 통해 CSS를 비동기식으로 요청하는 것입니다.

  • 서비스 워커는 애플리케이션 셸의 캐시된 항목을 저장하므로 반복 방문 시 네트워크에서 업데이트를 사용할 수 없는 경우 셸이 서비스 워커 캐시에서 완전히 로드될 수 있습니다.

콘텐츠용 앱 셸

실제 구현

애플리케이션 셸 아키텍처, 클라이언트용 vanilla ES2015 JavaScript, 서버용 Express.js를 사용하여 완전히 작동하는 샘플을 작성했습니다. 물론 클라이언트 또는 서버 부분 (예: PHP, Ruby, Python)에 자체 스택을 사용하는 것을 막을 방법이 없습니다.

서비스 워커 수명 주기

애플리케이션 셸 프로젝트에서는 다음과 같은 서비스 워커 수명 주기를 제공하는 sw-precache를 사용합니다.

이벤트 작업
설치 애플리케이션 셸 및 기타 단일 페이지 앱 리소스를 캐시합니다.
활성화 오래된 캐시를 삭제합니다.
가져오기 URL에 대해 단일 페이지 웹 앱을 제공하고 애셋 및 사전 정의된 부분에 캐시를 사용합니다. 다른 요청에는 네트워크를 사용합니다.

서버 비트

이 아키텍처에서는 서버 측 구성요소 (여기서는 Express로 작성됨)가 콘텐츠와 프레젠테이션을 별도로 처리할 수 있어야 합니다. 콘텐츠를 HTML 레이아웃에 추가하여 페이지의 정적 렌더링을 만들 수도 있고, 개별적으로 게재되어 동적으로 로드될 수도 있습니다.

서버 측 설정이 데모 앱에 사용하는 설정과 크게 다를 수 있습니다. 이 웹 앱 패턴은 약간의 재설계가 필요하기는 하지만 대부분의 서버 설정에서 달성할 수 있습니다. 다음 모델이 매우 잘 작동하는 것으로 확인되었습니다.

앱 셸 아키텍처 다이어그램
  • 엔드포인트는 애플리케이션의 세 부분, 즉 사용자에게 표시되는 URL (색인/와일드 카드), 애플리케이션 셸 (서비스 워커), HTML 부분의 세 부분으로 정의됩니다.

  • 각 엔드포인트에는 핸들 바 부분과 뷰를 가져올 수 있는 handlebars 레이아웃을 가져오는 컨트롤러가 있습니다. 간단히 말해서 일부는 최종 페이지로 복사되는 HTML 청크인 뷰입니다. 참고: 고급 데이터 동기화를 수행하는 자바스크립트 프레임워크는 애플리케이션 셸 아키텍처로 포팅하기가 훨씬 더 쉬운 경우가 많습니다. 부분 데이터보다는 데이터 바인딩 및 동기화를 사용하는 경향이 있습니다.

  • 처음에는 콘텐츠가 포함된 정적 페이지가 사용자에게 게재됩니다. 이 페이지는 서비스 워커를 등록하며(지원되는 경우) 애플리케이션 셸과 종속된 모든 요소(CSS, JS 등)를 캐시합니다.

  • 그러면 앱 셸이 특정 URL의 콘텐츠에서 JavaScript to XHR을 사용하여 단일 페이지 웹 앱 역할을 합니다. XHR 호출은 /partials* 엔드포인트에 수행되어 해당 콘텐츠를 표시하는 데 필요한 HTML, CSS 및 JS의 작은 청크를 반환합니다. 참고: 여기에는 여러 가지 방법이 있으며 XHR은 그 중 하나일 뿐입니다. 일부 애플리케이션은 초기 렌더링을 위해 데이터를 인라인 (JSON을 사용할 수도 있음)하므로 평면화된 HTML 측면에서 '정적'이 아닙니다.

  • 서비스 워커를 지원하지 않는 브라우저에는 항상 대체 환경이 제공되어야 합니다. 데모에서는 기본 정적 서버 측 렌더링으로 다시 돌아가지만, 이는 많은 옵션 중 하나일 뿐입니다. 서비스 워커 측면은 캐시된 애플리케이션 셸을 사용하여 단일 페이지 애플리케이션 스타일 앱의 성능을 향상할 수 있는 새로운 기회를 제공합니다.

파일 버전 관리

파일 버전 관리 및 업데이트를 처리하는 방법에 대한 의문이 제기됩니다. 이는 애플리케이션별로 적용되며 옵션은 다음과 같습니다.

  • 네트워크를 먼저 사용하고 그렇지 않으면 캐시된 버전을 사용하세요.

  • 네트워크 전용이며 오프라인인 경우 실패합니다.

  • 이전 버전을 캐시하고 나중에 업데이트하세요.

애플리케이션 셸 자체의 경우, 서비스 워커를 설정할 때 캐시 우선 방식을 사용해야 합니다. 애플리케이션 셸을 캐시하지 않는 경우 아키텍처를 적절하게 채택하지 않은 것입니다.

도구

Google은 애플리케이션의 셸을 미리 캐시하거나 공통 캐싱 패턴을 처리하는 프로세스를 보다 쉽게 설정할 수 있도록 다양한 서비스 워커 도우미 라이브러리를 유지관리합니다.

Web Fundamentals의 서비스 워커 라이브러리 사이트 스크린샷

애플리케이션 셸에 sw-precache 사용

sw-precache를 사용하여 애플리케이션 셸을 캐시하면 파일 수정, 설치/활성화 질문, 앱 셸 가져오기 시나리오 관련 문제가 해결됩니다. sw-precache를 애플리케이션의 빌드 프로세스에 드롭하고 구성 가능한 와일드 카드를 사용하여 정적 리소스를 선택하세요. 서비스 워커 스크립트를 수동으로 직접 작성하는 대신, sw-precache가 캐시 우선 가져오기 핸들러를 사용하여 안전하고 효율적인 방식으로 캐시를 관리하는 스크립트를 생성하도록 하세요.

앱을 처음 방문하면 필요한 전체 리소스 집합의 사전 캐싱이 트리거됩니다. 앱 스토어에서 기본 앱을 설치하는 환경과 유사합니다. 사용자가 앱으로 돌아오면 업데이트된 리소스만 다운로드됩니다. 이 데모에서는 '앱 업데이트. 새로고침하여 새 버전으로 업데이트하세요.' 이 패턴을 사용하면 사용자가 최신 버전으로 새로고침할 수 있음을 간단하게 알릴 수 있습니다.

런타임 캐싱에 sw-toolbox 사용

리소스에 따라 다양한 전략으로 런타임 캐싱에 sw-toolbox를 사용합니다.

  • cacheFirst(이미지의 경우)와 N maxEntry라는 커스텀 만료 정책이 적용된 전용 명명된 캐시와 함께 사용됩니다.

  • 원하는 콘텐츠 최신 상태에 따라 networkFirst 또는 가장 빠른 API 요청의 경우 가장 빠른 것이 괜찮지만 자주 업데이트되는 특정 API 피드가 있는 경우 networkFirst를 사용하세요.

결론

애플리케이션 셸 아키텍처에는 여러 이점이 있지만 일부 애플리케이션에서만 의미가 있습니다. 모델은 아직 개발 단계이므로 이 아키텍처의 노력과 전반적인 성능상의 이점을 평가할 가치가 있습니다.

이 실험에서는 클라이언트와 서버 간의 템플릿 공유를 활용하여 두 개의 애플리케이션 레이어를 구축하는 작업을 최소화했습니다. 이를 통해 점진적인 개선을 계속 핵심 기능으로 할 수 있습니다.

이미 앱에서 서비스 워커 사용을 고려하고 있다면 아키텍처를 살펴보고 내 프로젝트에 적합한지 평가하세요.

검토자인 제프 포스닉, 폴 루이스, 알렉스 러셀, 세스 톰슨, 롭 도드슨, 테일러 새비지, 조 메들리와 같은 분께 감사드립니다.