背景

目前该前端项目是VUE编写的单页应用,如果开始推广,目前的架构对SEO的支持很不友好,为更好的支持推广,预研采用服务端渲染(SSR)十分的必要,并且静态化后页面的渲染速度也会有所提高。

经综合比对,选择采用开源成熟的NuxtJs框架进行服务端渲染。

NuxtJs简介

Nuxt.js 是一个基于Vue.js 开发SSR应用的一站式解决方案。同时,Nuxt.js 的热加载机制可以使开发者非常便捷的进行网站的开发。

优点:

  1. 基于 Vue.js

  2. 自动代码分层

  3. 服务端渲染

  4. 强大的路由功能,支持异步数据

  5. 静态文件服务

  6. ES6/ES7 语法支持

  7. 打包和压缩 JS 和 CSS

  8. HTML头部标签管理

  9. 本地开发支持热加载

  10. 集成ESLint

  11. 支持各种样式预处理器: SASS、LESS、 Stylus等

文件迁移说明

nuxtJs有一套自己的文件体系及约定,需要我们把原项目的文件迁移到新框架固定目录下。nuxtJs取消了src和public这些一级目录,相应的要把原项目src和public这些一级目录下面的二级文件目录变更成一级目录

  1. 原项目public/components文件及内容,转移到新框架下/components(nuxtJs约定目录,用于存放组件);
  2. 原项目public/pages文件及内容,转移到新框架下/pages(nuxtJs约定目录,页面目录 pages 用于组织应用的路由及视图。Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置);
  3. 原项目public/router可以移除,nuxtJs会根据pages内部文件自动生成路由;
  4. 原项目public/store文件及内容,转移到新框架下/store(nuxtJs约定目录,用于组织应用的Vuex状态树文件);
  5. 原项目public/static文件及内容,转移到新框架下/static(nuxtJs约定目录,用于存放应用的静态文件,不会被webpack编译);
  6. 原项目涉及需要编译的静态文件及内容,如全局global.less等等,转移到新框架下/assets(nuxtJs约定目录,用于组织未编译的静态资源如 LESSSASS 或 JavaScript);
  7. 原项目public/api文件及内容,转移到新框架下/api;
  8. 原项目下plugins文件及内容可移除,里面只有elementUI的注册,在新框架初始化时,已安装及注册了elementUI。
  9. 原项目public/theme、public/utils、public/config文件及内容,转移到新框架根目录下即可。

文件修改说明

1. 使用框架自带@nuxt/axios及api方法改造

使用nuxtJs自带axios

nuxtJs自带axios,所以无需安装axios,只需要在全局配置里面引入后,即可使用。

引入自带的axios

modules: [

    // Introduction - Axios Module

    '@nuxtjs/axios',

],

api文件内方法的修改,主要为适配NuxtJS自带axios模块

之前我们都是在api/...js中对某个方法进行维护,并导出。然后在页面中引入和使用。

例子

// api/user.js

export async function getUser() {

  return request.get(`/api/get/user`)

}

// 页面.vue

import { getUser } from '@/api/user'

create:{

    getUser().then(res=>...)

}

在新的书写方式中,采用插件化的写法

​
// api/user.jsexport default ({ $axios }, inject) => {inject('apiUser', {getUser() {return $axios.get(`/api/get/user`).catch(() => Promise.resolve({}))}...})}// nuxt.config.js 注册export default {plugins: ['~api/user', // 插件注册],}// 页面.vueasync asyncData({app}){app.$apiPlugins.getUser().then(res=>{...})},create:{this.$apiUser.getUser().then(res=>{...})}​

通过上述修改,使用NuxtJS自带的axios模块进行网络请求,对相应的api文件和页面使用方式进行改造,为接下来第二步的拦截器修改提供了基础。

2. 拦截器修改(request.js改造)

原项目的全局配置及拦截操作放在public/utils/request.js中,正常情况下,在新框架也可以这样使用。

但是经排查发现request.js涉及到了操作store,目前的写法及用法在NuxtJs中是无法找到和使用store的,因此要对文件进行插件化改造(NuxtJs自有的插件化体系)

拦截器插件化改造

插件可以注入两个参数context和inject

