从 “卡顿” 到 “秒开”:外投首屏性能优化的实战锦囊|得物技术
mhr18 2025-08-05 19:32 7 浏览 0 评论
一、背景
在互联网时代,网站性能的好坏直接影响用户体验和转化率。对投放的广告页面而言,如何在保证视觉效果和功能的同时提升加载速度,成为了开发者必须面对的挑战。
本文将探讨几种有效的外投页面性能优化策略,包括构建方式的优化、非首屏组件的处理、关键大图的预载、动效方面的升级,以及针对弱网环境下的降级策略、外投流渲染的技术升级等相关内容。
二、难点 & 收益
首屏秒开率口径严格
首屏秒开率通常指用户从触发页面加载(如点击广告链接)开始,到首屏内容完全渲染完成,并可进行交互所花费的时间在 1 秒以内的比例。其中,“首屏内容” 指用户设备屏幕可见区域内的全部内容,包括文字、图片、按钮等关键信息元素。
“完全渲染完成” 不仅要求视觉上显示完整,还需保证页面元素的布局稳定,不存在闪烁、错位等情况;“可交互” 则意味着用户能够对首屏内的交互元素(如按钮、输入框)进行有效操作,不会出现点击无响应的情况。
投放环境复杂
外投页面的投放环境极为复杂。用户可能分布在不同网络环境中,包括 4G、5G、Wi-Fi 甚至弱网或不稳定网络环境,在 2G、3G 等低速网络或信号波动较大的区域,数据传输速率受限,资源加载容易出现延迟。而且,用户使用的设备性能差异悬殊,从高端旗舰机到中低端老旧设备,硬件配置的不同会导致页面解析、渲染能力参差不齐,部分低端设备难以快速处理复杂的页面代码和资源。
页面收益
经过长达两个季度的性能优化专项工作,外投页面取得了显著的收益:
- 外投页面曝光率提高了10%左右
- 首屏秒开率从15%提高到65%左右
三、外投页面的渲染策略
在构建现代 web 应用时,SSR(Server-Side Rendering)是常用的渲染方式,但我们外投页面大部分页面采用的是ISR,字面上理解,可以理解为SSR+SSG。可以参考下面这张图。
在初次构建时,ISR 会像 SSG 一样预生成静态页面,并将其部署到服务器或 CDN。
当用户访问页面时,若页面已经过期,ISR 会触发一个后台再生成过程。这个过程在服务器端完成,类似于 SSR,但与 SSR 不同的是,再生成的页面会被存储为静态文件,而非每次请求都进行实时渲染。
具体再生流程
- 用户请求过期的页面时,ISR 会启动一个后台再生成过程。
- 服务器重新生成页面,并将新的 HTML 文件更新到缓存中。
- 在生成过程完成后,下一个用户请求将会获得更新后的页面。
举个例子,假设一个 Next.js 页面设置了 revalidate 选项为 10 秒,这意味着页面在构建时将会生成静态 HTML 文件,并在 10 秒内保持不变。
具体流程
- 0-10 秒:在初始构建完成后的前 10 秒内,用户请求的 HTML 文件都是之前生成的静态页面。页面不会重新生成,所有请求都直接返回缓存的静态页面。
- 10 秒后:当第一个请求到达并发现页面已经过期时(超过了 10 秒),ISR 会启动一个后台再生成过程。此时,服务器仍然会返回旧的缓存页面给用户,同时在后台生成新的页面。
- 生成完成后:当后台生成的页面完成后,下一个用户请求将会得到新的页面。新生成的页面会被存储为缓存静态文件,并在后续的 10 秒内继续使用,直到再次触发再生成过程。
这种机制保证了页面在大部分时间内快速响应,同时能够在后台静默地更新内容,确保用户能尽快看到最新的数据。
外投主要是几个固定页面,此处作如下分类。
分类
- 交互性交强的页面, 如盲盒这类页面的特点,放量占比流量大,首屏秒开受动效影响较大,一般配置完运营就短期内不会进行更新,适合ISR。页面静态内容为主。
- 容器页面用于进行效果AB测试的页面,不太适合用ISR,在构建时获取AB的结果, 实时性要求较高,AB的结果因人而异,采用SSR策略。
- 变更比较频繁的页面,不接受短时间的差异的,这种一般采取SSR策略。
不足:使用ISG构建的页面, 在于客户端和客户端建联的时候,需要等待HTML文档全部下载完成,浏览器才开始渲染,所以ISG构建的页面 TTFB 都会比较大。若一个网页的TTFB 较慢,后续的FMP、LCP、FCP 等所有指标都会很难提高, 所以在此基础上,我们在Q1启动了外投落地页的流渲染的技术架构升级,具体会在本篇文章的第四段详细介绍。
四、组件懒加载
在优化页面加载时,非首屏组件的渲染方式至关重要。比如页面的挽留弹窗,页面的二级弹窗,这些都属于非首屏的内容。 可通过组件的封装和抽取,区分首屏组件和首屏组件,哪些需要直出dom,哪些无需直出dom,可以在客户端在进行渲染,这里 通过使用 Next.js的 next/dynamic 实现组件的动态加载,可以将这些非首屏组件的 SSR 设置为 false ,在用户点击到了到相应位置时再进行加载。这种懒加载策略最直接影响的首屏的bundle(js、css),追求极致的首屏体积。
const DyamicMarqueeTop = dynamic(() => import('./marqueTop'), {
ssr: false,
loading: () => <div></div>,
})
const DyamicDialog = dynamic(() => import('./dialog'), {
ssr: false,
loading: () => <div></div>,
})
下面是优化前后的外投H5页面对比, 大图模板 预计减少5.3kB。
商品流模板
五、图片preload策略
对于外投页面而言,影响首屏秒开的有的是一些关键性大图。为了提升大图的加载效率,可以在 HTML 的 <head> 中使用 <link rel="preload"> 标签提前加载关键大图。这种做法能够在用户访问页面时,优先加载重要的视觉元素,从而减少用户等待时间。
import Head from 'next/head'
/** 单品图模板自定义的头的一些 业务逻辑 */
export const ScratchHead = () => {
const defaultUrl = 'xx'
const scratchInfo = global?.componentList?.find((item: { name: string }) => item.name === 'scratchCard')
const bg = scratchInfo?.props?.bgImg || defaultUrl
return (
<>
<Head>
<link rel="preload" href={`${bg}?x-oss-process=image/format,webp/resize,w_750`} as="image" />
</Head>
</>
)
}
六、外投动画升级
现状
由于盲盒玩法,流量波动比较大,优化盲盒玩法的首屏秒开优先级较高。
结合apm上报的路径,这是一个canvas容器。
html.support-webp > body > div#__next > div > div > div > canvas
初步定位为,需要页面的动画渲染影响了整体秒开,于是我们推动产品和设计同学 进行动画升级从现有的apng升级到lottie
75分位的首屏秒开 从 4.8s 多下降到 1.8s 左右, 首屏秒开提升16pp左右。
为什么升级一个动效,就对页面的秒开影响之大?
apng
在没全量应用lottie之前,外投针对复杂动画的处理一直都是apng,可以说是苦apng久已。
- apng的缺点在于资源体积非常大,一般的动画都得达到2-4M, 光资源加载就要好几秒了,何谈首屏秒开。
- 由于apng有浏览器兼容性问题,对于apng资源的播放我们都是将apng解码,然后每一帧的图片用canvas 画布进行渲染。
- 由于动画的容器是canvas,无法首屏直出dom,只能等到客户端场景下载JS,加载资源后再进行渲染, 这就是含有动画模板秒开比较低的本质原因。
(下面的流程图均是cursor结合excalidraw自动生成出的)
染apng具体流程
lottie
为了解决上诉问题, 我们第一时间想到了lottie 动画, 对于首屏关键的动画从apng 直接转到lottie。
lottie的文件体积通常较小,因为它是以 JSON 数据的形式存储动画信息,而非实际的图像帧。对于复杂的动画,尤其是包含大量图形元素和动画效果的情况,Lottie 的文件大小优势更为明显。可以看下面对比图:
同样一个动画播放, apng 需要将近2M多,但是lottie需要的资源仅仅需要几百k,资源体积下降十分明显。
设计师通常给到的lottie资源是这样的:
开发往往需要对设计师给到的lottie文件进行cdn 化处理:
第一步: 需要压缩图片资源,然后上传到OSS。
第二步: 然后替换json 中的 assests 路径。
第三步: 上传lottie.json,生成一个url,供我们前端进行渲染。
对于这些复杂的操作,我们已经内置为一个npm 包@dw/lottie-cli,专门用于处理设计师给的lottie资源,同时封装了基于react的lottie组件。
- lottie-cli支持 tinify图片压缩
- 支持webp生成
- lottie-player 支持 webp 渲染
lottie的缺点,在于依赖一个lottie-web的运行时,以及lottie.json 资源的大小,至于方案如何选择,还是要具体业务具体分析。没有完美的方案,找到适合当前页面的动效才是最好的。
减少runtime体积
lottie动画依赖的lottie-web没法做到tree-shaking,因此修改外投引入lottie的方式。将lottie js 体积包拆分成 svg、html、canvas. 三种渲染依赖JS ,将运行时体积从80kb降低到40kb,首屏秒开预计提升100ms。
优化前
优化后
针对lottie动画SSR场景下没法直出dom的情况,新增lottie动画预渲染骨架。在SSR场景,用动画的第一帧dom,作为lottie动画的骨架(只适用于lottie渲染模式 是用svg 或者是html,canvas模式不支持 ),首屏秒开提升100ms。
优化前
优化后
七、低端机和弱网优化
为保障所有用户都能收获良好使用体验,在这个过程中,尤其需要关注那些使用低端机器的用户群体。针对这部分用户,我们可制定相应的降级策略,比如跳过动画展示环节。具体实施时,通过对用户设备性能进行检测,以此来决定是否加载动画元素,进而确保页面在性能相对较低的设备上也能够保持流畅运行。
弱网指标判断
弱网的初步定义
当网络的往返时间(RT)大于 150ms 时,便认定其处于弱网状态。
网络性能检测方法
针对同一张小体积图片,在每次访问时都对其进行访问操作,并计算相应的往返时间(RT)。我们将该 RT 值作为此次访问网络性能的代表指标,通常情况下,RT 值越短,则意味着网络性能越佳。
低端机指标判断
核心判定逻辑
综合考量去navigator上的 deviceMemory、hardwareConcurrency、saveData 以及显示屏幕、触摸屏幕等方面的信息,以此构成判断低端机的初步标准。
具体判定细则
- 内存方面:若 deviceMemory 的值小于等于 2GB,那么可直接判定该设备为低端设备。
- CPU 方面:当 navigator.hardwareConcurrency 的数值小于 4 时,同样能够直接判定此设备属于低端设备范畴。
- 设备像素方面:倘若设备像素低于 600,那么该设备可被判定为极端设备。
卡顿指标判断
卡顿指标的检测逻辑
针对卡顿指标的检测,我们采取如下方式:通过创建一批div元素,并为其施加动画效果,以此来测试设备的性能表现,随后给当前设备进行打分,最终依据分数的高低来区分不同性能的机器。具体的卡顿判定标准如下:
- 若渲染时间大于 60ms,这种情况可被视作存在发生卡顿的可能性。
- 而一旦渲染时间超过 200ms,那么就认定该设备出现了严重卡顿的情况。
收集到上述指标后,最终落地到页面就是动画跳过,或者是动画降级,从动画变成一张静态图,或者是在不阻塞主流程的情况下, 走下一步跳过动画。
伪代码如下:
// 是低端机或者是弱网设备
if (isLowEndDevice || isLowNetWork) {
// 跳过动画
} else {
// 加载动画
}
降级策略
移动端设备,针对我们外投页面而言,影响比较大的两个因素:
- 弱网
- 设备性能
安卓设备性能小于IOS 设备,安卓设备解析JS的时间长,在弱网的情况下,用户加载JS的时间比较长。
Lotttie动画降级
a. 主要是针对lottie 文件的assets 图资源进行降级。
b. 针对低端机型直接跳过动画,直接使用lottie预构建的骨架, 保证SSR的dom直出。
动画降级方案
目前外投播放apng 动画的流程涉及到如:
一个apng的动画的播放生命周期,涉及到资源拉取时间 + 解析apng 的时间,但是这对于性能较差的低端机来说,可能是在灾难。这里直接如果识别到是低端机,直接跳过动画。如果在3秒内没有加载完成,可以直接进行兜底超时逻辑,展示兜底静态图。
轮播图降级方案
针对低端机和弱网首屏引入的swiper.js,这里直接去掉swiper,使用css 手写无限轮播动画。
通过上面的优化动作,针对低端机或者是弱网设备,我们的页面也能够快速的打开。
八、流渲染技术架构升级
经历过上面一些常规性的优化,想要首屏秒开进一步提高,需要整体框架层面进行架构升级。 我们针对外投大流量页面进行了流渲染的架构迁移。
流渲染的优势
- 渲染将页面拆解为多个可独立渲染的 “片段”(Fragment),服务端逐段生成 HTML 并流式传输给客户端。例如,先返回头部和主体框架,再逐步填充内容模块(如导航栏、列表项等)。
- 用户体验优化:用户无需等待整个页面生成即可看到部分内容,尤其是关键信息(如标题、首屏图文)的展示时间显著缩短,提升 “感知速度”。
- 性能数据对比:在复杂页面中,流渲染可使首屏渲染时间(TTFB)减少 30%~50%,尤其适用于数据分批次加载的场景(如瀑布流、分页内容)。
- 流渲染的分阶段交互激活:流渲染可将页面拆解为多个 “可交互单元”,服务端在传输片段时,同步或异步注入对应的客户端逻辑。
- 例如:先渲染商品列表的静态结构,同时加载列表项的点击事件逻辑,使首屏内容在 hydration 完成前即可部分交互。结合增量 hydration(Incremental Hydration),仅对动态更新的片段进行交互激活,减少整体脚本执行时间。提升页面的 “可交互时间”(TTI,Time to Interactive),使用户更早能与页面互动。
历史流量迁移
在流渲染迁移过程中,外投链接完成从非流渲染到流渲染的升级。
具体实施策略
- 新增页面:直接使用新链接,原生支持流渲染技术,享受性能优化红利。
- 存量广告计划兼容:
- 已发布且无法修改链接的计划:通过代理转发机制,将原链接(cdn 域名)的请求回源到流渲染服务,实现 “链接不变、内容由流渲染承载” 的无感知迁移。
- 未发布的非流渲染链接计划:联合后端与产品团队,通过数据批量处理(刷数),将原链接批量替换为流渲染链接,确保新计划直接使用高性能架构。
具体的架构图参考如下
apm性能脚本内嵌
在流渲染页面外投场景中,虽已实现部分页面流渲染,但首屏秒开指标提升效果不佳。通过对大盘数据深入剖析,发现接口响应时间(RT)75 分位值约为 50ms,首字节时间(TTFB)75 分位值约为 500ms,由此可判断服务端性能并非瓶颈所在。将分析方向转向客户端后,确认首屏秒开的关键制约因素并非框架本身,而是监控 SDK 脚本加载耗时。
具体而言,页面首次有效绘制(FMP)的计算依赖于外部脚本 xxx.js。当前机制下,页面渲染时异步加载 xx.js,需等待该脚本加载完成后才能进行 FMP 标记计算,这导致 xx.js 加载耗时直接计入 FMP 统计。考虑到 xx.js 体积达 35KB,在首屏阶段异步加载此脚本,对网络条件较差的外投页面性能影响显著。
针对外投弱网场景,和APM团队提出首屏先采集后上报的需求,最终APM 团队提供了 7KB 的性能内联版本 SDK。在流渲染的的白屏阶段同步加载 性能脚本JS,经实践验证,优化效果显著:在 4G 网络环境下,FMP 指标从优化前的 1000ms 降至 500ms 左右,首屏秒开率提升约 25 个百分点,提升十分显著。
九、总结
在页面的性能优化过程中,结合流渲染的技术架构升级、ISR、动态加载非首屏组件、关键大图预加载、Lottie 动画,以及针对低端设备的降级策略,可以形成一个多层次的优化方案,为用户提供流畅而快速的访问体验。
通过这些优化措施,外投页面不仅能够更好地满足用户需求,还能有效提高转化率,为业务带来更多机会。希望本文能够帮助你在实际项目中更好地应用这些性能优化策略。
往期回顾
1.从Rust模块化探索到DLB 2.0实践|得物技术
2.eBPF 助力 NAS 分钟级别 Pod 实例溯源|得物技术
3.正品库拍照PWA应用的实现与性能优化|得物技术
4.汇金资损防控体系建设及实践 | 得物技术
5.Redis 是单线程模型?|得物技术
文 / 正飞
关注得物技术,每周更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~
未经得物技术许可严禁转载,否则依法追究法律责任。
相关推荐
- 订单超时自动取消业务的 N 种实现方案,从原理到落地全解析
-
在分布式系统架构中,订单超时自动取消机制是保障业务一致性的关键组件。某电商平台曾因超时处理机制缺陷导致日均3000+订单库存锁定异常,直接损失超50万元/天。本文将从技术原理、实现细节、...
- 使用Spring Boot 3开发时,如何选择合适的分布式技术?
-
作为互联网大厂的后端开发人员,当你满怀期待地用上SpringBoot3,准备在项目中大显身手时,却发现一个棘手的问题摆在面前:面对众多分布式技术,究竟该如何选择,才能让SpringBoot...
- 数据库内存爆满怎么办?99%的程序员都踩过这个坑!
-
你的数据库是不是又双叒叕内存爆满了?!服务器监控一片红色警告,老板在群里@所有人,运维同事的电话打爆了手机...这种场景是不是特别熟悉?别慌!作为一个在数据库优化这条路上摸爬滚打了10年的老司机,今天...
- springboot利用Redisson 实现缓存与数据库双写不一致问题
-
使用了Redisson来操作Redis分布式锁,主要功能是从缓存和数据库中获取商品信息,以下是针对并发时更新缓存和数据库带来不一致问题的解决方案1.基于读写锁和删除缓存策略在并发更新场景下,...
- 外贸独立站数据库炸了?对象缓存让你起死回生
-
上周黑五,一个客户眼睁睁看着服务器CPU飙到100%——每次页面加载要查87次数据库。这让我想起2024年Pantheon的测试:Redis缓存能把WooCommerce查询速度提升20倍。跨境电商最...
- 手把手教你在 Spring Boot3 里纯编码实现自定义分布式锁
-
为什么要自己实现分布式锁?你是不是早就受够了引入各种第三方依赖时的繁琐?尤其是分布式锁这块,每次集成Redisson或者Zookeeper,都得额外维护一堆配置,有时候还会因为版本兼容问题头疼半...
- 如何设计一个支持百万级实时数据推送的WebSocket集群架构?
-
面试解答:要设计一个支持百万级实时数据推送的WebSocket集群架构,需从**连接管理、负载均衡、水平扩展、容灾恢复**四个维度切入:连接层设计-**长连接优化**:采用Netty或Und...
- Redis数据结构总结——面试最常问到的知识点
-
Redis作为主流的nosql存储,面试时经常会问到。其主要场景是用作缓存,分布式锁,分布式session,消息队列,发布订阅等等。其存储结构主要有String,List,Set,Hash,Sort...
- skynet服务的缺陷 lua死循环
-
服务端高级架构—云风的skynet这边有一个关于云风skynet的视频推荐给大家观看点击就可以观看了!skynet是一套多人在线游戏的轻量级服务端框架,使用C+Lua开发。skynet的显著优点是,...
- 七年Java开发的一路辛酸史:分享面试京东、阿里、美团后的心得
-
前言我觉得有一个能够找一份大厂的offer的想法,这是很正常的,这并不是我们的饭后谈资而是每个技术人的追求。像阿里、腾讯、美团、字节跳动、京东等等的技术氛围与技术规范度还是要明显优于一些创业型公司...
- mysql mogodb es redis数据库之间的区别
-
1.MySQL应用场景概念:关系型数据库,基于关系模型,使用表和行存储数据。优点:支持ACID事务,数据具有很高的一致性和完整性。缺点:垂直扩展能力有限,需要分库分表等方式扩展。对于复杂的查询和大量的...
- redis,memcached,nginx网络组件
-
1.理解阻塞io,非阻塞io,同步io,异步io的区别2.理解BIO和AIO的区别io多路复用只负责io检测,不负责io操作阻塞io中的write,能写多少是多少,只要写成功就返回,譬如准备写500字...
- SpringBoot+Vue+Redis实现验证码功能
-
一个小时只允许发三次验证码。一次验证码有效期二分钟。SpringBoot整合Redis...
- AWS MemoryDB 可观测最佳实践
-
AWSMemoryDB介绍AmazonMemoryDB是一种完全托管的、内存中数据存储服务,专为需要极低延迟和高吞吐量的应用程序而设计。它与Redis和Memcached相似,但具有更...
- 从0构建大型AI推荐系统:实时化引擎从工具到生态的演进
-
在AI浪潮席卷各行各业的今天,推荐系统正从幕后走向前台,成为用户体验的核心驱动力。本文将带你深入探索一个大型AI推荐系统从零起步的全过程,揭示实时化引擎如何从单一工具演进为复杂生态的关键路径。无论你是...
你 发表评论:
欢迎- 一周热门
-
-
Redis客户端 Jedis 与 Lettuce
-
高并发架构系列:Redis并发竞争key的解决方案详解
-
redis如何防止并发(redis如何防止高并发)
-
Java SE Development Kit 8u441下载地址【windows版本】
-
开源推荐:如何实现的一个高性能 Redis 服务器
-
redis安装与调优部署文档(WinServer)
-
Redis 入门 - 安装最全讲解(Windows、Linux、Docker)
-
一文带你了解 Redis 的发布与订阅的底层原理
-
Redis如何应对并发访问(redis控制并发量)
-
Oracle如何创建用户,表空间(oracle19c创建表空间用户)
-
- 最近发表
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle主从同步 (56)
- oracle 乐观锁 (53)
- redis 命令 (83)
- php redis (97)
- redis 存储 (67)
- redis 锁 (74)
- 启动 redis (73)
- redis 时间 (60)
- redis 删除 (69)
- redis内存 (64)
- redis并发 (53)
- redis 主从 (71)
- redis同步 (53)
- redis结构 (53)
- redis 订阅 (54)
- redis 登录 (62)
- redis 面试 (58)
- redis问题 (54)
- 阿里 redis (67)
- redis的缓存 (57)
- lua redis (59)
- redis 连接池 (64)