Mainline 是一家线上服装零售商,提供时尚界最知名的设计师品牌。这家位于英国的公司将其内部专家团队与关键合作伙伴进行战略性整合,为所有人提供顺畅的购物体验。Mainline 通过 7 个定制的地区网站和应用,在 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)。
生成服务工件文件
为了生成 Service Worker,Mainline Menswear 通过 nuxt/pwa
Workbox 模块的自定义实现添加了配置。
之所以分叉 nuxt/pwa
模块,是为了让团队能够对服务工件文件进行更多自定义,而使用标准版本时无法进行自定义或会遇到问题。例如,针对网站的离线功能(例如,提供默认离线页面以及在离线状态下收集分析数据)就属于此类优化。
Web 应用清单详解
该团队生成一个清单,其中包含适用于不同移动应用图标大小的图标以及其他 Web 应用详细信息,如 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"
}
网页应用安装完毕后,即可从主屏幕启动,而无需浏览器的干扰。具体方法是在 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 应用后,系统才会显示离线页面。
为了实现这一点,Mainswear 通过预缓存的离线页面提供了一个回退机制,用于回退失败的 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);
演示
报告成功安装
除了主屏幕启动跟踪(在 Web 应用清单中使用 "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 应用案例研究,请浏览“案例研究”部分。