SPA进化到如今,已经从「拼能力」逐渐过渡到「拼体验」,大家也把目光转向了SPA相对多页后端渲染应用的两个最大缺陷:
在这样的背景下,主流框架都开始推出 SSR (server side rendering, 服务端渲染)方案,这里主要谈一下 科赛网 在迁移到 vue 2.x 的同时,对其 SSR 方案的评估及结论。
vue 的 SSR 思路比较清晰,使用服务端、客户端两个入口分别打包出适合在两端运行的应用,服务端会在 SSR 阶段预先注入页面所需的数据及输出对应的DOM字符串,客户端应用的起始状态就是服务端渲染的结果,完成状态混合之后的部分和普通的客户端渲染一致。
看起来没什么问题,实际操作了一下,好处不谈了,说一下潜在的坑:
API 稳定性 & vue 版本要求
SSR 相关 API 还未完全稳定,如果要入坑,建议升级到最新的 vue, vue-router, vuex 的发布版本。
服务器端要求
服务端必须是 node.js,或者专门跑一个 node.js 来支持 SSR。
库依赖
Polyfill 之类的对应用逻辑无影响的库可以放在客户端入口文件中,为客户端独有。其他的依赖库则要和应用打包在一起,也会在SSR阶段跑在服务端的 node 环境里。但是服务端的 js 环境和浏览器的环境不一致,有些库会在初始化时访问 window 对象或者使用 DOM API,造成异常。
这里点名批评一下 iview (A high quality UI Toolkit based on Vue.js),在 SSR 时会直接挂掉,和其定位与愿景并不相符。
应用状态管理
SSR 的标准实践是使用 vuex 来管理应用状态,也只有 vuex 的状态可以在服务端渲染后直接传递给客户端。在一个比较复杂的应用中,核心数据使用 vuex 管理没有任何问题,但同时有大量的状态不适于放在 vuex体系内,在 SSR 场景下要做额外考量。
对开发的额外约束
在 SSR 环境下,某些组件和路由的钩子不会被调用。
在官方demo 里,每个组件都要实现一个类似的 getAsyncData 方法,来配合服务端进行数据预取。这无可厚非,但是对于习惯使用路由及组件钩子完成数据获取的开发者,会带来额外的约束。
同时开发者也需要注意,created 钩子会在 SSR 阶段调用,在其中使用 DOM API 会导致异常。
数据请求&用户登录态
应用中 API 请求的逻辑也需要既运行在服务端,也运行在客户端。所以 vue-resource 不再可用,需要 axios.
在 SSR 阶段,如果数据依赖用户的登录状态,需要手动将用户 Cookie 传递到 SSR 的渲染器,才能在数据请求时从 context 中获取用户 cookie,走普通的API身份验证逻辑。
例如
// 这是服务端 router 代码
app.get('/my-ssr-app', (req, res) => {
constcontext = {
url: req.url,
cookies: req.cookies
}
constrenderStream = renderer.renderToStream(context)
// 其他略
})
PS: 之前我测试的时候,如果 router 有 base 的话, 在 SSR 阶段不能正确去掉 base 再解析前端路由,需要手动处理,不知道现在是否修复
服务端性能
SSR 是一个 CPU 密集型的应用,如果有扛高并发的需求,请慎用。
应用部署
对于纯 SPA, 如果在构建时将打包后的 bundle 上传到 CDN,则仅需要部署一个 index.html 到服务器。以至于我之前专门写了一个工具,可以实现 SPA 入口的热部署和版本切换。 但是在 SSR 场景下,应用代码和服务器端有了耦合,所以典型的部署需要重启 node.js 服务
注: 使用 createBundleRenderer,则可以实现服务端的热部署,详见文档
结论
花了一天时间研究以后,我否决了将科赛新版迁移到 SSR 的想法,目前 vue SSR 方案仅适用于重内容展示,并且规模有限的应用,科赛前端约 2w 行代码,并且新版增强了工具属性,强行 SSR 会带来更多问题。
另外,我本人非常认可 vue 团队在 SSR 上的努力,然而 vue 在浏览器里面写起来太爽,增加很多方便的特性,但是在 SSR 中某些特性会带来实现的困难,这是一枚硬币的两面。
另辟蹊径
那么首屏时间怎么办呢,SEO 呢,我是不是需要去知乎发帖「如何有效地糊弄产品经理,急,在线等」呢?
首先,对于以桌面端为主的前端应用,首屏时间不是瓶颈,只要以正确的姿势实现功能,首屏基本在 1s 左右。
其次,对于 SPA 的 SEO,有一个无痛的方案,叫做 prerender,代表实现就是 [http://prerender.io) 。
广告插入: 无痛 SEO, 今天 Prerender,明天就上线。
其原理是在你的网站服务器上判断请求来源,如果是来自搜索引擎的爬虫,则交给prerender去处理。prerender 利用一个 无头(headless,无界面)的浏览器,模拟打开一个SPA应用,然后将 JS 渲染出的 DOM 抓下来,喂给搜索引擎,从而实现一种伪 SSR 的效果。
目前 prerender 的实现都是基于 PhantomJS ,但是 Chome 59 也加入了 headless,还有一众和它相关的特性,有心人可以去看一下。
既然说到这了,我最近自己动手撸了一个 spa-renderer,并且支持开箱即用地部署到阿里云函数计算或者AWS Lambda。
为什么不用和 http://prerender.io 一样的常驻服务器的方案呢,因为搜索引擎抓取是一个频次较低的场景,完美契合函数计算或者Lambda的特性,用完就走,便宜,轻量,解耦。
之后会写文章详细介绍 spa-renderer,请关注专栏 DeepFE
鸣谢: 我家狗狗提供头图
欢迎关注前端外刊评论,关注前端前沿技术,探寻业界深邃思想。也欢迎给本专栏投稿,原作译作不限,质量上乘就好!