Mainline 是一家线上服装零售商,提供时尚界最知名的设计师品牌。这家位于英国的公司将其内部专家团队与关键合作伙伴进行战略性整合,为所有人提供顺畅的购物体验。Mainline 通过七个自定义的地区性网站和一个应用,在 100 多个国家/地区拥有市场影响力,并将继续确保其电子商务产品与竞争对手相抗衡。
挑战
Mainline Menswear 的目标是通过符合其“移动优先”愿景的渐进式功能来完善当前针对移动设备进行了优化的网站,同时着重于注重移动设备的设计和功能,以顺应不断增长的智能手机市场。
解决方案
目标是构建和发布一个 PWA,作为 Mainline Menswear 网站的原始移动友好版本的补充,然后将其统计数据与目前在 Android 和 iOS 上推出的混合移动应用进行比较。
该应用发布并被一小部分 Mainline Menswear 用户使用后,他们能够确定 PWA、应用和网站之间的关键统计数据差异。
Mainline 在将其网站转换为 PWA 时采用的方法是,确保为其网站选择的框架(利用 Vue.js 的 Nuxt.js)具有前瞻性,并让他们能够利用快速发展的 Web 技术。
结果
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
模块,是为了让该团队能够对服务工件文件进行更多自定义,而使用标准版本时无法进行自定义或会遇到问题。其中一种优化涉及网站的离线功能,例如在离线时投放默认离线页面和收集分析数据。
Web 应用清单详解
该团队生成了一个清单,其中包含适用于不同移动应用图标大小的图标,以及 name
、description
和 theme_color
等其他 Web 应用详细信息:
{
"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"
}
网页应用安装完毕后,即可从主屏幕启动,而无需浏览器的干扰。具体方法是在 Web 应用清单文件中添加 display
参数:
{
"display": "standalone"
}
最后但同样重要的是,该公司现在只需在清单的 start_url
字段中附加 utm_source
参数,即可轻松跟踪有多少用户从主屏幕访问其 Web 应用:
{
"start_url": "/?utm_source=pwa"
}
运行时缓存,以加快导航速度
若要优化网页速度并为回访用户提供更好的用户体验,必须为 Web 应用缓存。
在 Web 上缓存时,有许多不同的方法。该团队混合使用 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
仅缓存来自其 CDN 的图片,并且最多缓存 60 张图片。
请求的第 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,
}),
],
}),
);
提供离线功能
在安装并激活 Service Worker 后,系统会立即预缓存离线页面。为此,它们会创建一个包含所有离线依赖项的列表:离线 HTML 文件和离线 SVG 图标。
const OFFLINE_HTML = '/offline/offline.html';
const PRECACHE = [
{ url: OFFLINE_HTML, revision: '70f044fda3e9647a98f084763ae2c32a' },
{ url: '/offline/offline.svg', revision: 'efe016c546d7ba9f20aefc0afa9fc74a' },
];
然后,预缓存列表会馈送到 Workbox,后者会负责处理将网址添加到缓存、检查是否存在任何修订不一致的情况、更新以及使用 CacheFirst
策略分发预缓存文件的所有繁重工作。
workbox.precaching.precacheAndRoute(PRECACHE);
处理离线导航
服务工作线程激活并预缓存离线页面后,系统会使用该服务工作线程来响应用户的离线导航请求。虽然 Mainline Menswear 的 Web 应用是 SPA,但只有在网页重新加载、用户关闭并重新打开浏览器标签页,或者在离线状态下从主屏幕启动 Web 应用后,系统才会显示离线页面。
为此,Mainline Menswear 提供了一个回退机制,用于在 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"
)之外,该 Web 应用还会通过监听 window
上的 appinstalled
事件来报告成功的应用安装:
window.addEventListener('appinstalled', (evt) => {
ga('send', 'event', 'Install', 'Success');
});
为您的网站添加 PWA 功能有助于进一步提升客户的购物体验,并且相较于 [平台专用] 应用,PWA 的上市速度更快。
开发总监 Andy Hoyle
总结
如需详细了解渐进式 Web 应用及其构建方式,请前往 web.dev 上的“渐进式 Web 应用”部分。
如需阅读更多渐进式 Web 应用案例研究,请浏览案例研究部分。