即 搜索引擎优化(Search Engine Optimization),它是指通过站内优化,如:网站结构调整、网站内容建设、网站代码优化以及站外优化等方法,来进行搜索引擎优化。
简单说: 通过各种技术(手段)来确保,你的Web内容被搜素引擎最大化收录,最大化提高权重,带来更多流量。目前SEO;流量是变现的快车道,SEO 是低成本获取流量的最佳方法。
即 单页 Web 应用(single page web application,SPA),就是只有一张 Web 页面的应用,是加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的 Web 应用程序
简单说: Web 不再是一张张页面,而是一个整体的应用,一个由路由系统、数据系统、页面(组件)系统…组成的应用程序,其中路由系统是非必须的。
大部分的 Vue 项目,本质是 SPA 应用,Angular.js、Angular、Vue、React…还有最早的”Pjax”均如此。
SPA 时代,主要是在Web端使用了history或hash(主要是为了低版本浏览器的兼容)API,在首次请求经服务端路由输出整个应用程序后,接下来的路由都由前端掌控了,前端通过路由作为中心枢纽控制一系列页面(组件)的渲染加载和数据交互。
而上面所述的各类框架则是将以:路由、数据、视图为基本结构进行的规范化的封装。
最早的 SPA 应用,由 Gmail、Google Docs、Twitter 等大厂产品实践布道,广泛用于对SEO要求不高的场景中。
SSR: 服务端渲染(Server Side Render),即:网页是通过服务端渲染生成后输出给客户端。
在 SPA 之前的时代,我们的Web架构大都是 SSR,如:Wordpress(PHP)、JSP技术、JavaWeb…或者 DEDECMS、Discuz! 等这些程序都是传统典型的 SSR 架构,
即:服务端取出数据和模板组合生成 html 输出给前端,前端发生请求时,重新向服务端请求 html 资源,路由也由服务端来控制。
其次,有个概念叫预渲染(Prerendering)。
如果你只是用服务端渲染来改善一个少数的营销页面(如 首页,关于,联系 等等)的 SEO,那你可以用预渲染来实现。
预渲染不像服务器渲染那样即时编译 HTML,它只在构建时为了特定的路由生成特定的几个静态页面,等于我们可以通过 Webpack 插件将一些特定页面组件 build 时就编译为 html 文件,直接以静态资源的形式输出给搜索引擎。
但实际的商业应用中,大部分时候我们需要的是即时渲染,这也是我们今天讨论的主题。
为什么要SSR,为了体验,还有SEO。
首先,用户可能在网络比较慢的情况下从远处访问网站 - 或者通过比较差的带宽。 这些情况下,尽量减少页面请求数量,来保证用户尽快看到基本的内容。
可以用Webpack的代码拆分避免强制用户下载整个单页面应用,但是,这样也远没有下载个单独的预先渲染过的 HTML 文件性能高。
对于世界上的一些地区人,可能只能用1998年产的电脑访问互联网的方式使用计算机。
而 Vue 只能运行在 IE9 以上的浏览器,你可能也想为那些老式浏览器提供基础内容 - 或者是在命令行中使用 Lynx 的时髦的黑客。
在大部分的商业应用中,我们有 SEO 的需求,我们需要搜索引擎更多地抓取到我们的内容,更详细地认识到我们的网页结构,而不是仅对首页或特定静态页进行索引,这是 SSR 最重要的意义。
简单说就是,我们需要搜素引擎看到这样的代码:
而不是这样的代码:
且,我们还需要在 SSR 的基础上实现 SPA,即:首屏渲染。
基本流程是:
在浏览器第一次访问某个 URI 资源的时候(首屏),Web 服务器根据路由拿到对应数据渲染并输出,且输出的数据中包含两部分:
路由页对应的页面及已渲染好的数据
完整的SPA程序代码
在客户端首屏渲染完成之后,此时我们看到的其实已经是一个和之前的 SPA 相差无几的应用程序了,接下来我们进行的任何操作都只是客户端的应用进行交互,
页面/组件由Web端渲染,路由也由浏览器控制,用户只需要和当前浏览器内的应用打交道就可以了。
之前在各大 SPA 框架还未正式官方支持 SSR 时,有一些第三方的解决方案,如:prerender.io,
它们做的事情就是建立HTTP一个中间层,在判断到访问来源是蜘蛛时,输出已缓存好的html数据,此数据若不存在,则调用第三方服务对 html 进行缓存,往复进行。
另一方法是自行构建蜘蛛渲染逻辑,当识别 UA 为搜索引擎时,拿服务端已准备好的模板和数据进行渲染输出 html 数据,反之,则输出 SPA 应用代码;
我当时也考虑过此方法,但有很多弊端,如:
需要针对蜘蛛编写一套独立的渲染模板,因为大部分情况下 SPA 的代码是没法直接在服务端使用的
搜索引擎若检测到蜘蛛抓取数据和真实访问数据不一致,会做降权惩罚,也就意味着渲染模板还必须和SPA预期输出一模一样
所以,最好的方法是 SPA 能和服务端使用同一套模板,且使用同一个服务端逻辑分支,再简单说:最好 Vue、Ng2… 能直接在服务端跑起来。
于是,陆续诞生了基于 React 的Next.js、基于 Vue 的Nuxt.js、Ng2 诞生之日便支持。
没错,Nuxt.js 就是今天的主角。
官方是这么介绍自己的:
Nuxt.js 是一个基于 Vue 的通用应用框架。
通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI渲染。
我们的目标是创建一个灵活的应用框架,你可以基于它初始化新项目的基础结构代码,或者在已有 Node.js 项目中使用 Nuxt.js。
Nuxt.js 预设了利用 Vue 开发服务端渲染的应用所需要的各种配置。
除此之外,我们还提供了一种命令叫:nuxt generate,为基于 Vue 的应用提供生成对应的静态站点的功能。
我们相信这个命令所提供的功能,是向开发集成各种微服务(miscroservices)的 Web 应用迈开的新一步。
作为框架,Nuxt.js 为 客户端/服务端 这种典型的应用架构模式提供了许多有用的特性,例如异步数据加载、中间件支持、布局支持等。
Nuxt.js是使用 Webpack 和 Node.js 进行封装的基于Vue的SSR框架,使用它,你可以不需要自己搭建一套 SSR 程序,而是通过其约定好的文件结构和API就可以实现一个首屏渲染的 Web 应用。
之所以叫 Nuxt.js 也是因为受到了 Next.js 的启发。
作者是法国的兄弟俩,EvenYou 在微博多次提到,也在欧洲见过哥俩。
在此之前,国内有一些对 Vue SSR 的整合尝试,但都没有成功,主要在于 Webpack 和 Node 的结合上没有实践出最佳方案,
当我看到 Nuxt.js 以约束文件夹和配置文件nuxt.config.js的方式来管理多个程序组件之间的关系时,就觉得,很酷!
接下来,我不会提供具体更多的学习资料,因为官方文档已经非常全面和成熟,已经 0.10.5 了(现在是 RC-11),只讲下其架构和原理,和一些生产环境会遇到的问题。
首先,Nuxt.js 是一个 Node 程序,就像上面说的,我们是要把 Vue 跑在服务端,所以必须使用 Node 环境。
我们对 Nuxt.js 应用的访问,实际上是在访问这个 Node.js 程序的路由,程序输出首屏渲染内容 + 用以重新渲染的 SPA 的脚本代码,而路由是由 Nuxt.js 约定好的 pages 文件夹生成的。
所以,整体上,Nuxt.js 通过各个文件夹和配置文件的约束来管理我们的程序,而又不失扩展性,其有自己的插件机制。
nuxt.config.js对程序的扩展管理可大概分为以下类:
generate
同时,Nuxt.js 支持以generate命令将程序直接构建为静态 html ,就像上面说的,可以作为静态资源直接输出。 打包
npm run generate
特殊的异步需求
这是生产环境最常见的问题,没有之一。
拿我的博客右侧 Sidebar 为例,在组件结构中,其属于宿主 layout 下的子组件,不属于页面组件,无法使用页面组件中的fetch方法,
官方的解释是子组件无法使用阻塞异步请求,即:子组件得到的异步数据无法用于服务端渲染,这对于程序是合理的,避免异常阻塞,简化业务模型;
但实际需求中,我需要这些异步数据增强站内内链 SEO;于是,我们可以巧妙地使用内置 vuex 中的nuxtServerInit这个 API,这个 API 是在 Nuxt.js 程序实例化之后第一次执行的方法,
其内部返回一个promise,我们可以在这里完成我们站内的所有子组件异步请求,随后将数据映射至对应子组件即可,这里有实践代码。
内存问题
在阿里云低配机上出现内存膨胀的问题,一个 Blog 程序 Run 起的内存高达 100M+,当然也由于 Node.js 的特殊单线程异步机制,暂不关心。
但在经过一段时间的访问之后,特别是瞬间高并发访问,会导致内存膨胀爆表宕机,经分析,是由于组件缓存引起的,将组件缓存减少至10,问题有所改观,但不明显;
更深原因是,每次用户访问,程序均会重新渲染组件输出,组件数据即在一段时间内驻存在内存中,直到 V8GC 回收。
最终的解决方案是:
使用官方推荐的”使用编码中的 Nuxt.j**s “方法,自定义**Node.js程序的入口,对程序进行一些优化;
如果你对业务和程序都需要有深度掌控的话,我很推荐此方法,它可以使你以管理 Node.js 程序的方式管理应用。
具体的优化方法是使用了一个叫idle-gc的垃圾回收模块来优化内存管理,
idle-gc是在node早期版本中被废除的功能,主要负责空闲时的堆内存回收,然后早期被认为有 BUG,经常会导致 CPU 满载,于是从 Node.js 中移除了,此项目作者修复了这个 BUG,并发布了模块。
另外,如果机器配置足够,建议开启缓存,即cache选项,且适当往大的配置,cache 的意义在于使用内存常驻来减轻 CPU 的计算压力,这对于单线程的 Node.js 是很好的业务实践。
最新更新:已不再使用此模块,最终靠 [ 优化业务逻辑 ] + [ 优化页面结构和抽象粒度 ] + [ 升级硬件 ] 来解决了问题。
这是 PM2 监控进程的日常数据之一:
移动版本适配问题
几乎所有的搜索引擎对于 PC 和移动端业务都是分开的,所以我们可以巧妙地使用layouts布局模块来实现我们移动端和 PC 端业务的分离;
在我的博客项目里,由于业务逻辑和页面均不够复杂,故使用了 CSS3 媒体查询 + 组件内判断的形式实现了移动端的适配。
Route自定义meta问题
目前 API 中对 router 的支持不够全面,如自定义的配置都还无法实现,不过可以通过宿主组件对应周期的hook来实现对实例化后的 router 对象进行修改和管理,尽管这不够优雅。
Window问题
由于 Vue 的底层使用 Virtual DOM,所以 Nuxt.js 在 Node.js 环境中的编译实际上是对象计算为字符串的过程,并没有依赖 Window/Dom,或者说任何基于 Vue 的 SSR 程序均如此。
我们在实际生产时可能用到一些需要依赖 DOM 的插件/扩展,正确的方法是根据官方文档 - 只在浏览器里使用的插件推荐的方法,通过变量判断插件/扩展的应用环境,
这里有实践代码,或者使用SSR版本的组件,如:vue-awesome-swiper,
或自行封装directive类型的插件,而非component,
切记不要使用jsdom等类似 Node.js 中的 DOM 库,这类库本身是为爬虫或测试诞生的,且本身会占据大量的内存,这不是真正的解决方案!
Surmon提供