context:全局上下文,包含当前执行环境下的变量和方法(app,store,route,params,query,env,isDev,isHMR,redirect,error,$config,$axios

inject:方法,它是 plugin 导出函数的第二个参数。将内容注入 Vue 实例的方式与在 Vue 应用程序中进行注入类似。系统会自动将$添加到方法名的前面

context中暴露的$axios是第一步中全局注册的@nuxtjs/axios模块。

// plugins/interceptor.js   使用这种方式才能拿到store,传统的封装request,无法拿到store// axios全局拦截
// 变量名从app解构
export default function ({ store, route, redirect, $axios }) {// options// $axios.defaults.baseURL = '/'if (process.server) {// 获取服务端的tokenconsole.log('server')}if (process.client) {// 获取客户端tokenconsole.log('client')}// 通过context中的store可以获取到当前状态树const token = store.state.user.erpUserInfo && store.state.user.erpUserInfo.jti// 请求监听$axios.onRequest(config => {console.log('Making request to ' + config.url)})// 响应监听$axios.onResponse(response => {console.log('response')})// 异常监听$axios.onError(error => {console.log(error)const code = parseInt(error.response && error.response.status)if (code === 400) {redirect('/400')}})
}

文件创建完成后需要添加到全局插件中

// 在全局引入当前插件
// nuxt.config.js
export default {plugins: ['~plugins/interceptor', // 插件注册],
}

通过上述配置,实现了添加拦截器对请求或响应数据进行处理,同时支持在拦截器中可以操作状态树

3. 路由守卫及鉴权

目前的项目还没有涉及到鉴权,此内容为提前规划部分

NuxtJS提供一个中间件目录middleware,可以在里面注册中间件并在全局或页面中使用。

// 新建 middleware/auth.js
export default function (context) {const { app, store } = context// 在下面的内容中做鉴权、拦截跳转等处理if (!store.state.authUser) {return redirect('/login')}
}

中间件创建后可以根据情况在全局使用或页面使用

// nuxt.config.js
export default{router: {middleware: ['auth'],},
}
// 页面使用// pages/user.vue
<script>
export default {// 引用权限中间件middleware: 'auth',...
}
</script>

通过上述操作,可以在局部和全局使用中间件对权限进行鉴权

4. 项目配置

安装

npx create-nuxt-app <项目名>

Koa

Element UI

Jest

Universal

axios

EsLint

Prettier

5. 使用Vuex持久化插件(vuex-persistedstate)

vuex可以进行全局的状态管理,但刷新后数据会消失。传统的方案是手动在localStorage或sessionStorage或其它存储方式中获取和设置值,并同步到vuex的state中,缺点是手动写比较麻烦。

vuex-persistedstate原理其实也是结合了存储方式,只是统一的配置就不需要手动每次都写存储方法

安装: npm i --save vuex-persistedstate

在nuxt.config.js 配置文件中注册插件,注意修改ssr为false。否则会在服务端运行,且运行时找不到window变量并报错。

// nuxt.config.js 配置文件
plugins: [{ src: '~/plugins/vuex-persistedstate', ssr: false },
],// 新建文件 plugins/vue-persistedstate.js
import createPersistedState from "vuex-persistedstate";
export default (context) => {createPersistedState({reducer(obj) {// 其中 username authority 为需要自动存储的 stateconst { username, authority } = obj;return { username, authority };},})(context.store);
};
按照以上方法进行配置后,就不用再对store的持久化存储进行额外的维护。日常开发只用关注store的读写,减少了日常开发的工作量。

6. 使用缓存优化页面二次访问速度,减少服务器压力

虽然 Vue 的 SSR 非常快,但由于创建组件实例和 Virtual DOM 节点的成本,它无法与纯粹基于字符串的模板的性能相匹配。在 SSR 性能至关重要的情况下,合理地利用缓存策略可以大大缩短响应时间并减少服务器负载。

使用缓存的目的:1. 减轻api服务器请求压力;2.减轻Nuxt的服务器渲染压力;

对于某些页面常用的,且不经常变化的api请求,可以采用lru-cache进行缓存

通过对一定时间内页面的首次api的请求进行缓存处理,设置缓存的有效期(最大缓存时长和最小缓存时长)。再次访问页面时,如果缓存没过期的话,可以直接拿存储的数据,较少api请求和服务器压力。由于中间少了请求api及等待返回的流程,也能减少整个页面的渲染时间,优化访问速度。

实施方案:

import LRU from 'lru-cache'
const CACHED = new LRU({max: 100, // 缓存队列长度maxAge: 1000 * 60 // 缓存时间
})
export default {async asyncData(){// 判断有缓存直接返回缓存if (CACHED.has('indexData')) {let indexData = CACHED.get('indexData')indexData = JSON.parse(indexData)return indexData}.....const res = {...}// 存储缓存            CACHED.set('indexData', JSON.stringify(res))return res            }
}

通过对应用市场的测试,未添加api缓存之前,页面每次访问及刷新时:dom渲染完成时间(1.97s),页面加载完成时间/Load(2.41s)

添加api缓存方案后,首次访问页面时间几乎不变

添加api缓存方案后,二次访问页面:刷新后页面的加载时间为:dom渲染完成时间(1.49s),页面加载完成时间/Load(1.83s)

本地测试结果:采用api缓存方案后,页面二次访问dom加载时间缩短0.59s,页面加载时间缩短0.67s,提升了大约26%左右的页面响应速度。

ps:对于部分网页进行服务端的缓存,可以获得更好的渲染性能,但是缓存又涉及到一个数据的及时性的问题,所以在及时性和性能之间要有平衡和取舍。

asyncData函数优化,避免里面接口报错影响页面渲染

1. 方案一:直接在asyncData里面对请求进行catch,并返回空对象

// $axios.get('/api/data1').catch(() => Promise.resolve({}))
async asyncData({ $axios }) {// 1、增加catch处理,是为了让服务端,客户端运行时不报错,特别是防止服务端运行时不报错,不然页面就挂了// 2、catch函数返回一个resolve空字面量对象的Promise,表明dataPromise1的状态未来始终是resolved状态const dataPromise1 = $axios.get('/api/data1').catch(() => Promise.resolve({}))const dataPromise2 = $axios.get('/api/data2').catch(() => Promise.resolve({}))const dataPromise3 = $axios.get('/api/data3').catch(() => Promise.resolve({}))const dataPromise4 = $axios.get('/api/data4').catch(() => Promise.resolve({}))const dataPromise5 = $axios.get('/api/data5').catch(() => Promise.resolve({}))const dataPromise6 = $axios.get('/api/data6').catch(() => Promise.resolve({}))const dataPromise7 = $axios.get('/api/data7').catch(() => Promise.resolve({}))const dataPromise8 = $axios.get('/api/data8').catch(() => Promise.resolve({}))// 保证apiData有数据const apiData = await new Promise(resolve => {Promise.all([dataPromise1, dataPromise2, dataPromise3, dataPromise4,dataPromise5, dataPromise6, dataPromise7, dataPromise8,]).then(dataGather => {resolve({data1: dataGather[0],data2: dataGather[1],data3: dataGather[2],data4: dataGather[3],data5: dataGather[4],data6: dataGather[5],data7: dataGather[6],data8: dataGather[7],})})})return apiData}
}

2. 方案二:根据目前代码实际情况,把catch放在api请求里

// api/plugins.js
//原代码
getProductCategory(params) {return $axios.get(`${api}/v1/product-categories/all/all`, { params })
},
// 改造后
getProductCategory(params) {return $axios.get(`${api}/v1/product-categories/all/all`, { params }).catch(() => Promise.resolve({}))
},

这样的话页面就不需要做额外操作了,保证页面代码的简洁

通过上述操作,避免了asyncData生命周期内请求api报错时,导致前端页面无法访问的问题。

添加错误日志打印 nuxt-winston-log

npm i nuxt-winston-log

nuxt-winson-log的默认保存路径是当前文件夹下的logs文件夹。修改配置让日志保存在其他路径:

// nuxt.config.js
export default {modules:['xxxx其他modules',['nuxt-winston-log',{logPath:process.env.npm_lifecycle_event === 'build' ||process.env.NODE_ENV === 'development'? './logs': `/data/weblog/nodejs/${process.env.npm_package_name}`,logName: `${process.env.npm_package_name}.log`}]]
}

区分了开发和生产的日志存放目录。 同时使用npm_lifecycle_eventNODE_ENV而不是process.env.NODE_ENV === 'production'去做判断是因为构建过程中的process.env.NODE_ENV也是production,会因为构建机器上没有这个日志存放目录导致构建失败。

对日志做分级

// nuxt.config.js
import path from 'path';
import { format, transports } from 'winston';
const { combine, timestamp } = format;// 日志存放路径
const infoLogPath = path.resolve(process.cwd(), './logs', `info.log`);
const errorLogPath = path.resolve(process.cwd(), './logs', `error.log`);export default {modules: ['nuxt-winston-log'],winstonLog: {loggerOptions: {transports: [new transports.File({// format: combine(timestamp()),format: format.combine(format.timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),format.errors({ stack: true }),format.splat(),format.json()),level: 'info',filename: infoLogPath,maxsize: 5 * 1024 * 1024  // 这个是限制日志文件的大小}),new transports.File({// format: combine(timestamp()),format: format.combine(format.timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),format.errors({ stack: true }),format.splat(),format.json()),level: 'error',filename: errorLogPath,maxsize: 5 * 1024 * 1024})]}},
}
// plugins/interceptor.js
export default function ({ store, route, redirect, $axios, $winstonLog }) {$axios.onResponse(response => {// 打印日志 先判断是否有$winstonLog,因为该变量只在服务端存在if ($winstonLog) {$winstonLog.info(`[${response.status}] ${response.request.path}`)}}   $axios.onError(error => {// 打印日志 先判断是否有$winstonLog,因为该变量只在服务端存在              if ($winstonLog) {$winstonLog.error(`[${error.status}] | ${error.request.path} | ${error.message}`)$winstonLog.error(error.response && error.response.data)}}
}

这样配置就可以实现infoerror日志分级了!如果发现启动的时候两个日志文件没有生成,可以检查一下设置的保存路径是否存在。

通过日志打印可以方便的了解到页面的访问及错误情况,通过日志分析,可以更方便的开发中进行针对性的优化和错误定位。

ps: 日志目前仅在本地调试使用,如果开发环境已有相应的配置,则此项可以忽略,避免重复的日志输出。

前端错误页面配置

1. 定制化错误页面

通过编辑 layouts/error.vue 文件来定制化错误页面,这个布局文件不需要包含 <nuxt/> 标签。你可以把这个布局文件当成是显示应用错误(404,500 等)的组件。

<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>

2. 拦截不匹配的路由跳转至404

// nuxt.config.js
router: {extendRoutes(routes, resolve) {routes.push({name: 'custom',path: '*',redirect: '/404'})}},

以上定制化是用于显示应用错误(404,500 等)的组件,客户端遇到错误问题,会默认显示此页面。

添加百度统计

添加百度统计后可以随时监控网站的很多信息,比如流量来源,停留网页时间等很多重要信息。通过这些统计数据,可以了解自己网站的运营情况。

// plugins/hm.jsexport default ({ app: { router }, store }) => {/* 每次路由变更时进行pv统计 */router.afterEach((to, from) => {/* 告诉增加一个PV */try {window._hmt = window._hmt || [];window._hmt.push(["_trackPageview", to.fullPath]);} catch (e) {}});};// nuxt.config.jshead: {script: [{src: 'https://hm.baidu.com/hm.js?****'},/*引入百度统计的js*/]},plugins: ['~plugins/hm.js',/*百度统计*/],

ps: 生产环境使用需要先去百度统计官网注册申请。

NuxtJS服务端渲染相关推荐

  1. nuxtjs 服务端渲染从开发到发布的流程

    1.创建项目 yarn create nuxt-app <项目名> 2.开发模式启动 yarn dev 3.部署服务器 3.1.服务端build yarn build 3.2.启动服务端渲 ...

  2. 【服务端渲染】NuxtJs 综合案例

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 Nuxt.js 综合案例 基本介绍 案例名称:RealWorld 一个开源的学习项目,目的就是帮助开发者快速学习新技能 ...

  3. 【服务端渲染】NuxtJS基础

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 Nuxt.js 的基本介绍 官网:https://zh.nuxtjs.org/ GitHub 仓库:https://g ...

  4. vue服务端渲染之nuxtjs

    前言 本篇主要针对nuxtjs中的一些重要概念整理和代码实现! 在学习vue服务端渲染之前,先搞清楚几个概念: 什么是客户端渲染(CSR) 什么是服务端渲染(SSR) CSR和SSR有什么异同 客户端 ...

  5. nuxtjs+express+vue2+vuex搭建的服务端渲染(SSR)个人网站项目 1

    5se7en.com nuxtjs+express+vue2.0+vuex搭建的服务端渲染个人网站项目. github项目地址: https://github.com/se7en-1992... 项目 ...

  6. 【服务端渲染】之 Vue SSR

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:内容较多,建议通过左侧导航栏进行阅读 Vue SSR 基本介绍 Vue SSR 是什么 官方文档:https://ssr.vuejs.org/ V ...

  7. Vue 服务端渲染(SSR)、Nuxt.js - 从入门到实践

    前言 10月初有幸接到公司官网改版需求,要求采用服务端渲染模式对原网站进行seo优化. 由于团队一直使用的vue技术栈,所以我第一时间想到的就是采用vue 服务端渲染(SSR)来实现该需求,即能减少团 ...

  8. SSR服务端渲染(nuxt重构项目)

    SSR服务端渲染(nuxt重构项目) 目的:优化SEO,提高网站权重 ,页面静态化,强化搜索引擎,提高首屏渲染速度 参考文档:https://zh.nuxtjs.org/guide/installat ...

  9. Nuxt SSR 服务端渲染 详解

    Nuxt SSR 服务端渲染 详解 1.Nuxt项目构建 2.Nuxt的生命周期 2.1 nuxtServerInit 钩子 2.2 middleware 中间件 2.3 validate 数据校验 ...

最新文章

  1. 第四范式联合浪潮商用机器发布AI一体机,接入AI像使用手机一样简单
  2. 学python推荐书籍-零基础学python推荐几本python学习的书籍
  3. DOS命令行中用MAVEN构建Java和Java Web项目
  4. JDK8中ConcurrentHashMap源码解析
  5. 【学习笔记】springboot中的全局异常处理 和@ControllerAdvice的使用
  6. 树的高度 递归法和非递归法
  7. WPF学习笔记(二):初学者避坑实录
  8. html5内联框去滚动条,如何优雅的实现内联滚动条(前端底部固定方法 )
  9. 水印生成器第2版[原图质量水印可自定义设置]
  10. java获取网络带宽_Linux Java 获取CPU使用率,内存使用率,磁盘IO,网络带宽使用率等等...
  11. idea普通java项目引入lombok_IDEA中Lombok的使用
  12. jdbctemplate 开启事务_浅入浅出 Spring 事务传播实现原理
  13. 紫米创始人张峰兼任小米笔记本总经理
  14. 苹果发布会邀请函被玩坏:神似桂林西瓜霜
  15. ionic xcode 上传appstroe 创建Distribution证书报错 you already have a current iOS Distribution certificate
  16. TensorFlow GPU配置
  17. 2019-4-21 - plan
  18. Ubuntu安装jdk10
  19. catia装配体怎么把零件旋转180度_工件180度翻转装置的设计
  20. linux命令提示符详解

热门文章

  1. Errors running builder 'Integrated External Tool Builder' on project 'rVoix'
  2. spring boot 项目 前台向后台传递时间参数,插入到数据库中时间会少一天
  3. SRS解决RTMP转RTC的爆音问题
  4. 记录丨阿里云校招生的成长经历
  5. 【每日最爱一句】2013.06.17
  6. el-image src 属性使用本地图片加载失败解决方案 Cannot find module ‘../../xxx‘
  7. 图片img或者含有img元素拖拽时,或者scale缩放时产品的阴影效应问题
  8. 高校战“疫”网络安全分享赛
  9. 控制Python浮点数输出位数
  10. 第一次使用Xshell在服务器上跑程序