惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog RSS Feed
宝玉的分享
宝玉的分享
腾讯CDC
博客园_首页
T
Tailwind CSS Blog
月光博客
月光博客
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
SecWiki News
SecWiki News
美团技术团队
P
Privacy International News Feed
H
Help Net Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
Know Your Adversary
Know Your Adversary
Y
Y Combinator Blog
D
DataBreaches.Net
Project Zero
Project Zero
T
The Blog of Author Tim Ferriss
Cyberwarzone
Cyberwarzone
C
Cybersecurity and Infrastructure Security Agency CISA
C
Cisco Blogs
S
Schneier on Security
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
Cisco Talos Blog
Cisco Talos Blog
小众软件
小众软件
Forbes - Security
Forbes - Security
D
Docker
T
Tenable Blog
S
Secure Thoughts
雷峰网
雷峰网
S
Security @ Cisco Blogs
T
The Exploit Database - CXSecurity.com
The Cloudflare Blog
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
阮一峰的网络日志
阮一峰的网络日志

Surmon.me

一人有限集团 你就是不敢 创造力是温柔的谎言 人类正在退出人类 AI 代替不了这样的你 脉冲点火背后的架构设计 基于 Cloudflare 生态的 AI Agent 实现 NodePress 支持用户登录了 从统计学习到通用智能 2025 投资报告:走慢的路 无依之地 会杀人的菩萨 无我不是共识 文化的积重与偏见 当下即安 科学的尽头是态度 无我不是 Egoless 信仰不因恐惧而存在 世间无解的矛与盾 先别急着做些什么 佛不需要你的皈依 真理的幻觉 两扇大门 造心里的浮屠 自胜者强 逻辑与智慧 真的相 快乐的秘密 只需愿意 是名体验 最深沉的梦 森林里倒下的树 现象与相 庸人自扰 React 与 Vue 的完美融合 开心就好吗 与原生家庭和解 两支毒箭 怎么自净其意 一尘不染 一片白云 让风穿膛而过 喝下去,然后闭嘴 活在当下 当纠结发生时 唯「我」独尊 一场把戏(时间) 一场把戏(二元对立) “我” 和 “我” 谁是众生? 船里没有人 玫瑰不需要说话 没有人能渡任何人 你我为轮,使之不再 当我们在谈论独立时 自我约束就是最好的自我接纳 从善与恶的表象出发 佛法不是心理学 别人眼中的你并不是你 高高山上走 祝你心想事成 祝你的噩梦早日发生 道是无情却有情 裙子只为自己而穿 不要盲目地评估自己 先别急着 “爱自己” 让生命欣然舞动 你无法复制乔布斯 符号不是目的 如果一件事发生了 每当我起心动念时 爱出者爱返 理解不是尊重的基础 在没有解决自己的问题之前 “知足常乐” 不是比较 逻辑是无法成为信仰的 买不到的自由 成为诵经者 伤口听不懂大道理 是立场让你不自由 - Unfreedom 天雨大不润无根之草 做自己的神 当心诱惑本身 牛洞冥思录 修罗启示录 让大脑自举 我更喜欢叫它无梦寺 存在主义也许不是解药 出南洋记 2022 的所有答复 没意义的表达 思考的记录 故宫暂行之幻想 极端的比较 解构的终极 舍利子是如何炼成的 心要野 佛教的偏见 文艺青年 我是动物
实现基于 Nuxt.js 的 SSR 应用
Surmon · 2017-04-21 · via Surmon.me

SEO

很重要,所以要普及。

SEO: 搜索引擎优化(Search Engine Optimization),它是指通过站内优化,如:网站结构调整、网站内容建设、网站代码优化以及站外优化等方法,来进行搜索引擎优化。

简单说: 通过各种技术(手段)来确保,你的 Web 内容被搜素引擎最大化收录,最大化提高权重,带来更多流量。

常见关键词: 白帽、黑帽、SEM、Backlink、Linkbait、PageRank、Keyword Stuffing...,总之都围绕着一个核心:SEO;流量是变现的快车道,SEO 是低成本获取流量的最佳方法。

