近期公司成立了一个新我的项目,须要做SEO。做过前后端半拆散(局部数据是后端模板渲染,局部用AJAX获取)我的项目的同学都晓得跟后端人员把代码写在同一个页面有多好受,我的项目不易保护且开发体验很差,因而采纳了Nuxt.js
SEO
大家都晓得,网页在搜索引擎中能被搜寻进去是因为他们有本人的爬虫,能够爬取网页中的内容并保留下来,然而它们只会保留网页中的第一个申请返回的内容,即HTML文档。
在传统的前后端不拆散网站中,在浏览器上查看源代码,能够看到网页上有什么内容,源代码中就有与之对应的代码。随着前后端拆散开发越来越风行,也衍生了越来越多的单页面利用(SPA),在浏览器中关上一个SPA的源代码,往往只能看到寥寥无几的几个标签,其余内容全都是由JS渲染的,也就是客户端渲染。因而单页面利用的一大痛点就是搜索引擎无奈收录,有痛点就会有相应的计划产生,呈现了SSR。
SSR
SSR意思是服务端渲染,乍一看会认为是回到以前的前后不拆散开发模式,实际上相比以前,客户端与服务端之间多了一个中间层,用于接收数据并渲染页面返回到客户端。
有了中间层帮客户端渲染页面,在搜索引擎爬到页面时,就解决了前后端拆散页面不收录的的痛点。不仅如此,因为中间层也是一个服务端,前端工程师能够做更多事儿了,比方简略解决数据、做代理等等。
Nuxt.js
Vue官网提供了vue-server-renderer
模块实现SSR,而Nuxt.js是对其封装后的框架,用于提供一个开箱即用的NodeJS SSR服务。
生命周期
通过在地址栏输出链接,或者从其余网站点击链接跳转到Nuxt站点的页面时(首屏拜访),出现的第一个页面的数据会由中间层向服务端发动申请,而后渲染Vue组件,再将渲染实现后生成的HTML文档返回到客户端。随后点击页面的<NuxtLink>
组件或者通过vue-router的push
,replace
等办法跳转页面都是在客户端实现的,除非刷新浏览器,否则不会再次拜访中间层服务器。因而在页面中申请服务端的数据有可能是中间层发动的,也有可能是客户端发动的。
拜访Nuxt站点的页面时,会经验以下几个生命周期,咱们能够在不同的节点对站点的行为做解决
- 依照
nuxt.config.js
中的plugins配置程序执行插件。 - nuxtServerInit: 如果Nuxt利用中应用了Vuex,会运行actions中的
nuxtServerInit
函数,用于在进入页面之前将数据填充到Vuex中。 - Middleware: 按程序执行
nuxt.config.js
中配置的router中间件、layout中间件、page中间件,能够用来对用户做权限验证。 - validate(): 返回
true
、false
或者Promise
,返回false
时,将会跳转到谬误页面,次要用于验证动静路由的参数。 - asyncData(): 返回一个对象,执行结束之后Nuxt会将返回的对象合并到Vue实例的data属性中,用于在拜访到页面之前调用接口获取数据,这个钩子取代了SPA中罕用的
created
钩子。 - beforeCreate: Vue组件生命周期钩子。
- created: Vue组件生命周期钩子。
- fetch(): 与
asyncData
一样,也是用于调用接口获取数据,不同的是它不会将返回值合并到data,个别用于对本地数据处理,比方Vuex,值得一提的是,这个钩子在Vue实例创立之后执行,因而能够用this
拜访Vue实例。 - 执行beforeMount、mounted等其余Vue的生命周期钩子。
以上生命周期中,只有前两个是齐全在中间层执行的,其余的有可能在中间层执行,也有可能在客户端执行,这次要取决于页面是首屏渲染还是站内跳转,因而搞懂哪些代码什么时候在什么环境运行十分重要。
插件
vant-ui
我的项目中应用了vant ui作为组件库,Nuxt我的项目中没有相似main.js的入口文件,如果须要装置依赖,须要在plugins中申明。
每次拜访Nuxt站点的首屏时,都会将nuxt.config.js中申明的plugins执行一遍,从而让开发这能够把本人所需的插件注册到利用中
// nuxt.config.jsmodule.exports = { ..., plugins: [ '@/plugins/vant-ui' ], ...}
在根目录的plugins目录下须要创立一个对应的文件
// /plugins/vant-ui.jsimport Vue from 'vue'import { Button, Search, Toast} from 'vant'Vue.use(Button)Vue.use(Search)Vue.use(Toast)
为了按需引入,还须要装置babel-plugin-import和进行相干配置
// nuxt.config.jsmodule.exports = { build: { analyze: true, transpile: [/vant.*?less/], babel: { // 按需引入配置 plugins: [ [ 'import', { libraryName: 'vant', style: name => `${name}/style/less.js` }, 'vant' ] ] }, loaders: { less: { lessOptions: { modifyVars: { // 此处能够批改vant款式中的less变量,从而自定义ui款式 '@button-primary-background-color': '#000' } } } } }}
axios
Nuxt官网提供了专门封装的@nuxtjs/axios模块,看过文档后发现与平时的用法略有不同,闲麻烦就还是用了原汁原味的axios,毕竟此前也做了进一步的封装(官网强烈推荐的模块尽量能用就用,像我这种不听话的就会默默踩坑,通过摸索才悟出了上面这段话)。然而与SPA不同的是,axios发动的申请可能是在中间层服务端,也可能是在客户端,这取决于是否是首屏渲染(在下面生命周期有提到),因而须要在代码中利用process.client
和process.server
对相应的环境做出判断和解决。
import axios from 'axios'import { Toast } from 'vant'const { CancelToken } = axios// 用于获取在nuxt.config.js配置中的baseURLconst nuxtConfig = require('../nuxt.config')// 让申请函数带有axios cancelTokenexport const cancelToken = fn => { const newFn = (...arg) => { // 每个cancelToken只能应用一次,之后会放弃状态 // 因而每次发动申请都创立一个新的cancelToken const source = CancelToken.source() newFn.token = source.token newFn.cancel = source.cancel return fn(...arg) } return newFn}// 创立实例function createAxios () { const instance = axios.create({ baseURL: '/api', // 中间层代理地址 timeout: 10000 }) // 申请拦挡 instance.interceptors.request.use(config => { // 服务端不须要代理 if (process.server) { config.baseURL = nuxtConfig.env.baseUrl } return config }, err => { return Promise.reject(err) }) // 响应拦挡 instance.interceptors.response.use(res => { const { data } = res if (data.code !== 200) { if (process.server) { // 在plugins中,我把context挂载到了axios实例上 // 在服务端发动申请出错时,能够跳转到谬误页面 instance.nuxtContext.error({ statusCode: 500, message: '' }) } else { Toast(data.msg) } return Promise.reject(res) } return data.data }, err => { const { response } = err // 服务端和客户端有不一样的错误处理形式 if (process.client) { if (response) { switch (response.status) { case 401: // 未登录 Toast('请先登录') break case 403: // 操作被回绝(没有相应权限) Toast('您的操作被回绝') break case 404: Toast('未找到资源') break case 500: Toast('零碎出错了') break } return Promise.reject(response) } // 勾销申请 if (axios.isCancel(err)) { console.log('申请勾销') return Promise.reject(err) } // 申请超时 if (err.message?.includes('timeout')) { Toast('网络超时,请重试') return Promise.reject(err) } if (!window.navigator.onLine) { // 断网 Toast('请查看网络') return Promise.reject(err) } else { Toast('未知谬误') return Promise.reject(err) } } else { console.log(response) instance.nuxtContext.error({ statusCode: response.status, message: '' }) } }) return instance}export default createAxios()
增加一个插件将nuxt context挂载到axios实例上,用于出错时跳转到谬误页面,此外须要记得在nuxt.config.js
中申明此插件。
// /plugins/axios.jsimport axios from '@/utils/request'// 将context对象挂载到axios实例上export default context => { axios.nuxtContext = context}
Vuex长久化
在SPA中,通常会将token存储在localStorage中,然而拜访SSR站点首屏时,须要在中间层做登录等权限验证,无奈获取到客户端存在localStorage中的数据。然而能够将其存在cookie中,利用cookie的个性,在拜访站点时将其带到中间层,于是中间层就能够获取cookie中的数据做权限验证,并且将其填充进Vuex供客户端应用。
首先装置vuex-persistedstate
和js-cookie
,把Vuex中的数据同步到cookie,而后写一个插件将vuex-persistedstate
注入到Vuex中(别忘了在nuxt.config.js
中申明)。
// /plugins/vuex-persistedstate.jsimport createPersistedState from 'vuex-persistedstate'import Cookies from 'js-cookie'export default ({ store }) => { // “将Vuex中的数据同步到cookie中”这种事只有客户端会做 if (!process.client) { return } createPersistedState({ // 通过配置批改vuex-persistedstate的读写行为 // 将操作指标改为cookie storage: { getItem: key => { return Cookies.get(key) }, setItem: (key, value) => { Cookies.set(key, value, { expires: 365 }) }, removeItem: key => { Cookies.remove(key) } } })(store)}
而后在nuxtServerInit()
这个生命周期钩子中读取拜访时的cookie。
// /store/index.jsimport { SET_USER_INFO } from './mutation-types'export const state = () => ({ token: ''})export const mutations = { [SET_TOKEN] (state, token) { state.token = token }}export const actions = { nuxtServerInit ({ commit }, { req }) { // 通过Nuxt context获取cookie有很多办法,这里我用了cookie-parser const { vuex } = req.cookies if (!vuex) { return } commit(SET_TOKEN, JSON.parse(vuex).token) }}
这样一来,就能够在后续的Middleware、validate() 等钩子通过context.store
获取到所需的数据了。
此外,为了方便管理,我在文件中引入了/store/mutation-types.js
,默认状况下Nuxt会将/store
目录下的.js
文件当作一个Vuex的模块,因而须要配置一下Nuxt的疏忽文件,让它疏忽掉mutation-types.js
。
# /.nuxtignore# ignore store mutation typesstore/mutation-types.js
应用SCSS
为了更难受地编写CSS,当然须要一个CSS预处理器!为了能让通用的.scss
(变量、mixin等)可能用在任何组件中应用,须要借助@nuxtjs/style-resources
模块实现,第一步装置,第二步配置。
// nuxt.config.jsmodule.exports = { ..., buildModules: [ '@nuxtjs/style-resources' ], // 配置全局的function,mixin,variable styleResources: { scss: [ // 留神程序!被前面文件依赖的文件须要放在后面 '@/assets/scss/_variables.scss', '@/assets/scss/_functions.scss', '@/assets/scss/_mixins.scss' ] // sass: [], // less: [], // stylus: [] } ...}
代理
因为浏览器的平安机制,会拦挡跨域的AJAX申请,为了解决跨域问题,能够设置代理,而这次的代理能够不是Nginx
了,能够间接用中间层作为代理,为客户端申请数据。装置完@nuxtjs/proxy
之后进行配置即可。
// nuxt.config.jsmodule.exports = { ..., modules: [ '@nuxtjs/proxy' ], proxy: { '/api': { target: 'https://xxx.com', pathRewrite: { '^/api': '/' } } } ...}
自定义入口文件
入口文件的次要作用是启动服务和初始化Nuxt外围类,Nuxt曾经将入口文件封装好并暗藏了,看不到也改不了,不过咱们能够本人把这两件事件做了,让入口文件在本人掌控之下,变得更具备扩展性。
// 根目录创立server.jsconst { loadNuxt, build } = require('nuxt')const Express = require('express') // 应用express启动服务,这样就能够对中间层做更多的解决,比方应用更多中间件等等const cookieParser = require('cookie-parser') // 后面Vuex长久化中应用的cookie-parser就是这儿来的const app = Express()// 自定义入口文件之后,nuxt.config.js的server选项会生效,须要手动调用const config = require('./nuxt.config')const isDev = process.env.NODE_ENV !== 'production'const { host, port } = config.serverasync function start() { // We get Nuxt instance const nuxt = await loadNuxt(isDev ? 'dev' : 'start') app.use(cookieParser()) // Render every route with Nuxt.js app.use(nuxt.render) // Build only in dev mode with hot-reloading if (isDev) { build(nuxt) } // Listen the server app.listen(port, host) console.log('Server listening on `localhost:' + port + '`.')}start()
而后在package.json
中批改一下启动命令。
{ "scripts": { ..., "start": "node server.js", ... }}
结语
因为我的项目还处在初期阶段,能不能持续做上来还不晓得,所以我的项目规模十分无限,还有许多没有波及到的点,如果您有补充或者发现有误的中央,请帮我纠正,谢谢观看。