整体目标:
- 理解服务端渲染的概念
- 会用vue的SSR
- Nuxt中的async
一、基本概念
页面的两种生成方式
用户从浏览器地址栏中输入地址访问服务器上的页面时,服务器可以用两种不同的策略来生成这个页面,对应着程序员写的不同的实现方式。
打开给出的示意代码,观察效果。
(1)服务器端渲染Server Side Render
-
渲染是指:从数据到dom的过程(从json到html结构)
-
数据到dom的过程是发生在服务器端
-
客户端浏览器在地址栏中输入网页的地址,取回来的就是html页面。
-
理解:浏览器发送请求到服务器,服务器端获取数据库数据,进而编译成html,返回给浏览器,这一过程叫服务器段渲染
(2)客户器端渲染 Client Side Render
-
渲染是指:数据到dom的过程 (从json到html结构)
-
数据到dom的过程是发生在客户端浏览器
-
客户端浏览器在地址栏中输入网页的地址,取回来的只是html骨架,
-
再去发ajax请求,取回来数据,再显示到页面上(用vue,arttemplate,模板字符串等技术)
小结
- ssr:服务器端渲染。数据组装(从json—>dom结果)的过程是发生在服务器上的。客户端取回来的是有数据页面。
- csr:客户端渲染。数据组装(从json—>dom结果)的过程是发生客户端(浏览器)的。主要是通过ajax取数据,再用模板技术(arttemplate,vue…)来渲染。
SEO与爬虫
客户端渲染技术对SEO不友好,爬虫无法获取有效内容!
什么是SEO
Search Engine Optimization ,搜索引擎优化。
加入我们有个学习JavaScript网页,希望被更多人的知道,而推广自已的网页的方式之一是借助搜索引擎的力量,让其它人在百度中搜索某个关键字时就能找到你的网页。
那么,百度是如何得知http://xxxx.com/abc·········这个页面中有关键字javascript
的呢?
百度服务器会使用一些程序来获取网页的内容,分析内容,以提取出关键字,便 于在搜索时能找到网页。 这个过程一般称为爬虫。
SEO的目标是更明确地告诉百度,你的网页上的内容,以便更好地被收录。
爬虫
我们平常上网都是在浏览器中请求网页地址,而爬虫代码是通过代码的方式去请求网页地址。
下面就是一个简陋的爬虫:
步骤:
- 创建一个.js文件(假设名字是spider.js)内容如下:
// 引入http模块
const http = require("http")
// 定义要爬虫程序 访问的 网页
let url = "http://localhost:8080/index_csr.html"
// let url = "http://localhost:8080/index_ssr.html"
// 用http模块中的get方法去请求 url 这个网页,把请求到的结果显示出来。
http.get(url, (res) => {
let result = ""
res.setEncoding('utf8');
res.on('data', (chunk) => {
result += chunk
});
res.on('end', () => {
console.log(`爬虫得到的数据是: ${result}`);
});
});
-
运行js文件
node spider.js
这一段代码会去请求代码中url指定的网页(可在这里下载运行示例代码),并打印出结果。
-
小结
对于采用客户端渲染的网页:爬虫程序无法获取有效的数据。(因为有效的数据是通过ajax在客户端发出来,去取回来的) 所以客户端渲染对seo不友好,这就是为什么vue单页面应用不利于seo优化的原因。
对于采用服务器端渲染的网页:爬虫程序可以获取有效的数据。(因为获取的内容就已经包含数据)
客户端渲染与服务器端渲染的区别
在网页开发的初期 (2000-2010年),没有前后端的概念的,也没有前端工程师的概念。所有的网页上的内容:html,css,js,数据 , 包括提供数据的服务器代码 全是程序员写的。
我们的技术全是属于SSR范筹。 (php,asp,asp.net, jsp…)
优点 | 缺点 | |
---|---|---|
客户器渲染(通过ajax求数据) | 适合前后端分离开发,方便维护,单页应用(Singe Page Application)中几乎都是客户端渲染 | 首屏加载慢,不利于 SEO |
服务器渲染 | 响应速度快,有利于 SEO | 前后端代码混合在一起,难以开发和维护,不适合进行前后端分离开发 |
我们使用vue开发的项目都是典型的SPA(单页面应用程序single page application),是标准的前后端分离的,很显然是属于客户端渲染的范畴。
- 好处:页面导航不用刷新整个页面,体验好,有利于前后端分离开发
- 缺点:不利于 SEO(因为单页面应用中都是使用客户端渲染的方式),还有首次响应慢(第1次要加载大量的公共资源)
是否有相对折中的解决方案,或者是两全其美的解决方案呢?
即vue做的项目也可以达到seo优化等实现服务器端渲染的优点的实现
二、Vue的SSR介绍及示例演示
Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为**服务器端的 HTML 字符串
**,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
- 学习vue-server-renderer
- 在服务器端使用-server-renderer
创建项目( nodejs)
创建一个名为vue-ssr的空目录进入,并运行:
npm init --yes
来做初始化。
安装依赖
npm install vue vue-server-renderer --save
示例:在node代码使用vue渲染数据
新建文件为01.js,内容如下:
const Vue = require('vue')
// 第 1 步:创建一个 vue实例
const app = new Vue({
template: `<div>{{title}}</div>`,
data:{
title:"hello,vue ssr!"
}
})
// 第 2 步:创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer()
// 第 3 步:将 Vue 实例渲染为 HTML
// 方法一:回调函数
renderer.renderToString(app, (err, html) => {
if (err) throw err
console.log(html)
// => <div data-server-rendered="true">Hello World</div>
})
运行结果:
node 01.js
输出结果如下:
<div data-server-rendered="true">hello,vue ssr!</div>
<div data-server-rendered="true">hello,vue ssr!</div>
小结:
vue-server-renderer
就可以把Vue实例解析成 它对应的dom结构
示例:与服务器功能集成
目标:
请求网页http://localhost:3000/index.html
,在服务器端使用vue-server-renderer来渲染生成html文档,并返回。
涉及npm包:
- express(安装express)
npm i express
- vue, vue-server-renderer
创建02.js,代码如下:
/**
* 目标: 用户输入http://localhost:3000/index.html
* 在服务器使用vue-server-renderer来把vue实例渲染生成html文档,并返回
*
* 1) 先安装express
*/
const express = require('express')
const app = express()
const Vue = require('vue')
app.get('/index.html',(req, res) => {
// 1. 创建vue实例
const vm = new Vue({
template: `
<div>
<h2>{{title}}</h2>
<div v-for="item in list">
{{item.author}} - {{item.content}}
</div>
</div>
` ,
data: {
title: 'vue ssr',
list: [{author:"李白",content:"举杯邀明月"},
{author:"杜甫",content:"喝酒不开车"},
{author:"杜甫",content:"喝酒不开车"}]
}
})
// res.send('ok')
// 2. 创建一个renderer
const renderer = require('vue-server-renderer').createRenderer()
// 3. 渲染:把vue实例渲染成HTML字符串
renderer.renderToString(vm, (err, html) => {
if (err) {
console.log(err)
} else {
res.send(html)
}
})
})
app.listen(3000, ()=>{
console.log('3000.....')
})
运行
node 02.js
访问:
http://localhost:3000/index.html
-
小结:
在服务器使用vue,通过vue-server-render包,来把vue实例转成html字符串,再返回给客户端,这就是用vue技术的ssr。
三、Nuxt服务器端渲染框架
Nuxt官网基本介绍
如果我们实际项目中需要使用vue做服务器端渲染那么我们可以使用Nuxt服务器端渲染框架完成服务器端适配
基于 Vue、Webpack 和 Babel Nuxt.js 集成了以下组件/框架,用于开发完整而强大的 Web 应用:
注意:
- Nuxt**
不是
Vue官方**提供的 - Nuxt是**
基于Vue
**的服务端渲染的框架 - Nuxt.js 预设了利用 Vue.js 开发**
服务端渲染
**的应用所需要的各种配置。
next.js : react 服务器渲染框架
nuxt.js : vue 服务器渲染框架
使用脚手架工具创建项目
第一步:全局安装脚手架工具
npm i -g create-nuxt-app
----------------------------------
C:\Users\fanyoufu\AppData\Roaming\npm\create-nuxt-app -> C:\Users\fanyoufu\AppData\Roaming\npm\node_modules\create-nuxt-app\lib\cli.js
> ejs@2.7.4 postinstall C:\Users\fanyoufu\AppData\Roaming\npm\node_modules\create-nuxt-app\node_modules\ejs
> node ./postinstall.js
Thank you for installing EJS: built with the Jake JavaScript build tool (https://jakejs.com/)
npm WARN notsup Unsupported engine for create-nuxt-app@3.1.0: wanted: {"node":">=10.20.0"} (current: {"node":"10.16.3","npm":"6.13.4"})
npm WARN notsup Not compatible with your version of node/npm: create-nuxt-app@3.1.0
+ create-nuxt-app@3.1.0
added 38 packages from 4 contributors, removed 3 packages and updated 162 packages in 61.806s
第二步:创建项目
# 格式: create-nuxt-app 项目名
create-nuxt-app nuxt-demo
本示例中的采用的方案
如果你选择了某个ui框架,则在创建项目时,就会自动给你配置好。
第三步:安装完成
第四步:启动项目
- 进入目录。脚手架在创建项目时,会给我们创建一个文件夹。
- 运行命令。
运行启动命令npm run dev
启动成功:
在浏览器查看效果:
目录结构
|--components:组件
|--layouts:布局
|--middleware: 中间件
|--plugins:插件
|--static:静态资源
|--store: vuex
|--nuxt.config.js
最重要的就是pages!
运行项目
npm run dev
如果没有什么错误,你应该可以在localhost:3000
中看到代码运行的效果了。
- 此时访问的是pages下的index.vue的内容。
创建第二个页面
在pages文件夹,新建一个普通的page1.vue文件。
放在pages下面的.vue文件,nuxt会自动去给它创建路由
/pages/page1.vue
<template>
<div>
<h1>page1</h1>
<p>{{ title }}</p>
<nuxt-link to="/">
首页
</nuxt-link>
</div>
</template>
<script>
export default {
data () {
return {
title: '剑客'
}
}
}
</script>
注意:
- nuxt-link 用来进行页面跳转。
- 在地址栏中输入http://localhost:3000/page1来访问这个页面
小结
- 项目中只有一个包nuxt,而其它的vue,vue-router,webpack等都集成了。
- 页面全写在pages目录下(layout下面的default.vue 是默认的模板)。
- 不需要额外去设计路由。
- 是单页的:路由跳转,页面是不会刷新
认识单页应用和服务端渲染
服务端渲染
上面的例子中,http://localhost:3000/page1 得到的内容是
也就是说,从服务器取回来的是包含数据的网页。这时就满足单页面应用特点同时也满足服务端渲染。
单页应用
在从地址:http://localhost:3000/page1 转到 http://localhost:3000/ 时,页面并不刷新。
这就是典型的单页应用特点。
服务器渲染的能力
在我们前面学习的vue项目中,都是在客户端渲染:
- 先把页面(不带数据的)取回来
- 再发ajax去求接口,获取数据,在浏览器端渲染数据
服务器端渲染是:
- 请求页面
- 服务器收到这个接求,去请求数据,生成页面(带数据的)
- 返回给客户端(有数据的)
讨论发ajax取数据的过程。
回顾客户端渲染的情况
由于在创建项目时,已经选择了axios,所以,在vue的实例中,可以直接通过this.$axios来使用axios.
在page1.vue中改写代码如下。
就是一个最简单的ajax请求,并用v-for渲染数据的例子。
<template>
<div>
<h3>{{ title }}</h3>
<div>
<p v-for="item in books" :key="item.id">
{{ item.bookname }}
</p>
</div>
<!-- 在页面之间跳转 -->
<nuxt-link to="/">
主页
</nuxt-link>
</div>
</template>
<script>
export default {
name: 'MyComponent',
components: { },
props: { },
data () {
return {
title: '剑客',
books: [],
list: [
'html', 'css', 'js'
]
}
},
created () {
this.loadData()
},
methods: {
async loadData () {
const result = await this.$axios({
method: 'GET',
url: 'http://www.liulongbin.top:3006/api/getbooks'
})
// eslint-disable-next-line no-console
console.log(result)
this.books = result.data.data
}
}
}
</script>
运行代码。并在浏览器中观察数据的请求来源。
created中的代码是会在浏览器发出ajax请求的。
其它测试用的接口也可以使用的。
https://github.com/typicode/jsonplaceholder
这里还是一个典型客户端渲染的效果。
不难发现:以上渲染数据还是发生在客户端,那么如何让渲染的数据发生在服务端?
学习服务器端渲染asyncData 生命周期钩子函数
特点
-
asyncData是Nuxt中额外增加的 vue 生命周期的钩子函数。在这个钩子函数中,代码可以在服务端执行。
-
没有 Vue 实例的 this,this 指向 undefined
-
发请求,以获取数据。数据要求以对象格式返回,并最终会附加到vue组件的data数据项中,可以正常在template中使用。
<template> <div> <h3>{{ title }}</h3> <div> <p v-for="item in serList" :key="item"> {{ item }} </p> </div> <div> <p v-for="item in books" :key="item.id"> {{ item.bookname }} </p> </div> <!-- 在页面之间跳转 --> <nuxt-link to="/"> 主页 </nuxt-link> </div> </template> <script> export default { name: 'MyComponent', components: { }, props: { }, asyncData () { // eslint-disable-next-line no-console 忽略单行代码检测 console.log('asyncData 是在服务器端执行的') return { serList: ['服务器端数1', '服务器端数2'] } }, data () { return { title: '剑客', books: [], list: [ 'html', 'css', 'js' ] } } } </script>
上例是获取同步数据。
获取接口数据
目标:让数据请求发生在服务端,asyncData钩子函数中处理请求
在asyncData钩子函数中,去发ajax请求,获取数据。
<template>
<div>
<h3>{{ title }}</h3>
<div>
<p v-for="item in newBooks" :key="item.id">
{{ item.bookname }}
</p>
</div>
<!-- 在页面之间跳转 -->
<nuxt-link to="/">
主页
</nuxt-link>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'MyComponent',
async asyncData () {
// 在asyncData中发ajax请求
const result = await axios({
method: 'GET',
url: 'http://www.liulongbin.top:3006/api/getbooks '
})
// eslint-disable-next-line no-console
// 获取到的数据,通过return {} 的格式 把数据附加在当前Vue实例中的data上。
return {
newBooks: result.data.data
}
},
data () {
return {
title: '剑客',
books: [],
list: [
'html', 'css', 'js'
]
}
}
}
</script>
<style scoped lang='less'></style>
-
asyncData 它是一个特殊的钩子函数,它可以在服务器端运行。
-
上面的代码中,产生出页面就已经有数据了,不像在created中的代码是会在浏览器发出ajax请求的。
工作过程:
asyncData的执行时机
在asyncData()中的写的代码会在服务器端执行,也会在客户端执行(路由切换)。
假设在pages/index.vue这个页面中使用asyncData钩子函数,在这个函数中去请求接口,获取数据,并交给vue来渲染。
- 用户第一次请求这个http://localhost:3000/index页面,asyncData()中的代码会在服务器端执行,获取完成数据后,渲染好html再返回。
- 用户在页面上进行路由跳转:
- 从http://localhost:3000/index跳到http://localhost:3000/otherpage。
- 再从http://localhost:3000/otherpage回到http://localhost:3000/index页,此时
- asyncData()中的代码会在客户端执行。
从生命周期来说
在服务器端执行
按如下顺序执行这三个钩子
- asyncData
- beforeCreate
- created
在客户器端执行
vue的中的钩子函数正常执行。
asyncData 也会执行:只在路由切换时
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- …
重点:
- asyncData在不同的时机,可以在服务器和浏览器都执行。
发请求取数据的操作:
- 如果 需要SEO,写在asyncData中
- 如果不需要SEO,则可以写到mounted中,
- 不要写在created中,因为它会在服务器端和客户端渲染时都执行,如果这里写调用接口取数据,则会请求两次。
那么为什么我们还需要vuecli框架呢?直接使用nuxt框架不好么?
Nuxt与vuecli两个框架的对比
我们学习的vue知识,可以直接在这两个脚手架创建的项目中使用。
区别:
- nuxt 创建项目,是看重了它的服务器端渲染的功能。同时,要付出代价:布置上线需要服务器端的支持。
vuecli 打包之后得到的是一个dist 目录,就是index.html,.js,css… 。是可以双击打开的(不包含服务器代码,与服务器没有任何的关系,它就是发ajax,获取数据,并显示)。
nuxt项目打包之后会得到客户端与服务器端的代码,在上线时,需要把服务器代码也上线。
spa-vue-cli: 客户端渲染, 缺点:首屏比较慢(路由懒加载,… 图片…,打包优化), 对SEO不友好。
- 打包进入原生的app,h5应用,本身不需要seo
- 预渲染插件做seo优化:prerender-spa-plugin 把一些页面提前生成对应的.html(也不是必须要nuxt框架)