Nuxt.js实现一个SSR的前端博客的示例代码
为什么要用nuxt.js
公司现有的项目只有落地页是通过前端本身server读取pug文件进行服务端渲染的,当然是为了首屏加载速度以及seo。nuxt.js 是一个基于vue.js的通用应用框架,预设了利用vue.js开发服务端渲染的应用所需要的各种配置,只需要安装官方文档的要求进行开发,就可以很好的解决ssr的问题。我们以一个简单的博客为例,来实践一下nuxt.js。
项目介绍
当前基于nuxt.js的简化版博客,包括注册、登录、文章列表页面、文章详情页、以及用户列表页等几个页面,用户信息使用了vux进行存储,异步数据使用了asyncdata进行获取,配合了nuxtserverinit、cookie来处理刷新页面后vux数据丢失的问题,同时使用了error模板页面处理常规错误,使用了中间件进行了简单的权限校验。该项目不足点,统一封装了axios的方法,但是没有考虑到服务端请求接口,token的处理。
目录结构
- assets: 资源文件。用于组织未编译的静态资源如 less、sass或 javascript。
- components: 组件。
- layouts: page: 模板页面,默认为 default.vue可以在这个目录下创建全局页面的统一布局,或是错误处理页面页,需要提供一个nuxt 标签,类似于router-view
- middleware: 中间件,放置自定义的中间件,会在加载组件之前调用。可以在页面中调用: middleware: '中间件名称'。
- pages: 页面,index.vue 为根页面,nuxt.js 框架读取该目录下所有的 .vue文件并自动生成对应的路由配置,如需要动态参数id,则可以添加_id的文件,必须是下划线加参数名。
- plugin: 插件,用于组织那些需要在 根vue.js应用实例化之前需要运行的 javascript 插件。
- static: 静态文件,静态文件目录 static用于存放应用的静态文件,此类文件不会被 nuxt.js 调用 webpack 进行构建编译处理。
- store: 用于组织vuex状态管理。具体使用请移步至 官网。
- nuxt.config.js: nuxt.config.js文件用于组织nuxt.js 应用的个性化配置,配置head,loading,css,plugins等。
nuxt.js生命周期
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.js生命周期的钩子只有beforecreate和created会在服务端和客户端渲染。
- 以上生命周期里都获取不到window对象。
- asyncdata和fetch我们可以拿到数据,不要尝试挂载数据到data上,此时获取不到this对象。
开发总结
如何修改默认启动端口?
可以在package.json下面修改配置,如下。
"config":{ "nuxt":{ "host":"127.0.0.1", "port":"3304" } }
如何添加全局的样式?
可以在assets里添加全局css文件,如在assets下的css文件夹目录下添加了一个index.css文件,然后在nuxt-config.js里配置该css文件路径即可。 css:['~assers/css/index.css']
通过别名访问图片在template里是正确的,为何在css设置背景图却报错?
在css配置的是,需要将'~/'后面的'/'去除掉。
<img src="~/static/logo.jpg"/> backround-image:url('~static/logo.jpg');
如何添加路由动画?
同样,我们在css文件里添加一些动画代码,一般样式会在其后面添加-active和-leave-active,其实和vue动画形式一致。其中以page开头的动画,默认会作用于全部页面,如果想给特定的页面加动画,可以在对应的页面script里引用,如 transitions: 'bounce'即可。
.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(...)
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) }
如何添加404等错误页面?
可以在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可以使用promise也可以使用async函数,记住,此时返回的东西需要用一个对象进行包裹,不能挂载到data里,此时没有this对象。
// 方式一 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 } }
fetch函数同上,可以使用promise也可以使用async函数,通常会commit一个mutation。
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>
如何动态修改title的内容?
如果是写死的,可以直接修改head的配置。
head() { return { // title: '',这里一旦声明,在asyncdata里修改也不起作用,直接以这个为准 meta: [ { hid: 'description', // nuxt.config 替换唯一标识 hid { hid: 'description', name: 'description', content: 'nuxt.js project' } name: 'content', content: '文章详情' } ] } },
如果是动态数据从数据源里获取,然后通过asyndata里的app对象,动态修改head的title。
asyncdata({ app, params }) { const id = params.id; return getarticledetail({ id }) .then(result => { app.head.title = result.title; }) .catch(err => {}) }
如何进行权限jwt验证?
登录成功以后,我们会在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('/'); } }
nginx部署
npm run build
选择build以后的四个文件: .nuxt, static, nuxt.config.js, package.json上传到服务器。
pm2 pm2 start npm --name 'package.json.name' -- run start
nginx配置
查看网页源代码可以看到:
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; } }
这样,使用nuxt.js实现了一个服务端渲染的简易博客。
在线访问地址: http://felix12345.club:3000/article/
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。