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

预渲染

  在做 web 移动端项目的时候遇到首屏加载慢的情况,原因是因为页面的渲染和逻辑都在JS端,需要等待JS加载完成页面才能被渲染出来。在未加载完JS的情况下,会导致页面白屏好一段时间。

  IOSsafari 访问的页面的 Content-Length 长度小于1000~1500字节时(不确定是否为这个范围),页面加载的JS/CSS就会堵塞页面的渲染,即使JS加了 defer 属性,又或者 JS 放 body 标签后面,async 则不受影响,后来发现使用分块传输 bigpipe 在这种情况下也会失效。而 Chrome 就没有这个问题。

  如何解决JS/CSS导致的白屏问题?目前的前端框架 Vue/React 等都有自己的解决方案:Vue NuxtReact Next,利用这些同构框架在服务端渲染好页面输出到浏览器而无需等待JS加载完成,解决首屏渲染白屏问题。还有就是使用预渲染方案,在打包构建时就分离动静资源,例如:Vue prerender-spa-plugin 插件。

同构框架和预渲染方案的选择

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

  在权衡之后,选择使用 Nuxt, 并且只使用它自带构建时预渲染,生成静态HTML的功能:nuxt generate,来实现资源动静分离。也就是在 Nuxt 里不写和服务端相关的代码,部署时也不需要依赖于服务端,减少成本。

  为什么用 Nuxt,而不用 prerender-spa-plugin 这些插件实现预渲染呢?考虑到项目后期可能会使用到服务端渲染的情况,所以使用 Nuxt 省去了迁移的麻烦,后期维护也会方便很多,还有就是生态问题,Nuxt 的开发人员多,社区活跃度高,遇到问题解决起来也会快一点,即使 Nuxt 现在也踩了挺多的坑。

骨架屏

  在解决了页面加载慢的问题后,现在已经可以无需等待JS加载完成就能看到页面了。然而页面的呈现需要依赖后端的接口数据,如果使用的是服务端渲染,则会等待接口响应成功后才连同 html 一起返回,如果是首屏依赖多个接口的数据,这又影响了页面加载速度,而且万一其中一个接口请求失败了该怎么办,解决起来也棘手。也许可以将多个接口合并成一个,再进行调用,但是这样做又会造成后端的耦合了,因为这些接口可能还会被其它的业务线使用。

  想来想去,最终的较好的解决方案是使用骨架屏先占位,接口的请求还是在浏览器端完成。这样做的好处是可以快速输出静态的 html,让用户马上看到页面结构,虽然一个个占位图确实很难看。还有就是无需合并接口了,一个模块的接口挂了也不会影响其它的模块。

其它

  利用预渲染和骨架屏已经可以带来较好的用户体验和加载速度了,像路由的预加载懒加载/代码分割/样式内联等优化方案,如果使用的是脚手架,只需要配置一下即可,还可以把静态资源都放到CDN上,实现动静分离。另外现在还可以使用 service worker 来更进一步的提升性能。