Mainline 是一家线上服装零售商,经营大牌时装设计师品牌。这家英国公司委托其内部专家团队与关键合作伙伴进行战略性整合,以便为所有用户提供顺畅的购物体验。Mainline 通过 7 个定制的区域性网站和一个应用,在 100 多个国家/地区开展市场推广,将继续确保电商服务能够与竞争对手相媲美。
挑战
Mainline Menswear 的目标是为当前针对移动设备进行了优化的网站补充符合其“移动优先”愿景的渐进式功能,专注于适合移动设备的设计和功能,满足不断增长的智能手机市场需求。
解决方案
目标是构建并发布一款 PWA,对 Mainline 男装网站的原始移动设备适用版本进行补充,然后将统计信息与其混合移动应用(目前适用于 Android 和 iOS 应用)进行比较。
当应用启动并被 Mainline 男装一小部分用户使用后,他们就能够确定 PWA、应用和 Web 之间的关键统计信息之间的差异。
在将其网站转换为 PWA 时,Mainline 采取的方法是确保其为网站选择的框架(Nuxt.js,使用 Vue.js)能够适应未来变化,并让他们能够利用快速发展的网络技术。
成果
139%
PWA 中的每次会话浏览页数与网站中的每次会话浏览页数相比增幅。
161%
PWA 与网站相比的会话时长更长。
10%
PWA 中的跳出率比网页低
12.5%
PWA 平均订单价值增幅(与网站相比)
55%
PWA 的转化率高于网站。
243%
PWA 的每次会话收入高于网站。
技术层面的深入探讨
Mainline Menswear 使用 Nuxt.js 框架捆绑和呈现其网站,这是一个单页应用 (SPA)。
生成 Service Worker 文件
为了生成 Service Worker,Mainline Menswear 通过 nuxt/pwa
Workbox 模块的自定义实现添加了配置。
他们创建 nuxt/pwa
模块的原因是允许团队向 Service Worker 文件添加更多在使用标准版本时无法添加或出现问题的自定义内容。其中一项优化涉及网站的离线功能,例如提供默认离线网页和在离线状态下收集分析数据。
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 应用安装后,可从主屏幕启动,而无需浏览器进行干预。这可通过在 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, Workbox 将处理将网址添加到缓存、检查任何修订版本不匹配、更新以及使用 CacheFirst
策略提供预缓存文件的所有繁重工作。
workbox.precaching.precacheAndRoute(PRECACHE);
处理离线导航
在 Service Worker 激活并预缓存离线页面后,它将用于响应用户的离线导航请求。虽然 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);
演示
报告成功安装
除了主屏幕启动跟踪(在 Web 应用清单中使用 "start_url": "/?utm_source=pwa"
)之外,Web 应用还会通过监听 window
上的 appinstalled
事件来报告应用安装成功:
window.addEventListener('appinstalled', (evt) => {
ga('send', 'event', 'Install', 'Success');
});
向您的网站添加 PWA 功能可进一步提升客户的购物体验,并且会比 [平台专用] 应用更快上市。
Andy Hoyle,开发主管
总结
如需详细了解渐进式 Web 应用以及如何构建这些应用,请访问 web.dev 上的“渐进式 Web 应用”部分。
要详细了解渐进式 Web 应用案例研究,请浏览“案例研究”部分。