记录使用较小的成本换来较好的用户体验和提升页面加载速度,适用场景:web 移动端单页应用,前后端(数据层)分离的项目。

白屏

  在做 Web 移动端项目的时候遇到首屏加载慢的情况,因为页面的渲染都依赖 JS 文件输出,所以就导致需要等待 JS 文件加载完成页面才能被渲染出来。在未加载完 JS 文件的情况下,会导致页面白屏一段时间。

解决白屏问题:

  1. 使用同构框架 / 预渲染插件。让浏览器在加载 JS 文件之前就输出渲染好的页面,目前的前端框架 Vue/React 有现成的解决方案:Vue NuxtReact Next,利用这些同构框架在服务端渲染好页面输出到浏览器而无需等待 JS 加载完成,解决首屏渲染白屏问题。还有就是使用预渲染插件,在打包构建时分离动静资源,如:Vue prerender-spa-plugin 插件。
  2. 使用占位元素(骨架屏)。渲染页面依旧由 JS 完成,然后在需要访问的 HTML 页面里填充一段描述页面结构的块状元素,让用户可以马上看到页面结构,等 JS 加载完了再替换掉。使用这个要注意防止外链的 JS/CSS 文件阻塞 HTML 页面的渲染,最好 JS 文件放底部,加上 async/defer,CSS 只加载基础样式,业务和组件的样式可以异步加载。

在做加载性能测试的时候,还发现了一个问题:用 IOS 10.X Safari 访问页面时,当目标页面的 Content-Length 长度大概小于1000~1500字节左右并且页面有好几个外链文件的情况下,页面加载的外链脚本文件会堵塞页面的渲染,无论是给脚本文件加 defer 属性,还是把它放 Body 标签后面都会无效,而 async 则不受影响,后来发现分块传输方式在这种情况下也会失效。以上的这些问题在 Chrome 下不会出现。

同构框架的问题

  使用同构框架也会有一些问题,一个是依赖服务器,页面刷新渲染挪到了服务端,给服务器带来了一些压力。开发上也需要兼顾两端,服务端没有 window document location 等这些浏览器变量,需要做兼容处理,特别是在项目中使用了第三方的插件,如果插件本身不支持 SSR,还得自己对它进行改造,带来了开发成本和维护上的问题。

方案

  根据业务和项目大小使用不同的方案。如果项目是完全前后端分离,只通过接口来通讯,可以预渲染+骨架屏搭配使用,在页面级组件上写好占位元素(骨架屏),然后用 nuxt generate 命令构建,生成渲染后带占位元素的 HTML (预渲染主要用来成生成骨架屏),接口的请求和拿到数据后的渲染依旧在浏览器端完成,这样做的好处有:

  1. 部署时不需要依赖于服务端的 Node 环境,因为 nuxt generate 生成了静态页面,所以减少了开发和服务器成本,只需要一个 web 服务器就可以。
  2. 骨架屏的插入不需要自己实现,只需要写好占位元素的样式和 HTML 即可,nuxt generate 会自动生成。
  3. 接口数据请求和渲染在服务端的话会造成如果接口请求异常导致整个页面报错,这时候需要处理这种异常情况,在浏览器端请求接口再渲染则不会导致整个页面报错,最多页面上关联的模块出错了,也不会导致整个页面看不了。
  4. 页面的呈现所需的数据通常是由多个接口组成,如果把这些接口的请求放到服务端获取,此时如果接口没做合并处理,会导致最终页面渲染时间大大增加,因为在服务端获取是同步的,而浏览器端是可以异步的。
  5. 可以解决需要 SEO 的页面。

   prerender-spa-plugin 预渲染插件适合小型项目,而用 Nuxt 可扩展性强,功能多。考虑到项目后期可能会使用到服务端渲染的情况,所以使用 Nuxt 省去了迁移的麻烦。

其它

  利用预渲染和骨架屏已经可以带来较好的用户体验和加载速度了,有CDN的话,可以把打包生成的静态资源都放到 CDN 上,还可以使用 service worker 来更进一步的提升性能。