作者:汪楠
目前 Vue
、 React
在前端界混的风生水起,它们的开发思想使得我们能真正做到前后端分离、解耦。单页面的使用给用户带来了更好体验。不过对于 Vue
和 React 这种框架来说, HTMLinJS
的思路在首屏加载慢、白屏以及 SEO
等问题就日益突出了。
不仅需要拼框架的功能、生态,当然还不能忘记“用户至上的原理“,拼体验。孜孜不倦的前端朋友们给出了几个解决方案:1.Server-side rendering(SSR),2.Prerendering。下面我将一一介绍一下。
什么是SSR?
SSR 直译就是服务端渲染,通过设置 SSR
,你就可以在后台的 Node.js
环境中完成渲染逻辑,然后将 HTML
视图直接返回给客户端。这样你不仅可以使用 Vue
和 React
技术,而且可以直出页面内容。而非只有一个空壳子在后端那里。这样也方便了搜索引擎的蜘蛛获取页面,解决 SEO
问题。
SSR有什么问题?
你可以回想一下我们在很早之前的前端开发模式,其实就是后端直出页面。前端重构页面,交由后端套页面渲染首页的数据。当然一些异步的数据,则通过 ajax
获取数据渲染。这似乎又回到了之前的开发模式。前端和后端还是紧密联系在一起了。给维护和迭代带来的不便。
那它没有好处吗?有的!目前,社会上还是有成熟的框架和线上的产品,比如 Nuxt.js
[1],
, 说明它还是具有价值的。它很明显可以解决首屏白屏或者SEO等问题。但是它也引入很多问题,其一,对工程师要求较高,需要同时掌握的前后端知识。其二,要考虑在服务端 Node.js
环境中的内存泄露、运行速度、并发压力等问题。 Node
层的服务可以用“脆弱”两个字来形容。其三,开发成本增加,研发周期变长等。一般来说,是需要一个强大且稳定的基础架构来支撑服务端的压力。
如果你可以应付这些,无疑 SSR
对于增强应用体验是非常棒的~,但对于像我这样有点焦虑的人来说,是否有其他解决办法呢?有的, Prerendering
!
Prerendering
有时候,我们开发的单页面应用也就几个页面,非常小型,仅仅是为了 SEO
、首页白屏问题,大家都觉得有点校枉过正了。可以利用第三方插件 prerender-spa-plugin
[2],在客户端实现渲染,这样无需将 Vue
或者 React
代码部署在服务端。 prerender-spa-plugin
是 Webpack
的插件,它可以编译应用中的所有静态页面,轻而易举的建立对应的索引路径。下面结合 Vue.js
和 prerender-spa-plugin
来解决前面所提出的的问题。
安装
npm install prerender-spa-plugin --save-dev复制代码
使用
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
module.exports = {
plugins: [
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, 'dist'),
routes: [ '/', '/about', '/contact' ],
renderer: new Renderer({
inject: {
foo: 'bar'
},
renderAfterDocumentEvent: 'render-event'
})
})
])
]
}
复制代码
staticDir
指的是预渲染输出的页面地址, routes
指的是需要预渲染的路由地址, renderer
则是所采用的渲染引擎是什么,目前用的是 V3.4.0
版本支持 PuppeteerRenderer
。 inject
则是预渲染过程中都能拿到的值,该值提供给你了机会,让你觉得是否渲染这部分代码。例如下面的代码,是不会被预渲染进 HTML
中的。
showMessage(){
if(window.__PRERENDER_INJECTED && window.__PRERENDER_INJECTED.foo =='bar') return;
this.message = '我是测试预加载拦截';
}
复制代码
renderAfterDocumentEvent
这个则很关键,这个是监听 document.dispatchEvent
事件,决定什么时候开始预渲染。
new Vue({
el: '#app',
router,
render: h => h(App),
mounted () {
// You'll need this for renderAfterDocumentEvent.
document.dispatchEvent(new Event('render-event'))
}
});
复制代码
实例
具体可以看一下官方的 Vue.js2.0+vue-routerPrerenderSPAExample
[3] 实例。
原理及缺点
prerender-spa-plugin
利用了 Puppeteer
[4] 的爬取页面的功能。 Puppeteer
是一个 Chrome
官方出品的 headlessChromenode
库。它提供了一系列的 API, 可以在无 UI 的情况下调用 Chrome
的功能, 适用于爬虫、自动化处理等各种场景。它很强大,所以很简单就能将运行时的 HTML
打包到文件中。原理是在 Webpack
构建阶段的最后,在本地启动一个 Puppeteer
的服务,访问配置了预渲染的路由,然后将 Puppeteer
中渲染的页面输出到 HTML
文件中,并建立路由对应的目录。
利用官方的实例进行编译结果如下:
每个对应的路都有一个对应的静态 HTML
。每一个 HTML
内除了
"app">复制代码
这个 Vue
的挂载元素外,还有静态的标签内容。
"en">
"utf-8">
PRODUCTION prerender-spa-plugin
"shortcut icon" href="/favicon.ico">
"app">
"/logo.png?82b9c7a5a3f405032b1db71a25f67021"> Welcome to your prerender-spa-plugin Vuejs 2.0 demo app!
汪楠大大about页
"/" class="router-link-active">Home "/about" class="router-link-exact-active router-link-active">About "/contact" class="">Contact
"javascript:;">点击我,看看有什么效果 最好不要点我
复制代码
既然有了每个路由对应的 HTML
,那么对应 SEO
优化应该不成问题了。我们可以更改 title
、 meta
。而且页面的内容都已经在 HTML
中直接呈现,就不会有因为 js
等资源加载慢导致白屏的问题。 prerender-spa-plugin
的确在一定程度上解决了我们对于 SEO
的诉求和页面加载慢的问题。但是它的缺点还是很明显的。
不同的用户看到不同的页面,动态数据页面
经常发生变化的页面,数据实时性展示(比如体育比赛等)
路由过多,构建时间过长
正确的使用姿势
聊了这么多,大家都是为了给用户更好的体验。基本上我们做的页面都是强依赖动态数据展示,如果渲染的静态页面和最后呈现的页面之前切换并不自然,那么体验是很差的。我们不能为了解决 SEO
或者加载慢的问题,引入新的问题。那到底该怎么做呢?其实很简单: Loading
或者 骨架屏。这两个都是将过渡的 HTML
片段插入到