公司现有的项目只有落地页是经过前端自己server读取pug文件进行服务端渲染的,固然是为了首屏加载速度以及SEO。Nuxt.js 是一个基于Vue.js的通用应用框架,预设了利用Vue.js开发服务端渲染的应用所须要的各类配置,只须要安装官方文档的要求进行开发,就能够很好的解决SSR的问题。咱们以一个简单的博客为例,来实践一下Nuxt.js。javascript
当前基于Nuxt.js的简化版博客,包括注册、登陆、文章列表页面、文章详情页、以及用户列表页等几个页面,用户信息使用了Vux进行存储,异步数据使用了asyncData进行获取,配合了nuxtServerInit、cookie来处理刷新页面后Vux数据丢失的问题,同时使用了error模板页面处理常规错误,使用了中间件进行了简单的权限校验。该项目不足点,统一封装了axios的方法,可是没有考虑到服务端请求接口,token的处理。css
1. incoming Request 浏览器发出的请求)
2. nuxtServerInit 服务端接受请求后,要检查当前有没有 nuxtServerInit配置项,若是有就执行这个函数
3. store action 用来操做vuex
4. middleware 能够作jWT等一些操做。
5. validate() 检验参数,参数检验失败,能够在layout里的error里面进行捕捉。
6. asyncData()& fetch() asyncData用来渲染组件,fetch用来渲染vuex
7. Render
复制代码
Nuxt扩展之后的生命周期和方法如下:前端
beforeCreate: ƒ beforeCreate()
components: {NuxtLoading: {…}}
computed: {isOffline: ƒ}
context: {isStatic: false, isDev: true, isHMR: true, app: {…}, payload: undefined, …}
created: ƒ created()
data: ƒ data()
head: {title: "nuxt-meituan-ssr", meta: Array(3), link: Array(1), style: Array(0), script: Array(0)}
methods: {refreshOnlineStatus: ƒ, refresh: ƒ, errorChanged: ƒ, setLayout: ƒ, loadLayout: ƒ}
mounted: ƒ mounted()
nuxt: {…}
render: ƒ render(h, props)
router: VueRouter {app: Vue, apps: Array(1), options: {…}, beforeHooks: Array(2), resolveHooks: Array(0), …}
watch: {nuxt.err: "errorChanged"}
复制代码
注意:vue
能够在package.json下面修改配置,以下。java
"config":{
"nuxt":{
"host":"127.0.0.1",
"port":"3304"
}
}
复制代码
能够在assets里添加全局Css文件,如在assets下的Css文件夹目录下添加了一个index.css文件,而后在nuxt-config.js里配置该css文件路径便可。 css:['~assers/css/index.css']ios
在css配置的是,须要将'~/'后面的'/'去除掉。nginx
<img src="~/static/logo.jpg"/>
backround-image:url('~static/logo.jpg');
复制代码
一样,咱们在Css文件里添加一些动画代码,通常样式会在其后面添加-active和-leave-active,其实和Vue动画形式一致。其中以page开头的动画,默认会做用于所有页面,若是想给特定的页面加动画,能够在对应的页面script里引用,如 transitions: 'bounce'便可。vuex
.page-enter-active, .page-leave-active {
transition: opacity .3s
}
.page-enter, .page-leave-active {
opacity: 0
}
.bounce-enter-active {
animation: bounce-in .8s;
}
.bounce-leave-active {
animation: bounce-out .5s;
}
@keyframes bounce-in {
0% { transform: scale(1) }
50% { transform: scale(1.01) }
100% { transform: scale(1) }
}
@keyframes bounce-out {
0% { transform: scale(1) }
50% { transform: scale(1.01) }
100% { transform: scale(1) }
}
复制代码
同Vue-router,有声明式和编程式两种方式,无非是标签变成了 router.push(...)npm
nuxt-link :to="{name:'article',params:{id:1234}}" >声明式</nuxt-link>
// 编程式
this.$router.push({
name:'article',
params:{
id:1234
}
})
复制代码
Nuxt.js提供了一个validate的生命周期钩子,能够在此进行参数的校验。以文章详情校验id为例,咱们须要判断传入的id是不是数字,能够像下面这样处理。编程
validate({ params }) {
return /^\d+$/.test(params.id)
}
复制代码
能够在layout下新建一个error.vue页面,内容以下,当访问一个不存在的页面的时候,或者参数检验失败的时候,或者咱们在middleware中间件处理抛出异常的时候,都会跳转到该页面。
<template>
<div class="container">
<h1 v-if="error.statusCode === 404">页面不存在</h1>
<h1 v-else>应用发生错误异常</h1>
<nuxt-link to="/">首 页</nuxt-link>
</div>
</template>
<script>
export default {
props: ['error'],
layout: 'blog' // 指定模板页面
}
</script>
复制代码
middleware中的文件抛出错误
export default function({ store, error, redirect }) {
if (!store.state.user.userInfo.auth) {
error({
message: '没有权限哦!',
statusCode: 403
})
}
}
复制代码
loading 属性配置 能够在nuxt-config.js设置loading的颜色,使用了this.loading可能没法在created里当即使用。此种配置loading有严重的缺陷,没法知道真正的加载进度。也能够自定义加载组件,loading: '~components/loading.vue'。
export default {
mounted () {
this.$nextTick(() => {
this.$nuxt.$loading.start()
setTimeout(() => this.$nuxt.$loading.finish(), 500)
})
}
}
复制代码
Nuxt.js提供了两个函数,asyncData和fetch函数。asyncData 获取组件的数据,fetch 在渲染页面以前获取数据填充应用的状态树(store)。
// 方式一
asyncData({ app,params,route,query,error}) {
return getUserlist({}).then(res => {
let user = [];
user = res.list
console.log(user,'user')
return {user}
})
.catch(err => {
console.log(err)
})
},
// 方式二
async asyncData({ app }) {
let data = await getUserlist({});
let user = data.list;
return { user }
}
复制代码
export default {
fetch ({ store, params }) {
return axios.get('http://my-api/stars')
.then((res) => {
store.commit('setStars', res.data)
})
}
}
</script>
// 或者使用 async 或 await 的模式简化代码以下:
<template>
<h1>Stars: {{ $store.state.stars }}</h1>
</template>
<script>
export default {
async fetch ({ store, params }) {
let { data } = await axios.get('http://my-api/stars')
store.commit('setStars', data)
}
}
</script
复制代码
head() {
return {
// title: '',这里一旦声明,在asyncdata里修改也不起做用,直接以这个为准
meta: [
{
hid: 'description', // nuxt.config 替换惟一标识 hid { hid: 'description', name: 'description', content: 'Nuxt.js project' }
name: 'content',
content: '文章详情'
}
]
}
},
复制代码
asyncData({ app, params }) {
const id = params.id;
return getArticleDetail({ id })
.then(result => {
app.head.title = result.title;
})
.catch(err => {})
}
复制代码
登陆成功之后,咱们会在cookie和Vuex中缓存token信息,当界面刷新的时候,会走store里的nuxtServerInit 函数,该函数仅在每一个服务器端渲染中运,可使用req.headers.cookie获取浏览器的cookie,再次更新store里的值,接着会走到中间件,中间件进行验证,若是有token信息则继续,没有则跳转到登陆页。
1. 为何要在nuxtServerInit更新store的值?
须要在middleware里使用,不然刷新后store里的值为空了。
2. 客户端调用接口能够拿到token,服务器端如何拿到?
能够经过nuxtServerInit里的req拿到请求信息的cookie,而后请求接口。
3. 先后端分离,刷新的时候如何保证用户名、token等信息依然存在?
能够像上面同样,每次取cookie的值再次更新store,但这样有一个问题,cookie可能会被篡改,后端代码须要作验证。也能够每次刷新从新经过token请求接口,更新用户信息。
复制代码
store代码
import Vue from 'vue';
import Vuex from 'vuex';
import user from './modules/user';
import { COOKIE_KEY } from '~/assets/js/constant.js';
Vue.use(Vuex);
const store = () =>
new Vuex.Store({
modules: {
user
},
actions: {
async nuxtServerInit({ commit, dispatch }, { req, app }) {
if (req.headers.cookie) {
let parsedResult = {};
req.headers.cookie.split(';').forEach(cookie => {
const currentCookie = cookie.split('=');
parsedResult[currentCookie[0].trim()] = (currentCookie[1] || '').trim();
});
const userInfo = {
name: parsedResult[COOKIE_KEY.NAME],
token: parsedResult[COOKIE_KEY.TOKEN]
};
commit('user/setUserInfo',userInfo);
}
}
}
});
export default store;
复制代码
中间件代码
export default function({ store, error, redirect }) {
if (!store.state.user.userInfo.token || !store.state.user.userInfo.name) {
// error({
// message: 'You are not connected',
// statusCode: 403
// })
redirect('/');
}
}
复制代码
查看网页源代码能够看到:
server{
listen 3000;
server_name felix12345.club;
gzip on;
gzip_buffers 32 4K;
gzip_comp_level 6;
gzip_min_length 100;
gzip_types application/javascript text/css text/xml;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
location / {
root /data/ww/nuxt;
proxy_pass http://127.0.0.1:3002;
proxy_set_header X-Real-IP $remote_addr;
}
}
复制代码
在线访问地址: http://felix12345.club:3000/article/