目前大部分的搜索引擎仅能抓取 URI 直接输出的数据资源,对于 Ajax 类的异步请求的数据无法抓取;Google 除外,Google 有自己的 Google’s Webmaster AJAX Crawling Guidelines 技术支持。

SPA

SPA: 单页 Web 应用(single page web application,SPA),就是只有一张 Web 页面的应用,是加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的 Web 应用程序。

简单说: Web 不再是一张张页面,而是一个整体的应用,一个由路由系统、数据系统、页面(组件)系统...组成的应用程序,其中路由系统是非必须的。

大部分的 Vue 项目,本质是 SPA 应用,#angular.js、Angular、Vue、React...还有最早的 “Pjax” 均如此。

SPA 时代,主要是在Web端使用了 historyhash (主要是为了低版本浏览器的兼容)API,在首次请求经服务端路由输出整个应用程序后,接下来的路由都由前端掌控了,前端通过路由作为中心枢纽控制一系列页面(组件)的渲染加载和数据交互。

而上面所述的各类框架则是将以:路由、数据、视图为基本结构进行的规范化的封装。

最早的 SPA 应用,由 Gmail、Google Docs、Twitter 等大厂产品实践布道,广泛用于对 SEO 要求不高的场景中。

SSR

SSR: 服务端渲染(Server Side Render),即:网页是通过服务端渲染生成后输出给客户端。

在 SPA 之前的时代,我们的Web架构大都是 SSR,如:Wordpress (PHP)、JSP技术、JavaWeb... 或者 DEDECMS、Discuz! 等这些程序都是传统典型的 SSR 架构, 即:服务端取出数据和模板组合生成 HTML 输出给前端,前端发生请求时,重新向服务端请求 HTML 资源,路由也由服务端来控制。

其次,有个概念叫预渲染(Prerendering)。

如果你只是用服务端渲染来改善一个少数的营销页面(如 首页,关于,联系 等等)的 SEO,那你可以用预渲染来实现。 预渲染不像服务器渲染那样即时编译 HTML,它只在构建时为了特定的路由生成特定的几个静态页面,等于我们可以通过 Webpack 插件将一些特定页面组件 build 时就编译为 HTML 文件,直接以静态资源的形式输出给搜索引擎。

但实际的商业应用中,大部分时候我们需要的是即时渲染,这也是我们今天讨论的主题。

Why

为什么要 SSR,为了体验,还有 SEO

首先,用户可能在网络比较慢的情况下从远处访问网站 - 或者通过比较差的带宽。 这些情况下,尽量减少页面请求数量,来保证用户尽快看到基本的内容。 可以用 Webpack 的代码拆分 避免强制用户下载整个单页面应用,但是,这样也远没有下载个单独的预先渲染过的 HTML 文件性能高。

对于世界上的一些地区人,可能只能用 1998 年产的电脑访问互联网的方式使用计算机。 而 Vue 只能运行在 IE9 以上的浏览器,你可能也想为那些老式浏览器提供基础内容 - 或者是在命令行中使用 Lynx 的时髦的黑客。

在大部分的商业应用中,我们有 SEO 的需求,我们需要搜索引擎更多地抓取到我们的内容,更详细地认识到我们的网页结构,而不是仅对首页或特定静态页进行索引,这是 SSR 最重要的意义。

简单说就是,我们需要搜素引擎看到这样的代码:

包含有意义内容的 HTML 代码
包含有意义内容的 HTML 代码

而不是这样的代码:

无内容的 HTML 代码
无内容的 HTML 代码

且,我们还需要在 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

官方是这么介绍自己的

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.js 的程序的文件结构大概分为以下部分:

  • pages:各页面组件,用于生成对应路由,支持嵌套,支持动态路由
  • components:各组件,用于你自己管理公共组件或非公共组件
  • layouts:宿主布局页面模板组件,用于你可以把不同的页面指定使用不同的布局
  • assets:用于 Webpack 编译的各类资源,通常是一些小的资源,如代替雪碧图之类的图片等东西
  • middleware:中间件,首屏渲染和路由跳转前均执行对应中间件,可以返回 Promise 或直接 next(很实用!)
  • plugins:插件,SPA 中用的各类第三方组件和一些 Node.js 模块都可以在这引入,甚至可以引入自己编写的第三方库
  • store:内置了 vuex,可以直接返回数据模块或返回一个自建 vuex 根对象,具体要翻文档
  • 其他:你可以自定义文件夹和别名映射,文档都有提及,这里有 配置代码

