Mainline은 패션계에서 가장 큰 디자이너 브랜드 이름을 제공하는 온라인 의류 소매업체입니다. 영국에 소재한 이 회사는 주요 파트너와 전략적으로 결합된 사내 전문가 팀을 통해 모든 사용자에게 원활한 쇼핑 환경을 제공하고 있습니다. 맞춤 제작된 7개 지역 웹사이트와 앱을 통해 100개가 넘는 국가에 진출한 Mainline은 전자상거래 제품이 경쟁업체와 경쟁할 수 있도록 계속해서 노력할 것입니다.
도전과제
Mainline Menswear의 목표는 성장하는 스마트폰 시장을 염두에 두고 모바일 친화적인 디자인과 기능에 중점을 두면서 '모바일 우선' 비전을 준수하는 진보적인 기능으로 현재의 모바일 최적화 웹사이트를 보완하는 것이었습니다.
솔루션
목표는 메인라인 남성 의류 웹사이트의 원래 모바일 친화적 버전을 보완하는 PWA를 빌드하고 출시한 후 통계를 현재 Android 및 iOS에서 사용할 수 있는 하이브리드 모바일 앱과 비교하는 것이었습니다.
앱이 출시되어 일부 메인라인 남성복 사용자들이 사용하기 시작하면 PWA, 앱, 웹 간의 주요 통계 차이를 파악할 수 있었습니다.
Mainline은 웹사이트를 PWA로 변환할 때 웹사이트에 선택한 프레임워크(Vue.js를 사용하는 Nuxt.js)가 미래에 대비하고 빠르게 변화하는 웹 기술을 활용할 수 있도록 하는 접근 방식을 취했습니다.
결과
139%
웹 대비 PWA의 세션당 페이지 수 증가
161%
웹보다 PWA의 세션 시간이 더 길다.
10%
웹보다 PWA의 이탈률이 낮음
12.5%
웹보다 PWA의 평균 주문 금액이 높음
55%
PWA의 전환율이 웹보다 높습니다.
243%
웹보다 PWA의 세션당 수익이 더 높습니다.
기술 심층 정보
Mainline Menswear는 Nuxt.js 프레임워크를 사용하여 단일 페이지 애플리케이션(SPA)인 사이트를 번들로 묶고 렌더링합니다.
서비스 워커 파일 생성
서비스 워커를 생성하기 위해 Mainline Menswear는 nuxt/pwa
Workbox 모듈의 맞춤 구현을 통해 구성을 추가했습니다.
nuxt/pwa
모듈을 포크한 이유는 팀이 표준 버전을 사용할 때 불가능하거나 문제가 발생한 맞춤설정을 서비스 워커 파일에 더 많이 추가할 수 있도록 하기 위해서였습니다.
이러한 최적화 중 하나는 사이트의 오프라인 기능과 관련이 있습니다. 예를 들어 기본 오프라인 페이지를 게재하고 오프라인 상태에서 애널리틱스를 수집하는 기능이 여기에 해당합니다.
웹 앱 매니페스트 분석
팀은 다양한 모바일 앱 아이콘 크기와 name
, description
, theme_color
과 같은 기타 웹 앱 세부정보의 아이콘이 포함된 매니페스트를 생성했습니다.
{
"name": "Mainline Menswear",
"short_name": "MMW",
"description": "Shop mens designer clothes with Mainline Menswear. Famous brands including Hugo Boss, Adidas, and Emporio Armani.",
"icons": [
{
"src": "/_nuxt/icons/icon_512.c2336e.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#107cbb"
}
웹 앱이 설치되면 브라우저를 방해하지 않고 홈 화면에서 실행할 수 있습니다. 웹 앱 매니페스트 파일에 display
매개변수를 추가하면 됩니다.
{
"display": "standalone"
}
마지막으로, 이제 매니페스트의 start_url
필드에 utm_source
매개변수를 추가하기만 하면 홈 화면에서 웹 앱을 방문하는 사용자 수를 쉽게 추적할 수 있습니다.
{
"start_url": "/?utm_source=pwa"
}
빠른 탐색을 위한 런타임 캐싱
웹 앱의 캐싱은 페이지 속도를 최적화하고 재사용자에게 더 나은 사용자 환경을 제공하기 위해 반드시 필요합니다.
웹에서 캐싱하는 방법에는 여러 가지가 있습니다. 이 팀은 클라이언트 측에서 애셋을 캐싱하기 위해 HTTP 캐시와 Cache API를 함께 사용합니다.
Cache API를 사용하면 Mainline Menswear에서 캐시된 애셋을 더 세부적으로 제어하여 각 파일 형식에 복잡한 전략을 적용할 수 있습니다. 이러한 작업은 설정하고 유지하기가 복잡하고 어려울 수 있지만 Workbox를 사용하면 이러한 복잡한 전략을 간편하게 선언하고 유지관리의 어려움을 완화할 수 있습니다.
CSS 및 JS 캐싱
CSS 및 JS 파일의 경우 팀은 이를 캐시하고 StaleWhileRevalidate
Workbox 전략을 사용하여 캐시보다 우선 제공하기로 했습니다. 이 전략을 사용하면 모든 Nuxt CSS 및 JS 파일을 빠르게 제공할 수 있으므로 사이트 성능이 크게 향상됩니다.
동시에 다음 방문을 위해 파일이 백그라운드에서 최신 버전으로 업데이트됩니다.
/* sw.js */
workbox.routing.registerRoute(
/\/_nuxt\/.*(?:js|css)$/,
new workbox.strategies.StaleWhileRevalidate({
cacheName: 'css_js',
}),
'GET',
);
Google 글꼴 캐싱
Google Fonts를 캐시하는 전략은 다음 두 가지 파일 유형에 따라 다릅니다.
@font-face
선언이 포함된 스타일시트입니다.- 기본 글꼴 파일 (위에서 언급한 스타일시트 내에서 요청)
// Cache the Google Fonts stylesheets with a stale-while-revalidate strategy.
workbox.routing.registerRoute(
/https:\/\/fonts\.googleapis\.com\/*/,
new workbox.strategies.StaleWhileRevalidate({
cacheName: 'google_fonts_stylesheets',
}),
'GET',
);
// Cache the underlying font files with a cache-first strategy for 1 year.
workbox.routing.registerRoute(
/https:\/\/fonts\.gstatic\.com\/*/,
new workbox.strategies.CacheFirst({
cacheName: 'google_fonts_webfonts',
plugins: [
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200],
}),
new workbox.expiration.ExpirationPlugin({
maxAgeSeconds: 60 * 60 * 24 * 365, // 1 year
maxEntries: 30,
}),
],
}),
'GET',
);
이미지 캐싱
이미지의 경우 Mainline Menswear는 두 가지 전략을 사용하기로 결정했습니다. 첫 번째 전략은 일반적으로 제품 이미지인 CDN에서 가져온 모든 이미지에 적용됩니다. 페이지의 이미지가 너무 많기 때문에
사용자의 기기 저장용량을 너무 많이 차지하지 않도록 의식합니다. 따라서 Workbox는 ExpirationPlugin
를 사용하여 최대 60개의 이미지와 함께 CDN에서만 제공되는 이미지를 캐싱하는 전략을 추가했습니다.
요청된 61번째(최신) 이미지가 1번째(최초) 이미지를 대체하므로 특정 시점에 60개 이하의 제품 이미지가 캐시됩니다.
workbox.routing.registerRoute(
({ url, request }) =>
url.origin === 'https://mainline-menswear-res.cloudinary.com' &&
request.destination === 'image',
new workbox.strategies.StaleWhileRevalidate({
cacheName: 'product_images',
plugins: [
new workbox.expiration.ExpirationPlugin({
// Only cache 60 images.
maxEntries: 60,
purgeOnQuotaError: true,
}),
],
}),
);
두 번째 이미지 전략은 출처에서 요청한 나머지 이미지를 처리합니다. 이러한 이미지는 전체 출처에서 매우 적고 작지만 안전을 위해 이러한 캐시된 이미지의 수는 60개로 제한됩니다.
workbox.routing.registerRoute(
/\.(?:png|gif|jpg|jpeg|svg|webp)$/,
new workbox.strategies.StaleWhileRevalidate({
cacheName: 'images',
plugins: [
new workbox.expiration.ExpirationPlugin({
// Only cache 60 images.
maxEntries: 60,
purgeOnQuotaError: true,
}),
],
}),
);
오프라인 기능 제공
오프라인 페이지는 서비스 워커가 설치되고 활성화된 직후 미리 캐시됩니다. 이를 위해 모든 오프라인 종속 항목(오프라인 HTML 파일 및 오프라인 SVG 아이콘)의 목록을 만듭니다.
const OFFLINE_HTML = '/offline/offline.html';
const PRECACHE = [
{ url: OFFLINE_HTML, revision: '70f044fda3e9647a98f084763ae2c32a' },
{ url: '/offline/offline.svg', revision: 'efe016c546d7ba9f20aefc0afa9fc74a' },
];
그런 다음 미리 캐시 목록이 Workbox에 제공되며, Workbox는 캐시에 URL을 추가하고, 버전 불일치를 확인하고, 업데이트하고, CacheFirst
전략으로 미리 캐시된 파일을 제공하는 모든 작업을 처리합니다.
workbox.precaching.precacheAndRoute(PRECACHE);
오프라인 탐색 처리
서비스 워커가 활성화되고 오프라인 페이지가 미리 캐시되면 사용자의 오프라인 탐색 요청에 응답하는 데 사용됩니다. Mainline Menswear의 웹 앱은 SPA이지만 오프라인 페이지는 페이지가 새로고침되거나 사용자가 브라우저 탭을 닫았다가 다시 열거나 오프라인 상태에서 홈 화면에서 웹 앱을 실행할 때만 표시됩니다.
이를 위해 메인라인 남성복은 미리 캐시된 오프라인 페이지를 사용하여 실패한 NavigationRoute
요청에 대한 대체 옵션을 제공했습니다.
const htmlHandler = new workbox.strategies.NetworkOnly();
const navigationRoute = new workbox.routing.NavigationRoute(({ event }) => {
const request = event.request;
// A NavigationRoute matches navigation requests in the browser, i.e. requests for HTML
return htmlHandler.handle({ event, request }).catch(() => caches.match(OFFLINE_HTML, {
ignoreSearch: true
}));
});
workbox.routing.registerRoute(navigationRoute);
데모
설치 성공 보고
웹 애플리케이션 매니페스트의 "start_url": "/?utm_source=pwa"
를 사용한 홈 화면 실행 추적 외에도 웹 앱은 window
에서 appinstalled
이벤트를 수신 대기하여 앱 설치 성공을 보고합니다.
window.addEventListener('appinstalled', (evt) => {
ga('send', 'event', 'Install', 'Success');
});
웹사이트에 PWA 기능을 추가하면 고객의 쇼핑 경험이 더욱 개선되고 [플랫폼별] 앱보다 더 빠르게 시장에 출시할 수 있습니다.
앤디 호일, 개발 책임자
결론
프로그레시브 웹 앱 및 빌드 방법에 관한 자세한 내용은 web.dev의 프로그레시브 웹 앱 섹션을 참고하세요.
프로그레시브 웹 앱 우수사례를 더 보려면 우수사례 섹션으로 이동하세요.