Nuxt.js实现一个SSR的前端博客的示例代码


Posted in Javascript onSeptember 06, 2019

为什么要用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生命周期

Nuxt.js实现一个SSR的前端博客的示例代码

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实现一个SSR的前端博客的示例代码

这样,使用Nuxt.js实现了一个服务端渲染的简易博客。

在线访问地址: http://felix12345.club:3000/article/

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
根据判断浏览器类型屏幕分辨率自动调用不同CSS的代码
Feb 22 Javascript
jquery 将disabled的元素置为enabled的三种方法
Jul 25 Javascript
Javascript调用C#代码
Jan 17 Javascript
jQuery Ajax 仿AjaxPro.Utility.RegisterTypeForAjax辅助方法
Sep 27 Javascript
jquery 触发a链接点击事件解决方案
May 02 Javascript
Yarn的安装与使用详细介绍
Oct 25 Javascript
全面解析node 表单的图片上传
Nov 21 Javascript
EasyUI为Numberbox添加blur事件的方法
Mar 05 Javascript
Bootstrap进度条实现代码解析
Mar 07 Javascript
Vue Cli与BootStrap结合实现表格分页功能
Aug 18 Javascript
JS如何在数组指定位置插入元素
Mar 10 Javascript
使用Ajax实现无刷新上传文件
Apr 12 Javascript
layui type2 通过url给iframe子页面传值的例子
Sep 06 #Javascript
使用layui的router来进行传参的实现方法
Sep 06 #Javascript
layui关闭弹窗后刷新主页面和当前更改项的例子
Sep 06 #Javascript
layui关闭层级、简单监听的实例
Sep 06 #Javascript
layui表格内容溢出的解决方法
Sep 06 #Javascript
layui表格 列自动适应大小失效的解决方法
Sep 06 #Javascript
vue中 this.$set的用法详解
Sep 06 #Javascript
You might like
PHP 5.3 下载时 VC9、VC6、Thread Safe、Non Thread Safe的区别分析
2011/03/28 PHP
php中in_array函数用法探究
2014/11/25 PHP
php自动识别文字编码并转换为目标编码的方法
2015/08/08 PHP
javascript椭圆旋转相册实现代码
2012/01/16 Javascript
tangram框架响应式加载图片方法
2013/11/21 Javascript
JavaScript通过元素的ID和name设置样式
2014/07/08 Javascript
javascript框架设计读书笔记之模块加载系统
2014/12/02 Javascript
$.extend 的一个小问题
2015/06/18 Javascript
jQuery实现的自适应焦点图效果完整实例
2016/08/24 Javascript
PHP+jquery+ajax实现分页
2016/12/09 Javascript
使用bat打开多个cmd窗口执行gulp、node
2017/02/17 Javascript
NodeJS如何实现同步的方法示例
2018/08/24 NodeJs
详解webpack自定义loader初探
2018/08/29 Javascript
React Native中Mobx的使用方法详解
2018/12/04 Javascript
原生javascript自定义input[type=radio]效果示例
2019/08/27 Javascript
uni-app自定义导航栏按钮|uniapp仿微信顶部导航条功能
2019/11/12 Javascript
Vue初始化中的选项合并之initInternalComponent详解
2020/06/11 Javascript
jquery+ajax实现异步上传文件显示进度条
2020/08/17 jQuery
[06:20]2015国际邀请赛第三日top10
2015/08/08 DOTA
Python中__call__用法实例
2014/08/29 Python
Python实现一个简单的MySQL类
2015/01/07 Python
python实现指定字符串补全空格、前面填充0的方法
2018/11/16 Python
django框架自定义模板标签(template tag)操作示例
2019/06/24 Python
Python求两个字符串最长公共子序列代码实例
2020/03/05 Python
Jmeter HTTPS接口测试证书导入过程图解
2020/07/22 Python
Python如何进行时间处理
2020/08/06 Python
PyCharm2019.3永久激活破解详细图文教程,亲测可用(不定期更新)
2020/10/29 Python
解决pycharm修改代码后第一次运行不生效的问题
2021/02/06 Python
css sprite简单实例
2016/05/23 HTML / CSS
Right-on官方网站:日本知名的休闲服装品牌
2019/07/12 全球购物
EJB与JAVA BEAN的区别
2016/08/29 面试题
初中生期末考试的自我评价
2013/12/17 职场文书
高中军训感言600字
2014/03/11 职场文书
青年教师个人总结
2015/02/11 职场文书
2015年毕业实习工作总结
2015/05/29 职场文书
Grafana可视化监控系统结合SpringBoot使用
2022/04/19 Redis