Nuxt SSR 服务端渲染 详解
Nuxt.js 支持基于 Vue 应用程序生成静态站点。这是“两全其美”的,因为你不要服务器,但是仍能获得 SEO 的好处,这是因为 Nuxt 将预先渲染所有页面,并且包括必要的 HTML。
Nuxt SSR 服务端渲染 详解
1、Nuxt项目构建
Nuxt.js 支持基于 Vue 应用程序生成静态站点。这是“两全其美”的,因为你不要服务器,但是仍能获得 SEO 的好处,这是因为 Nuxt 将预先渲染所有页面,并且包括必要的 HTML。
nuxt官网地址:https://www.nuxtjs.cn/
项目的构建,这里我们可以之间使用如下命令进行构建一个nuxt项目。npx 在 NPM 版本 5.2.0 默认安装了
npx create-nuxt-app web-nuxt-lzq
在安装时会让你进行一些选择,如下图
安装项目之后,直接使用npm run dev 进行项目启动,访问默认端口3000
首先我们对项目的目录进行简要说明:
目录 | 说明 |
---|---|
assets | 资源目录 用于组织未编译的静态资源如 LESS、SASS 或 JavaScript |
components | 组件目录 用于组织应用的 Vue.js 组件。Nuxt.js 不会扩展增强该目录下 Vue.js 组件,即这些组件不会像页面组件那样有 asyncData 方法的特性 |
layouts | 布局目录 用于组织应用的布局组件 |
middleware | 用于存放应用的中间件 |
pages | 页面目录 用于组织应用的路由及视图。Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置 |
plugins | 插件目录 用于组织那些需要在 根vue.js应用 实例化之前需要运行的 Javascript 插件 |
static | 静态文件目录 用于存放应用的静态文件,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下 |
store | 用于组织应用的 Vuex 状态树 文件。 Nuxt.js 框架集成了 Vuex 状态树 的相关功能配置,在 store 目录下创建一个 index.js 文件可激活这些配置 |
nuxt.config.js | 用于组织 Nuxt.js 应用的个性化配置,以便覆盖默认配置 |
package.json | 用于描述应用的依赖关系和对外暴露的脚本接口 |
我的nuxt项目git地址:https://gitee.com/lizuoqun/web-nuxt-lzq.git
2、Nuxt的生命周期
nuxt生命周期:主要还是区分了服务端和客户端的生命周期钩子,如下图所示:
下表是对nuxt的生命周期钩子的一些说明
钩子 | 说明 |
---|---|
validate | 用来校验动态路由参数的有效性。 在该生命周期内可以访问this.methods 该生命周期必须返回一个布尔值或者包装了布尔值的Promise,如果为false会跳转进layouts/error.vue, 使用context.params.xxx来验证路径 |
asyncData | nuxt中新增的生命周期,在这里获得异步数据,然后将数据提交给服务端,在服务端拼接好html后发送给客户端,一般写成异步函数。该生命周期在beforeCreate前调用,同样在服务器端调用,类似setup所以不能使用this window document asyncData返回一个对象,和data一样用来渲染页面。 asyncData接收一个上下文参数,里面含有多个对象和方法。 1、error:该函数接收两个属性的对象 statusCode message表示状态码和错误信息,调用会将当前页面替换为layouts中的error.vue 2、store:vuex 3、redirect:类似$router,用来在服务器端跳转路由 4、query params:该页面路由的query params 5、app: |
fetch | 该方法用于在渲染页面前填充store,它不能返回数据 |
watchQuery | 该对象属性可以是字符串或字符串组,设置监听的路由query部分,如果发生变化则会重新触发asyncData fetch等生命周期钩子 |
head | 返回一个对象用来设置<head> 标签内的标签。在nuxt.config.js中能设置全局的标签 |
2.1 nuxtServerInit 钩子
如果在状态树中指定了 nuxtServerInit 方法,Nuxt.js 调用它的时候会将页面的上下文对象作为第 2 个参数传给它(服务端调用时才会酱紫哟)。当我们想将服务端的一些数据传到客户端时,这个方法是灰常好用的。在这里,改钩子定义在stroe目录下,使用一个index.js文件进行保存。
export const actions = {
nuxtServerInit(stroe, context) {
console.log("nuxtServerInit")
// console.log(stroe)
// console.log(context)
}
}
2.2 middleware 中间件
中间件允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。每一个中间件应放置在 middleware/ 目录。文件名的名称将成为中间件名称 (middleware/auth.js将成为auth 中间件)。这里定义一下这个auth中间件
export default function(context) {
console.log("this is middleware")
console.log(context)
}
中间件执行流程顺序如下:
- nuxt.config.js
- 匹配布局
- 匹配页面
首先在nuxt.config.js文件当中注册这个中间件
router: {
middleware: 'auth',
},
然后我们可以在布局进行引入该中间件,当页面进行加载时会加载布局,同样的就会加载这个中间件。
<template>
<Nuxt />
</template>
<script>
export default {
middleware: 'auth',
或者
middleware() {
console.log("this is page middleware")
},
}
</script>
在页面进行使用中间件的配置与在布局文件进行使用是相同的方式进行使用。
middleware: 'auth'
或者
middleware() {
console.log("this is page middleware")
},
2.3 validate 数据校验
validate可以让你在动态路由对应的页面组件中配置一个校验方法用于校验动态路由参数的有效性。
validate({
query,
params
}) {
console.log(query)
console.log(params)
console.log("this is validate")
return true
},
return为true时会继续访问该页面,return为false则会跳转到错误页面。
2.4 asyncData 与 fetch 异步数据
asyncData方法会在组件(限于页面组件)每次加载之前被调用。它可以在服务端或路由更新之前被调用。在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用 asyncData方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。
asyncData(context) {
console.log("asyncData")
// console.log(context)
return {
a: 5
}
},
fetch({
store
}) {
console.log("fetch")
},
其中使用asyncData进行return回来的数据在当前vue当中可以直接进行渲染
2.5 beforeCreate与 created异步数据
beforeCreate() {
console.log("beforeCreate")
},
created() {
console.log("created")
},
这两个方法在客户端或服务端都会被执行
2.6 window和this对象
window和this对象是作用于客户端的,所以在服务端的钩子当中进行获取该对象是拿不到的,并且会报错。
3、Nuxt路由
3.1 约定式路由
Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置。要在页面之间使用路由,我们建议使用<nuxt-link>
标签。对于nuxt当中的约定式路由,会根据page目录下的层级自动生成构建路由,并且使用index.vue文件来作为某个目录下的主页面。
这里我们构建一个目录已经里面对应的vue文件来进行测试路由:这里的index.vue就是主页面,one.vue和two.vue就是当前goods目录下其余页面,而_id.vue表示的是动态路由,需要携带路由后的动态参数再进行渲染页面
-- pages
-- goods
-- _id.vue
-- index.vue
-- one.vue
-- two.vue
首先还是index.vue,当直接访问localhost:30000/goodos就会转到这个主页面,这个主页面主要来做页面跳转,其中到one.vue和two.vue就是简单的页面跳转。主要是直接跳转到_id.vue,在后面直接跟上 /1 这种也就是一个动态路由,这个1事可以进行改变的。并且我们还可以给这个跳转的url进行携带参数,而使用:to进行跳转的时候就需要进行传递对象。参数分别是跳转的路由名称,这里使用中划线进行连接,随后是params动态路由参数,以及最后的url传参
<template>
<div>
<h3>this is goods</h3>
<nuxt-link to="/goods/one">one</nuxt-link>
<nuxt-link to="/goods/two">two</nuxt-link>
<br />
<nuxt-link to="/goods/1">detail 01</nuxt-link>
<nuxt-link to="/goods/1?a=1&b=2">detail 02</nuxt-link>
<nuxt-link :to="{name:'goods-id',params:{id:3},query:{a:3,b:3}}">detail 03</nuxt-link>
<nuxt/>
</div>
</template>
而后就是_id.vue文件,这里也就是动态路由跳转到的页面,在这里我们可以通过vue来进行获取参数之后丢给页面进行数据渲染。并且可以使用nuxt服务端的validate钩子进行数据校验
<template>
<div>
<h3>this is detail</h3>
<h4>{{params}}</h4>
<h4>{{query}}</h4>
</div>
</template>
<script>
export default {
validate({
params
}) {
return true
},
data() {
return {
params: '',
query: '',
}
},
mounted() {
console.log(this.$route.params)
this.query = this.$route.query
this.params = this.$route.params
}
}
</script>
3.2 扩展路由
在nuxt的约定式路由当中,路由都会根据目录的层级进行构建,但是这就会存在一个问题,现在我们知道当直接访问3000,路由返回的是pages下的index.vue,但是这里我们如果想访问3000/index这个地址来访问前面说的那个index.vue作为主页面这要如何做呢?nuxt给我们提供了一个扩展路由。我们可以直接在nuxt.config.js的配置文件当中添加如下配置:代表的是访问3000/hyy这个路由时,页面会跳转到根路径
router: {
middleware: 'auth',
extendRoutes(routes,resolve){
console.log(routes);
routes.push({
name:'root',
path:'/hyy',
component: resolve(__dirname,'pages/index.vue')
})
}
},
3.3 自定义错误页面
自定义错误页面,在layouts目录下的error.vue文件也就是nuxt的约定式路由的错误页面,并且这个页面还可以通过props接受error对象。这里我们进行简单的error错误数据渲染
<template>
<div>
<h3>this is error page</h3>
<h4>{{error.message}}</h4>
<h5>{{error}}</h5>
</div>
</template>
<script>
export default {
props: ['error'],
mounted() {
console.log(this.error)
}
}
</script>
3.4 统一动效和独享动效
在nuxt当中,assets目录主要是用来存放css、js等静态资源的,这时外面就会想了,既然这样的话,那么我们能不能给定义一个样式给到整个项目进行使用,这样当然是可以的,首先我们在assets目录下新增一个translation.css样式文件。这里的样式也相当于路由一样,都是约定式的样式,该表示路由页面进入退出的动效
.page-enter-active, .page-leave-active{
transition: opacity 5s;
}
.page-enter, .page-leave-active{
opacity: 0s;
}
之后我们还需要在nuxt的配置文件nuxt.conig.js文件将我们定义的css文件进行添加进去
css: [
'assets/css/translation.css'
],
而对于单个页面进行设置独享样式,我们可以定义一个transition并且设置一个名称test,这个test就相当于前面的page
<script>
export default {
transition: 'test',
}
</script>
只需要进行相对应的替换,其后在当前页面的style标签当中将样式添加进去。而当前面我们在nuxt的配置文件当中引入了那个assets下的样式文件,我们这里的样式也可以写道translation.css文件当中
<style>
.test-enter-active, .test-leave-active {
transition: 2s ease all;
}
.test-enter, .test-leave-active {
margin-left: 500px;
}
</style>
3.5 路由守卫
这里路由守卫主要可以分为以下几个方式进行配置,其中中间件作用于配置文件及布局、插件的前后置路由配置都属于全局的路由守卫,其余路由守卫都是作用于页面的,只对页面的路由生效。
-- 路由守卫
-- 中间件
-- nuxt.config.js 全局配置
-- layouts 布局配置
-- pages 单页面配置
-- 插件
-- 插件前置守卫
-- 插件后置守卫
-- 页面
-- 页面后置守卫
第一个的话就是中间件的路由守卫,这里就简单的做一个说明,在中间件当中(如下代码)我们可以获取到store,route,redirect,params,query对象,当我们需要进行数据拦截之后可以通过redirect对页面进行强制跳转。如一个系统,没有登录就直接访问系统的其他页面,这个时候就可以将其进行拦截,给其重定向到登录页。
export default function({store,route,redirect,params,query}) {
console.log("this is middleware")
redirect('/')
}
第二个的话就是插件的路由守卫,这里我们需要先定义一个插件,在plugins目录下新建一个route.js文件作为一个插件,之后将该插件在nuxt.config.js文件当中进行配置。
plugins: [
'~/plugins/route'
],
在插件当中我们可以获取到app对象,再通过app对象的beforeEach和afterEach进行设置前置和后置守卫,分别对应的是进入页面于离开页面要执行的方法和操作。
export default ({app,redirect}) => {
console.log("this plugins is start")
// 前置守卫
app.router.beforeEach((to,from,next)=>{
console.log("this plugins is top start")
console.log("to = ",to)
next()
})
// 后置守卫
app.router.afterEach((to,from,next)=>{
console.log("this plugins is end start")
console.log("go to = ",to)
})
}
而对于页面进行路由守卫的配置,我们同样的还是采用钩子进行定义,使用beforeRouteLeave钩子,当离开当前路由触发该方法(路由守卫)。
<script>
export default {
beforeRouteLeave(to,from,next){
next(window.confirm("是否离开"))
}
}
</script>
4、数据交互
Nuxt.js 扩展了 Vue.js,增加了一个叫 asyncData 的方法,使得我们可以在设置组件的数据之前能异步获取或处理数据
4.1 asyncData
asyncData方法会在组件(限于页面组件)每次加载之前被调用。它可以在服务端或路由更新之前被调用。在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用 asyncData方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。
由于asyncData方法是在组件 初始化 前被调用的,所以在方法内是没有办法通过 this 来引用组件的实例对象。
并且这里既然要进行数据交互,那么必然少不了axios来进行发送请求,并且同时我们将nuxt的代理的依赖加入进来。
npm i @nuxtjs/axios @nuxtjs/proxy --save
并且我们可以将axios直接丢到nuxt的配置文件当中,后续我们就可以直接再项目当中通过$axios进行获取axios对象
modules: [
'@nuxtjs/axios'
],
在前面我们有说过nuxt的目录,其中static是用来存放相对应的静态文件的,这里我们在static下加上一个data/data.json文件,用来模拟axios返回的数据。这里直接使用异步的方式来进行对asyncData方法进行测试,获取前面加入的json文件的数据,并且可以直接进行return返回给到客户端直接渲染
async asyncData({
$axios
}) {
let res = await $axios({
url: '/data/data.json'
})
console.log(res.data)
return {
asyncData: res.data
}
},
这里是获取同域的资源数据,但是当我们发送一个请求访问的是跨域的资源的时候,可以看到会报一个跨域的错误。
let crosData = await $axios({
url: 'http://localhost:8888/service-yqy-student/demo'
})
console.log(crosData.data)
这个时候我们需要在配置文件当中加上允许跨域和代理的配置
axios: {
proxy: true,
},
proxy: {
'/service-yqy-student': {
target: "http://localhost:8888",
chageOrigin: true,
pathRewrite: {}
}
},
4.2 拦截配置与携带token
在当中我们可以对axios异步请求进行请求、响应、error拦截。这里使用一个插件来对axios进行统一管理。添加一个axios.js作为一个插件,将插件加入到配置文件当中。
plugins: [
{
src: '~/plugins/axios',
ssr: true
}
],
这里在axios.js文件当中获取$axios对象,对其请求(onRequest)、响应(onResponse)、异常(onError)方法进行拦截处理
export default function({
$axios,
redirct,
route,
store
}) {
$axios.defaults.timeout = 10000
$axios.onRequest(config => {
config.headers.token = 'hyy-token'
console.log("请求拦截")
// console.log(config)
return config
})
$axios.onResponse(res => {
console.log("响应拦截")
console.log(res)
return res
})
$axios.onError(error => {
console.log("请求异常")
return error
})
}
后续的对于axios请求进行访问的时候,在这里都会被插件拦截处理,
4.3 自定义loadding
nuxt支持自定义的loading加载动效。在配置文件当中我们可以使用nuxt自带的简单的loadding加载。
loading: {color: "#339",height: "3px"},
同样我们也可以进行自定义的loadding加载,在配置文件当中指定对应加载的vue文件。
loading:'~/components/loadding.vue',
这里对应的loadding自定义加载就简单写了,后续的样式可以导入elementui来进行美化
<template>
<div>
<div v-if="loadding">loading...</div>
</div>
</template>
<script>
export default {
data() {
return {
loadding: true
}
},
methods: {
start() {
this.loadding = true
},
finish() {
this.loadding = false
}
}
}
</script>
4.4 VueX
对于vuex这里也不进行过多阐述了,这里主要说一下vuex定义以及其他页面如何获取vuex里面的数据,
nuxtjs对于vuex的约定式主要有以下几步。Nuxt.js 会尝试找到 src 目录(默认是应用根目录)下的 store 目录,如果该目录存在,它将做以下的事情:
- 引用 vuex 模块
- 将 vuex 模块 加到 vendors 构建配置中去
- 设置 Vue 根实例的 store 配置项
而对于vuex的使用可以参考该文章:VueX 详解
首先我们在store目录下使用 index.js 来作为默认的主store状态树。
export const state = () => ({
id: 1,
name: "黄月月"
})
export const mutations = {
updateId(state, id) {
state.id = id
},
updateName(state, name) {
state.name = name
}
}
export const getters = {
getId(state) {
return state.id
}
}
而在页面当中进行使用直接使用以下代码进行获取数据、而对于其他的相关vuex操作可查看前面关于vuex的文档。
console.log("state", this.$store.state)
4.4 状态持久化与token校验
这里要使用token我们需要先安装该依赖,用来读写cookie到浏览器当中。
npm i cookie-universal-nuxt --save
安装之后将该依赖加入到配置文件的modules当中。
modules: [
'@nuxtjs/axios',
'cookie-universal-nuxt'
],
这里我们已登录为例,发送axios请求进行获取数据,将数据再写道cookies当中,这里简单进行说明。发送一个axios请求之后,将一个user对象给丢给token进行管理。并且对vuex当中的数据进行更新。
<template>
<div>
<button @click="login">login</button>
</div>
</template>
<script>
export default {
methods: {
login() {
this.$axios({
url: '/data/data.json'
}).then((data) => {
let user = {
"id":10,
"name":"yueyueniao"
}
this.$cookies.set("user",user)
this.$store.commit("updateId",user.id)
console.log(this.$store.getters.getId)
})
}
}
}
</script>
触发方法后可以看到对应的cookie也被写入到了浏览器当中,并且对vuex当中的数据进行修改,再查看vuex当中的数据,也会发现数据被修改了。而我们在页面进行强刷的时候我们利用nuxt的生命周期nuxtServerInit钩子会被最新加载,直接获取这个cookie数据,如果获取不到我们就可以直接跳转到登录页面再进行操作。
export const actions = {
nuxtServerInit(stroe, {
app: {
$cookies
}
}) {
let user = $cookies.get("user")
console.log("nuxtServerInit ---- user = ", user)
}
}
再往后同理,我们可以在进行发送请求的时候进行拦截,将登录触发的cookies对象作为一个token由请求进行携带进行请求发送。而对于不存在的cookie的请求进行拦截跳转到指定页面。并且在cofig对象当中查看token对象是否存在这个user对象。
$axios.onRequest(config => {
config.headers.token = store.state ? store.state : null
console.log("请求拦截 config = ", config)
return config
})
4.5 整合ElementUI
首先先安装一下element-ui库
npm i element-ui --save
在这里我们使用一个插件将elementui给整合给Vue。
import Vue from 'vue'
import ElementUI from 'element-ui'
Vue.use(ElementUI)
然后将插件通过配置文件进行注册。
plugins: [
{
src: '~/plugins/element-ui',
ssr: true
}
],
并且将css样式也在配置文件导入。
css: [
'element-ui/lib/theme-chalk/index.css'
],
然后就可以在所有的pages页面使用elementui的组件了,
4.6 注销 清除cookies
在前面我们login登录的时候将user对象给作为cookie存到了浏览器当中,我们这里通过remove方法就可以将原先的cookie值进行删除清空并且将vuex当中的数据也进行清空处理。
<template>
<div>
<el-button size="mini" type="primary" plain @click="login">login</el-button>
<el-button size="mini" type="danger" plain @click="logout">logout</el-button>
</div>
</template>
<script>
export default {
methods: {
logout() {
this.$cookies.remove("user")
this.$store.commit("updateId", 0)
console.log(this.$store.getters.getId)
},
login() {
// 。。。省略,和前面登录代码一致
}
}
}
</script>
5、页面展示
5.1 使用scss
使用scss我们需要先安装style-resources
npm i @nuxtjs/style-resources --save
如果你的项目在初始化的时候没有安装scss,在这里也需要安装scss
npm install sass-loader --save-dev
npm i -D sass
npm install style-loader --save
安装之后我们在配置文件当中引入相对应scss的配置。这里先在modules当中导入这个style-resources,再定义一个styleRescources,用来指定到对应的scss文件。这里需要区分版本,以下是2.14.12以前的版本
modules: [
'@nuxtjs/style-resources'
],
styleResources:{
scss:[
'~assets/scss/index.scss'
]
},
而对于2.14.12之后的版本采用以下配置。
buildModules: [
'@nuxtjs/style-resources'
],
styleResources: {
scss: [
'@/assets/scss/index.scss'
]
},
而对于scss这个来说,就是将样式给到对应的变量,后续就可以直接通过变量来直接进行给定样式
$bg:blue;
而我们就可以直接在vue文件当中指定style的lang,直接使用对应的scss。
<template>
<div>
<div class="box">box</div>
<div class="box1">box1</div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
$box1-bg: red;
.box {
width: 40px;
height: 20px;
background-color: $bg;
}
.box1 {
width: 40px;
height: 20px;
background-color: $box1-bg;
}
</style>
5.2 自定义模板
在项目根目录下新增一个app.html文件。
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>
更多推荐
所有评论(0)