手机上打开慢,第一步不是跑分,而是先确定哪一类页面在拖后腿。首页、集合页、产品页吃掉资源的地方不一样:首页是首屏那张图和轮播,集合页是首屏一次渲染出来的卡片数量,产品页是变体、加购这类交互脚本。把三类页面混在一起看一个总分,只会得到一堆没法落地的结论。
为什么检查从移动端开始
先看一个你手里就有的数据:后台的流量来源和订单来源里,移动端占多少。这个比例决定了投入顺序。移动端占比高的店铺,桌面端的漂亮成绩没有意义,因为访客遇到的不是那个版本。

不同页面的瓶颈不一样(光算 · 示意图)
指标口径本身也是分开的。web.dev 对 Core Web Vitals 的评估要求是:在 75 分位的访问上达到阈值,并且移动端与桌面端分别统计(Web Vitals 指标说明)。也就是说移动端和桌面端的达标率本来就是两个数字,拿其中一个解释另一个,属于算错账。
三项指标各自衡量什么、在 Shopify 上通常由什么引起,以及基线该怎么建立,那篇写得比较细(三项指标各自对应什么体验)。这里只做一件事:按页面类型给出检查顺序和执行优先级。
首页:先处理首屏那一张图
首页的 LCP 元素一般就是首屏大图或轮播的第一帧,而最常见的三个错误恰好都压在这张图上。
- 首屏图被懒加载了。Shopify 的性能文档把这件事列为最常见也最伤的反模式之一:
image_tag在服务端渲染出图片地址,浏览器预加载扫描器在解析 HTML 时就能发现它,而loading="lazy"会把下载推迟到浏览器处理完隐藏的观察器事件之后,等于把这个提前量扔掉(Never lazy-load the LCP image)。改法是把loading="lazy"换成eager,或者直接去掉这个属性。 - 缺
fetchpriority="high"。图片默认以低优先级被发现,等布局完成后浏览器才把视口内的图片升级为高优先级,这段时间是白等的;给 LCP 图加上这个属性可以消掉等待,但一页只加一张,而且不能和懒加载同时出现(Mark the LCP image with fetchpriority="high")。 - 动画把图藏住了。淡入、页面过渡、滚动触发的显示效果都会让元素从
opacity: 0开始,而浏览器不把透明元素算作 LCP 候选,必须等到它以非零透明度重绘才记录。官方给的替代做法是改用 CSS@keyframes,它在元素第一次渲染时启动,不需要等脚本;时长控制在 0.3 秒以内,并且先去主题设置里关掉全局的页面过渡(Don't hide the LCP image behind animations)。
{% if section.index == 1 %}
{{ section.settings.image | image_url: width: 1000 | image_tag: loading: 'eager', fetchpriority: 'high' }}
{% elsif section.index > 3 %}
{{ section.settings.image | image_url: width: 1000 | image_tag: loading: 'lazy' }}
{% else %}
{{ section.settings.image | image_url: width: 1000 | image_tag: loading: 'eager' }}
{% endif %}
写条件判断的时候注意 section.index 在主题编辑器、静态 section 和 Section Rendering API 的返回里是空值,所以懒加载分支要写成正判断,让空值落到 eager 那一侧;否则预览时看着正常,线上跑的是懒加载。轮播还要多一个判断:如果首屏可见区域里同时能看到第二帧,那它也要按 eager 处理,只有滚动之后才出现的才懒加载。图片尺寸和容器比例的细节,以及响应式宽度怎么配,放在 LCP 图优化里讲(首屏大图的加载顺序与尺寸设置)。
轮播是不是值得留
首页轮播对性能的代价有两部分:首屏要同时准备多张大图,交互上还要跑切换逻辑,而手机上多数访客只看得到第一帧。判断它该不该留,标准不是「别人首页都有」,而是你自己的行为数据里有没有人真的切到第二帧、切完之后有没有往下走。如果数据看不出来,先把它降成一张静态主图加一个链接入口,改动小、可回滚,也顺手解决了一部分 LCP。
集合页:先数首屏有几张图
集合页的 LCP 元素通常是第一张产品卡片图,但真正拖慢加载的是首屏一次渲染出来的卡片总数。官方文档提到过这个场景:一个 section 里可能一次输出几十个产品,这时用 section.index 判断不够用,因为只有最前面几张在视口里,要改成按循环序号控制哪几张 eager、其余 lazy。

抓取与用户都主要在移动端(光算 · 示意图)
{% for product in collection.products %}
{% if forloop.index > 4 %}
{% assign image_loading = 'lazy' %}
{% else %}
{% assign image_loading = 'eager' %}
{% endif %}
{{ product.featured_image
| image_url: width: 400
| image_tag: loading: image_loading,
widths: '200, 300, 400',
sizes: '(min-width: 1200px) calc(25vw - 2rem), (min-width: 768px) calc(33vw - 2rem), calc(50vw - 2rem)' }}
{% endfor %}
第二件事是确认图片走的是 image_url 的响应式宽度。如果主题直接把原图地址塞进 src,手机就会下载桌面尺寸的文件,这个问题在集合页最明显,因为同一页有几十张。
第三件事是交互。集合页的筛选和排序在手机上比在桌面上更容易出问题,应用的脚本和主题的交互经常互相干扰,表现是筛选项被清掉或者排序后结果不对。这一段属于主题行为,不属于加载性能,排查顺序也不一样(排序丢筛选时的状态排查思路)。
至于集合页要不要写正文、分页和排序参数怎么处理,那是集合页自身的内容结构问题,不是性能问题,两件事不要混着改(集合页的信息结构与内链安排)。
产品页:交互比加载更值得盯
产品页的加载问题通常和首页同源:主图是 LCP 元素,处理方式一样。差别在交互环节——选变体、加减数量、加入购物车、展开说明书、放大图片,这些动作都会在主线程上跑事件处理,慢的时候表现成点了没反应,而不是页面转圈。
INP 衡量的是这段延迟:从点击、触摸或按键开始,到浏览器下一次绘制完成之间的时间,合格线是 200 毫秒(75 分位);它统计的交互只有鼠标点击、触摸和键盘输入,滚动和悬停不算在内(Interaction to Next Paint 说明)。所以「滚起来卡」在 INP 里看不到,要单独测。
产品页的定位顺序是:先在实验室里走一遍真实操作——选规格、改数量、加购、展开描述——看哪个动作响应最慢;再看慢的那一步是不是应用注入的脚本;最后决定是延迟加载、改写事件处理,还是换实现。判断脚本归属和改法,主线程那篇讲得更细(交互延迟的排查与取舍)。
产品页还有一种更隐蔽的问题:图片没写宽高、字体替换后尺寸变化,或者变体切换时价格区域被替换,都会造成布局跳动。这类偏移的成因和修法在另一篇里拆过(布局偏移的来源分类)。
按影响面和风险排执行顺序
检查项列出来之后,按两件事排序:影响多少页面,以及改错以后多难回滚。先做影响面大、回滚容易的。
| 改动 | 影响面 | 风险 | 建议位置 |
|---|---|---|---|
| 首屏图改 eager,LCP 图加高优先级 | 全站每一类页面 | 低,改错只影响单张图 | 先做 |
| 关掉全局页面过渡与首屏淡入 | 全站 | 低,视觉变化需和店主确认 | 先做 |
| 集合页首屏卡片按序号控制加载 | 集合页 | 低 | 其次 |
| 补齐响应式宽度与尺寸属性 | 全站图片 | 中,写错会取到错误尺寸 | 其次 |
| 延迟非必需第三方脚本 | 全站 | 中,可能影响统计与客服 | 确认业务归属后做 |
| 改事件处理或替换应用 | 单页或单功能 | 高,需要完整回归测试 | 最后做 |
表格里最后两行的顺序不要颠倒。脚本和应用相关的改动波及面看着小,实际牵扯统计口径、客服接入和订单流程,一旦出问题,损失的不是分数。
测试条件不固定,改完也看不出效果
手机上的测试最容易在条件上出错。同一台手机、同一档网络(不要一边测一边切 Wi-Fi)、同一个工具、同一个地区节点,跑三次以上再下结论。移动端和桌面端的结论不能互相搬——桌面不缺带宽和 CPU,很多只在手机上暴露的脚本开销,在桌面上根本测不出来。
弱网和低端设备才是真实体验的下限。做实验室测试时把网络档位调到接近真实移动网络,别拿办公室宽带的结果当结论;有条件的话用一台两三年前的手机实际走一遍加购流程,比任何分数都直接。
改动的节奏也要控制。同一天改多个页面、同时上线两处脚本调整,等结果变差时你无法知道是哪一处造成的。三类页面分开测、分开改,每类改完先在自己的条件下复测同一页,再决定要不要继续下一项。检查本身可以每周固定一个时间做,一次只走一类页面,比一次性做完三类更容易坚持,也更容易看出趋势。
边界:哪些不是你能改的
平台已经处理的部分不要重复做。CDN、图片格式转换、默认压缩都在平台侧完成,把它当成自己的工作量,会得出一个永远做不完的清单。你要负责的是主题和应用引入的那部分。
应用注入的东西要单独归类。弹窗、横幅、推荐位、评论组件在手机上往往同时带来资源和主线程开销,但它们不是主题代码,改之前先确认来源,属于应用的和应用方确认能不能延迟加载,属于已停用应用的先清理。判断一个应用值不值得留,标准不是它有几个功能,而是它解决的问题有没有别的办法(应用取舍的判断标准)。
不承诺分数。移动端单次测试的结果受网络波动和第三方脚本是否超时影响很大,说某个改动能让分数提高多少,是无法复现的。要判断有没有变好,用同条件的前后对比和后台字段数据的长期变化。加载速度之外的抓取与收录问题要走另一条线,谷歌 SEO 做的是那部分,和这里的页面性能是两套检查表(谷歌 SEO 与整站技术检查)。