系列文章:html
从单页应用(SPA)到服务器渲染(SSR)(本文)node
我的博客以前已经将 vue-router 的模式改成了 history
,即 url 中不包含 hash
,再经过将全部的静态请求转发到 index.html,使它看上去彷佛像一个静态多页的网站。webpack
然而,它其实和其余的 SPA (Single Page Application 单页应用)来讲没有任何的区别,最终是经过前端的路由去控制页面的显示。单页应用虽然在交互体验上比传统多页更友好,但它也有一个天生的缺陷,就是对搜索引擎不友好,不利于爬虫爬取数据。git
正所谓成也萧何,败也萧何。github
讲人话就是,搜索引擎搜不到个人博客啊~哭...web
那什么对搜索引擎和爬虫友好的哪?答案就是静态页,而非浏览器渲染,这就须要服务器直接渲染,也就 SSR(Server Side Render)。vue-router
SSR,服务器渲染。简单来讲就是,服务器将每一个要展现的页面都运行完成后,将整个相应流传送给浏览器,全部的运算在服务器端都已经完成,浏览器只须要解析 HTML 就行。segmentfault
提及来简单,那到底该如何着手将项目改形成 SSR,和曾经的多页又有什么区别哪?既然本身在 SSR 方面是个小白,天然要先从查资料看文档入手,Vue 2.0 的文档中有一章就是关于 SSR。
看了文档以后,它给了我一个新思路,能够在无须大幅修改原先代码的状况下作到 SSR,又不失单页良好的体验。
听上去很酷是否是,具体怎么作继续看下去。
一个普通的单页应用一般是经过 webpack 将源代码打包后插入到 html 中,当页面请求时,返回 html 再加载打包后的 js 文件,也就是下图中的 Application Code,Webpack build 和 browser 这三大块。
剩下的那几部分就是 SSR 须要额外新加的部分,一个个来看。
Server entry & client entry 二者的有共同的词尾 entry,对应的是 webpack.config 中的 entry,即打包入口文件,也就是分别表明服务器端所运行代码的入口和浏览器端所运行代码的入口文件。
入口文件天然不用多复杂。
server entry: 根据路由状态,返回渲染完成后相应的组件
clinet entry: 将应用直接挂载到 DOM 上
OK。它俩的事就作完啦,是否是很简单。
有了不一样的 entry,打包的内容也有不一样,天然就要两套配置。
配置 webpack 的配置文件的确很麻烦,但有个好消息就是原先的打包文件不须要修改,只需加一个 server 端的配置文件就能够了。server 端的配置文件也至关简单,基本能够沿用客户端的配置,改改 entry
和 output
基本就差很少了。
不过,有一点要注意,必定要将 target
属性设置成 node
,否则打包完了也无法在 node 环境下跑。还能够将全部依赖都设置成 externals
(跑在服务器本地嘛,依赖天然都拿获得),这只是个优化点,不加也没有任何问题。
有了配置文件,也就能生成 Server Bundle 了,只剩下最后一块 Bundle Renderer 了。
到这里才要用上 vue 为支持 ssr 所依赖的库 vue-server-renderer
。
经过 vue-server-renderer
提供的 API 就能容易地根据 url 生成对应的组件树,而后将它返回给客户端。
这里要注意,由于用的是 webpack 打包后的文件,因此只能用 createBundleRenderer
而不能用 createRenderer
来建立 renderer。
建立 renderer 的时候还能够为它配置 cache,方法在 README 中也写得很清楚了,因为我我的博客的场景不适合添加 cache 就没有添加。
这样从 SPA 到 SSR 的变动就完成了,经过浏览器访问看看是否是已经将页面整个返回了。
遇到控制台 ⚠️
The client-side rendered virtual DOM tree is not matching server-rendered content.
固然,多是你的标签不对应,也有多是 text node 中的空格字符长度不对应,我我的遇到的都是空格不对应形成的问题,非常尴尬(多是使用 template 语法形成的)...
Memory-fs
在开发环境下,因为使用服务器渲染,天然不能使用 webpack-dev-server,而是要用 webpack-dev-middleware。然而,webpack-dev-middleware 所建立的文件都是在内存里的,server 就没法读到 server bundle 文件,这里就要用到 memory-fs 来从内存中读文件。
KOA 2
用 koa 2 做为服务器时,在 renderToString
或 renderToStream
时,记得外面要加 await
,不然,程序就不等组件渲染好,就直接跑下个 middleware 去了。
(奉劝你们不要用 koa 做 SSR 服务器,koa 和 webpack-dev-middleware 天生水土不服,不要问我为何~?)
document
在 Server 端渲染时,node 环境下是没有 document 对象的。当一个界面的显示依赖于 document 对象(好比,页面滚动监听事件),那么,在 node 端运行时就会报错。
这时,有两个解决的办法。
根据运行时的环境变量,经过添加逻辑来判断是否依赖 document
使用 jsdom mock document 对象(我的偷懒的作法)
固然,从设计的角度移除对 document 的依赖就最好啦。
$root._isMounted:组件中能够用这个参数来判断应用是否为第一次挂载
这样当浏览器请求时,返回的页面是服务器渲染以后的,浏览器解析后,页面仍就是一个单页应用。
最后,看效果的戳这里,看代码的戳这里,原先 SPA 的代码依旧保留在了 SPA 分支。
对 Vue SSR 有兴趣的童鞋,必定要看看 vue hackernews 2.0,大神的水准比我但是高多了。
最后的最后,吐槽下 Daocloud,最近老挂我服务器,枉我一直为它说好话。
本身写完,看看感受好简单,为何还搞了那么久...