nuxt.config.js 对程序的扩展管理可大概分为以下类:

  • build:主要对应 Webpack 中的各配置项,可以对默认的 Webpack 配置进行扩展,如 这里的代码
  • cache:主要对应内置的组件缓存模块 lru-cache 的配置对象,有默认值,可选关闭
  • css:对应我们在SPA随处引用样式文件的 require 语句
  • dev:用于自定义配置环境变量,对应之前 webpack.config.js 相关文件中的变量语句
  • env:同上息息相关
  • generate:对 generate 命令执行时的行为做一些定制
  • head:对应 vue-meta 插件的全局配置,vue-meta 用于 Vue/SSR 程序的文档元信息的管理
  • loading:用于定制化 Nuxt.js 内置的进度条组件
  • performance:用于配置 Node.js 服务器性能上的配置
  • plugins:用于管理和应用对应 plugins 文件夹中的插件
  • rootdir:用于设置 Nuxt.js 应用的根目录(这俩 API 有很大合并的意义)
  • srcdir:用于设置 Nuxt.js 应用的源码目录(这俩 API 有很大合并的意义)
  • router:用于对 vue-router 的扩展和定制,其中还包括了中间件的配置,但并不完美(后面说)
  • transition:用于定制 Nuxt.js 内置的页面切换过渡效果的默认属性值
  • watchers:用于定制 Nuxt.js 内置的文件监听模块 chokidar 和 Webpack 的相关配置项

generate

同时,Nuxt.js 支持以 generate 命令将程序直接构建为静态 HTML,就像上面说的,可以作为静态资源直接输出。

生产环境实践

特殊的异步需求

这是生产环境最常见的问题,没有之一。

我的博客 右侧 Sidebar 为例,在组件结构中,其属于宿主 layout 下的子组件,不属于页面组件,无法使用页面组件中的 fetch 方法, 官方的解释是子组件无法使用阻塞异步请求,即:子组件得到的异步数据无法用于服务端渲染,这对于程序是合理的,避免异常阻塞,简化业务模型。

但实际需求中,我需要这些异步数据增强站内内链 SEO;于是,我们可以巧妙地使用内置 vuex 中的 nuxtServerInit 这个 API,这个 API 是在 Nuxt.js 程序实例化之后第一次执行的方法, 其内部返回一个 Promise,我们可以在这里完成我们站内的所有子组件异步请求,随后将数据映射至对应子组件即可,这里有 实践代码

内存问题

在阿里云低配机上出现内存膨胀的问题,一个 Blog 程序 Run 起的内存高达 100M+,当然也由于 Node.js 的特殊单线程异步机制,暂不关心。

但在经过一段时间的访问之后,特别是瞬间高并发访问,会导致内存膨胀爆表宕机,经分析,是由于组件缓存引起的,将组件缓存减少至10,问题有所改观,但不明显;

更深原因是,每次用户访问,程序均会重新渲染组件输出,组件数据即在一段时间内驻存在内存中,直到 V8GC 回收。

最终的解决方案是:

使用官方推荐的 “使用编码中的 Nuxt.js” 方法 自定义 Node.js 程序的入口 ,对程序进行一些优化; 如果你对业务和程序都需要有深度掌控的话,我很推荐此方法,它可以使你以管理 Node.js 程序的方式管理应用。

具体的优化方法是使用了一个叫 idle-gc 的垃圾回收模块来优化内存管理,

idle-gc 是在 Node.js 早期版本中被废除的功能,主要负责空闲时的堆内存回收,然后早期被认为有 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 库,这类库本身是为爬虫或测试诞生的,且本身会占据大量的内存,这不是真正的解决方案!

有关更多常用使用问题,可以参考 官方解答

最后:这是 我的博客 ,也是一个完整的 Nuxt.js 程序, 源码在这里

若有差池,期待指正