Nuxt是解决SEO的比较常用的解决方案,随着Nuxt也有很多坑,每当突破一个小技术点的时候,都有很大的成就感,在这段时间里着实让我痛并快乐着。在这里根据个人学习情况,所踩过的坑做了一个汇总和总结。

Nuxt开发跨域

项目可以使用Nginx来反向代理,将外来的请求(这里也注意下将Linux的防火墙放行相应端口)转发的内部Nuxt默认的3000端口上,最简单的配置文件如下:

nuxtjs.config.js

  1. {
  2. modules: [
  3. '@nuxtjs/axios',
  4. '@nuxtjs/proxy'
  5. ],
  6. proxy: [
  7. [
  8. '/api',
  9. {
  10. target: 'http://localhost:3001', // api主机
  11. pathRewrite: { '^/api' : '/' }
  12. }
  13. ]
  14. ]
  15. }

@nuxtjs/proxy需要手动单独安装。

Nuxt Store 使用

Nuxt中使用Vuex跟传统在Vue中使用Vuex还不太一样,首先Nuxt已经集成了Vuex,不需要我们进行二次安装,直接引用就好,在默认Nuxt的框架模板下有一个Store的文件夹,就是我们用来存放Vuex的地方。

Nuxt官方也提供了相关文档,可以简单的过一下,但是官方文档我看来比较潦草。

根据官方文档在store文件下面创建两个.js文件,分别是index.jstodo.js。并在pages文件夹下面创建index.vue

store - index.js

  1. export const state = () => ({
  2. counter: 0
  3. })
  4. export const mutations = {
  5. increment (state) {
  6. state.counter++
  7. }
  8. }

store - todo.js

  1. export const state = () => ({
  2. list: []
  3. })
  4. export const mutations = {
  5. add (state, text) {
  6. state.list.push({
  7. text: text,
  8. done: false
  9. })
  10. },
  11. remove (state, { todo }) {
  12. state.list.splice(state.list.indexOf(todo), 1)
  13. },
  14. toggle (state, todo) {
  15. todo.done = !todo.done
  16. }
  17. }

pages - index.vue

  1. <template>
  2. <section class="container">
  3. <div>
  4. <h2 @click="$store.commit('increment')">{{counter}}</h2>
  5. <ul>
  6. <li v-for="(item,index) of list"
  7. :key="index">{{item.text}}</li>
  8. </ul>
  9. </div>
  10. </section>
  11. </template>
  12. <script>
  13. import Logo from '~/components/Logo.vue'
  14. import {mapState} from "vuex";
  15. export default {
  16. components: {
  17. Logo
  18. },
  19. computed:{
  20. ...mapState(["counter"]),
  21. ...mapState("todos",{
  22. list:state => state.list
  23. })
  24. },
  25. created(){
  26. for(let i =0;i<10;i++){
  27. this.$store.commit("todos/add",i);
  28. }
  29. console.log(this.list)
  30. }
  31. }
  32. </script>

Nuxt中可以直接使用this.$store,并且是默认启用命名空间的。再看一下computed中的代码,在使用mapState的时候,counter属性是直接获取出来的,然而todos属性则是通过命名空间才获取到的。这又是怎么回事?

Nuxtstore中的index.js文件中所有的state、mutations、actions、getters都作为其公共属性挂载到了,store实例上,然而其他的文件则是使用的是命名空间,其对应的命名空间的名字就是其文件名。

运行项目的时候可以在.nuxt文件夹内找到store.js看下是怎么完成的。简单的解释一下代码作用,以及做什么用的。

.nuxt - store.js

  1. // 引入vue
  2. import Vue from 'vue'
  3. // 引入vuex
  4. import Vuex from 'vuex'
  5. // 作为中间件
  6. Vue.use(Vuex)
  7. // 保存console 函数
  8. const log = console
  9. // vuex的属性
  10. const VUEX_PROPERTIES = ['state', 'getters', 'actions', 'mutations']
  11. // store属性容器
  12. let store = {}
  13. // 没有返回值的自执行函数
  14. void (function updateModules() {
  15. // 初始化根数据,也就是上面所说的index文件做为共有数据
  16. store = normalizeRoot(require('@/store/index.js'), 'store/index.js')
  17. // 如果store是函数,提示异常,停止执行
  18. if (typeof store === 'function') {
  19. // 警告:经典模式的商店是不赞成的,并将删除在Nuxt 3。
  20. return log.warn('Classic mode for store is deprecated and will be removed in Nuxt 3.')
  21. }
  22. // 执行存储模块
  23. // store - 模块化
  24. store.modules = store.modules || {}
  25. // 解决存储模块方法
  26. // 引入todos.js 文件,即数据
  27. // 'todos.js' 文件名
  28. resolveStoreModules(require('@/store/todos.js'), 'todos.js')
  29. // 如果环境支持热重载
  30. if (process.client && module.hot) {
  31. // 无论何时更新Vuex模块
  32. module.hot.accept([
  33. '@/store/index.js',
  34. '@/store/todos.js',
  35. ], () => {
  36. // 更新的根。模块的最新定义。
  37. updateModules()
  38. // 在store中触发热更新。
  39. window.$nuxt.$store.hotUpdate(store)
  40. })
  41. }
  42. })()
  43. // 创建store实例
  44. // - 如果 store 是 function 则使用 store
  45. // - 否则创建一个新的实例
  46. export const createStore = store instanceof Function ? store : () => {
  47. // 返回实例
  48. return new Vuex.Store(Object.assign({
  49. strict: (process.env.NODE_ENV !== 'production')
  50. }, store))
  51. }
  52. // 解决存储模块方法
  53. // moduleData - 导出数据
  54. // filename - 文件名
  55. function resolveStoreModules(moduleData, filename) {
  56. // 获取导出数据,为了解决es6 (export default)导出
  57. moduleData = moduleData.default || moduleData
  58. // 远程store src +扩展(./foo/index.js -> foo/index)
  59. const namespace = filename.replace(/\.(js|mjs|ts)$/, '')
  60. // 空间名称
  61. const namespaces = namespace.split('/')
  62. // 模块名称(state,getters等)
  63. let moduleName = namespaces[namespaces.length - 1]
  64. // 文件路径
  65. const filePath = `store/${filename}`
  66. // 如果 moduleName === 'state'
  67. // - 执行 normalizeState - 正常状态
  68. // - 执行 normalizeModule - 标准化模块
  69. moduleData = moduleName === 'state'
  70. ? normalizeState(moduleData, filePath)
  71. : normalizeModule(moduleData, filePath)
  72. // 如果是 (state,getters等)执行
  73. if (VUEX_PROPERTIES.includes(moduleName)) {
  74. // module名称
  75. const property = moduleName
  76. // 存储模块 // 获取存储模块
  77. const storeModule = getStoreModule(store, namespaces, { isProperty: true })
  78. // 合并属性
  79. mergeProperty(storeModule, moduleData, property)
  80. // 取消后续代码执行
  81. return
  82. }
  83. // 特殊处理index.js
  84. // 模块名称等于index
  85. const isIndexModule = (moduleName === 'index')
  86. // 如果等于
  87. if (isIndexModule) {
  88. // 名称空间弹出最后一个
  89. namespaces.pop()
  90. // 获取模块名称
  91. moduleName = namespaces[namespaces.length - 1]
  92. }
  93. // 获取存储模块
  94. const storeModule = getStoreModule(store, namespaces)
  95. // 遍历 VUEX_PROPERTIES
  96. for (const property of VUEX_PROPERTIES) {
  97. // 合并属性
  98. // storeModule - 存储模块
  99. // moduleData[property] - 存储模块中的某个属性数据
  100. // property - 模块名称
  101. mergeProperty(storeModule, moduleData[property], property)
  102. }
  103. // 如果moduleData.namespaced === false
  104. if (moduleData.namespaced === false) {
  105. // 删除命名空间
  106. delete storeModule.namespaced
  107. }
  108. }
  109. // 初始化根数据
  110. // moduleData - 导出数据
  111. // filePath - 文件路径
  112. function normalizeRoot(moduleData, filePath) {
  113. // 获取导出数据,为了解决es6 (export default)导出
  114. moduleData = moduleData.default || moduleData
  115. // 如果导入的数据中存在commit方法,则抛出异常
  116. // - 应该导出一个返回Vuex实例的方法。
  117. if (moduleData.commit) {
  118. throw new Error(`[nuxt] ${filePath} should export a method that returns a Vuex instance.`)
  119. }
  120. // 如果 moduleData 不是函数,则使用空队形进行合并处理
  121. if (typeof moduleData !== 'function') {
  122. // 避免键入错误:设置在覆盖顶级键时只有getter的属性
  123. moduleData = Object.assign({}, moduleData)
  124. }
  125. // 对模块化进行处理后返回
  126. return normalizeModule(moduleData, filePath)
  127. }
  128. // 正常状态
  129. // - 模块数据
  130. // - 文件路径
  131. function normalizeState(moduleData, filePath) {
  132. // 如果 moduleData 不是function
  133. if (typeof moduleData !== 'function') {
  134. // 警告提示
  135. // ${filePath}应该导出一个返回对象的方法
  136. log.warn(`${filePath} should export a method that returns an object`)
  137. // 合并 state
  138. const state = Object.assign({}, moduleData)
  139. // 以函数形式导出state
  140. return () => state
  141. }
  142. // 对模块化进行处理
  143. return normalizeModule(moduleData, filePath)
  144. }
  145. // 对模块化进行处理
  146. // moduleData - 导出数据
  147. // filePath - 文件路径
  148. function normalizeModule(moduleData, filePath) {
  149. // 如果module数据的state存在并且不是function警告提示
  150. if (moduleData.state && typeof moduleData.state !== 'function') {
  151. // “state”应该是返回${filePath}中的对象的方法
  152. log.warn(`'state' should be a method that returns an object in ${filePath}`)
  153. // 合并state
  154. const state = Object.assign({}, moduleData.state)
  155. // 覆盖原有state使用函数返回
  156. moduleData = Object.assign({}, moduleData, { state: () => state })
  157. }
  158. // 返回初始化数据
  159. return moduleData
  160. }
  161. // 获取store的Model
  162. // - storeModule store数据模型
  163. // - namespaces 命名空间名称数组
  164. // - 是否使用命名空间 默认值 为false
  165. function getStoreModule(storeModule, namespaces, { isProperty = false } = {}) {
  166. // 如果 namespaces 不存在,启动命名空间,命名空间名称长度1
  167. if (!namespaces.length || (isProperty && namespaces.length === 1)) {
  168. // 返回model
  169. return storeModule
  170. }
  171. // 获取命名空间名称
  172. const namespace = namespaces.shift()
  173. // 保存命名空间中的数据
  174. storeModule.modules[namespace] = storeModule.modules[namespace] || {}
  175. // 启用命名空间
  176. storeModule.modules[namespace].namespaced = true
  177. // 添加命名数据
  178. storeModule.modules[namespace].modules = storeModule.modules[namespace].modules || {}
  179. // 递归
  180. return getStoreModule(storeModule.modules[namespace], namespaces, { isProperty })
  181. }
  182. // 合并属性
  183. // storeModule - 存储模块
  184. // moduleData - 存储模属性数据
  185. // property - 模块名称
  186. function mergeProperty(storeModule, moduleData, property) {
  187. // 如果 moduleData 不存在推出程序
  188. if (!moduleData) return
  189. // 如果 模块名称 是 state
  190. if (property === 'state') {
  191. // 把state数据分到模块空间内
  192. storeModule.state = moduleData || storeModule.state
  193. } else {
  194. // 其他模块
  195. // 合并到对应的模块空间内
  196. storeModule[property] = Object.assign({}, storeModule[property], moduleData)
  197. }
  198. }

以上就是编译后的store文件,大致的意思就是对store文件进行遍历处理,根据不同的文件使用不同的解决方案,使用命名空间挂载model

页面loading

Nuxt有提供加载Loading组件,一下是配置。

nuxtjs.config.js

  1. module.exports = {
  2. loading: { color: '#3B8070' }
  3. }

Nuxt提供的loading不能满足项目需求,可能有的项目不需要这样加载动画,so~,就需要自己手动配置一个。添加一个loading组件 (官方示例如下,详情可看官方文档)引用该组件。

nuxtjs.config.js

  1. module.exports = {
  2. loading: '~components/loading.vue'
  3. }

一个小插曲在Nuxt中,~与@都指向的是根目录。

components/loading.vue

  1. <template lang="html">
  2. <div class="loading-page" v-if="loading">
  3. <p>Loading...</p>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. data: () => ({
  9. loading: false
  10. }),
  11. methods: {
  12. start () {
  13. this.loading = true
  14. },
  15. finish () {
  16. this.loading = false
  17. }
  18. }
  19. }
  20. </script>

第三方组件库

项目开发过程中,难免会用到组件库,与在Vue中使用的时候是太一样的,需要添加一些依赖才能正常使用。

plugins - element-ui.js

  1. import Vue from 'vue';
  2. import Element from 'element-ui';
  3. import locale from 'element-ui/lib/locale/lang/en';
  4. export default () => {
  5. Vue.use(Element, { locale })
  6. };

nuxtjs.config.js

  1. module.exports = {
  2. css: [
  3. 'element-ui/lib/theme-chalk/index.css'
  4. ],
  5. plugins: [
  6. '@/plugins/element-ui',
  7. '@/plugins/router'
  8. ]
  9. };

使用中间件

中间件Nuxt没有给出具体的使用文档,而是放入了一个编辑器。这一点我感觉到了一丝丝的 差异。为什么要这样。。。简单的研究了一下,弄明白了大概。

middleware中创建想要的中间件。这里借用一下官网的例子。

middleware - visits.js

  1. export default function ({ store, route, redirect }) {
  2. store.commit('ADD_VISIT', route.path)
  3. }

向上面这样就创建好了一个中间件,但是应该怎么使用呢?在使用的时候有两种方式,一种是全局使用,另一种是在页面中单独使用,文件名会作为其中间件的名称。

++全局使用++

nuxtjs.config.js

  1. export default {
  2. router: {
  3. middleware: ['visits']
  4. }
  5. }

页面中单独使用

  1. export default {
  2. middleware: 'auth'
  3. }

官网中在页面中的asyncData中有一段这样的代码。

  1. export default {
  2. asyncData({ store, route, userAgent }) {
  3. return {
  4. userAgent
  5. }
  6. }
  7. }

持续更新。。。

总结

Nuxt的学习曲线非常小,就像Vue框架一样,已经是一个开箱即用的状态,我们可以直接跨过配置直接开发。对配置有兴趣的可以在Vue官方文档找到SSR渲染文档。

服务端预渲染之Nuxt(爬坑篇)的更多相关文章

  1. 服务端预渲染之Nuxt (使用篇)

    服务端预渲染之Nuxt - 使用 现在大多数开发都是基于Vue或者React开发的,能够达到快速开发的效果,也有一些不足的地方,Nuxt能够在服务端做出渲染,然后让搜索引擎在爬取数据的时候能够读到当前 ...

  2. 服务端预渲染之Nuxt(介绍篇)

    现在前端开发一般都是前后端分离,mvvm和mvc的开发框架,如Angular.React和Vue等,虽然写框架能够使我们快速的完成开发,但是由于前后台分离,给项目SEO带来很大的不便,搜索引擎在检索的 ...

  3. vue-cil 服务端预渲染 prerender-spa-plugin

    众所周知单页面应用不利于SEO,为了解决这个问题网上所给出的2个解决方案1.SSH服务器端渲染2.预渲染由于页面较少,且预渲染相对于SSH比较简单,于是选择预渲染页面,预渲染可以极大的提高网页访问速度 ...

  4. 服务端集成支付宝踩过的坑RSA、RSA2

    坑 在配置蚂蚁开发平台的时候,切记一定要注意选择的加密方式是RSA,还是RSA2.因为这两种方式生成的支付宝公匙是不一样的.RSA2对应的是2048位支付宝公匙.在配置类Config中,要根据加密方式 ...

  5. 对爱奇艺PC Web主站来说,良好的SEO能够帮助其获得更多的搜索流量,因而页面上一些非常重要的内容仍然需要依靠服务端进行渲染,由于另外开发一套基于Node的SSR后台成本较高,而乐趣(基于java和velocity模板引擎)平台作为渲染系统已经十分成熟且运行稳定,在充分试验后,我们决定在Uniqy中使用服务端同步与客户端浏览器异步二次渲染相结合的方式,结合Vue2.0提供的 slot插槽机制,很

    https://mp.weixin.qq.com/s/eB20BoqzENO_oNk8eDg4Eg 干货|爱奇艺PC Web新框架实践 原创: 前端研发团队 爱奇艺技术产品团队 昨天      

  6. Angular开发实践(六):服务端渲染

    Angular Universal Angular在服务端渲染方面提供一套前后端同构解决方案,它就是 Angular Universal(统一平台),一项在服务端运行 Angular 应用的技术. 标 ...

  7. 使用 PHP 来做 Vue.js 的 SSR 服务端渲染

    对于客户端应用来说,服务端渲染是一个热门话题.然而不幸的是,这并不是一件容易的事,尤其是对于不用 Node.js 环境开发的人来说. 我发布了两个库让 PHP 从服务端渲染成为可能.spatie/se ...

  8. 前端性能优化成神之路--SSR(服务端渲染)

    Nuxt.js的介绍 Nuxt.js概述 nuxt.js简单的说是Vue.js的通用框架,最常用的就是用来作SSR(服务器端渲染).Vue.js是开发SPA(单页应用)的,Nuxt.js这个框架,用V ...

  9. ASP.NET Core 与 Vue.js 服务端渲染

    http://mgyongyosi.com/2016/Vuejs-server-side-rendering-with-aspnet-core/ 原作者:Mihály Gyöngyösi 译者:oop ...

随机推荐

  1. 拿到月薪30K,必选一些Python好书!

    论述: Python是所有编程语言中与人工智能最紧密相连的编程语言,阿尔法狗都在使用的 Python 语言. 教育部早在两个月前(自2018年3月起)就以及公布:大学生全国计算机二级考试中必考Pyth ...

  2. HTTP 前端需明白的相关知识点

    简介: http(Hyper Text Transfer Protocol)超文本传输协议是万维网应用层的协议,使用了面向连接的TCP作为运输层协议. 特征: 简单快速:通过url就可以访问资源,协议 ...

  3. TensorFlow 常用函数汇总

    本文介绍了tensorflow的常用函数,源自网上整理. TensorFlow 将图形定义转换成分布式执行的操作, 以充分利用可用的计算资源(如 CPU 或 GPU.一般你不需要显式指定使用 CPU ...

  4. Securing Spring Cloud Microservices With OAuth2

    From Zero to OAuth2 in Spring cloud Today I am presenting hours of research about a (apparently) sim ...

  5. PHP 7.3 我们将迎来灵活的 heredoc 和 nowdoc 句法结构

    php.net RFC 频道已经公布了 PHP 7.3 的 Heredoc 和 Nowdoc 语法更新,此次更新专注于代码可读性: Heredoc 和 Nowdoc 有非常严格的语法,有些时候这令很多 ...

  6. 巩固java(五)----通过实例理解java多态

    package duotai; class A{ public String show(){ return "A"; } } class B extends A{ public S ...

  7. switch case 支持的 6 种数据类型!

    有粉丝建议可以偶尔推送一些 Java 方面的基础知识,一方面可以帮助一初学者,也可以兼顾中高级的开发者. 那么今天就讲一下 Java 中的 switch case 语句吧,有忘记的同学正好可以温习一下 ...

  8. Windows 下python 环境安装

    1.先在官网上下载安装包,官网地址:  https://www.python.org   2. 选择自己需要的版本进行安装,最好选择新版本下载,   3. 下载完成后,双击运行安装,一直next,直至 ...

  9. Stackoverflow 最受关注的 10 个 Java 问题

    Stack Overflow 是一个大型的编程知识库.在 Stack Overflow 中已经有数以百万计的问题,并且很多答案有着很高的质量.这就是为什么 Stack Overflow 的答案经常位于 ...

  10. python struct.pack() 二进制文件,文件中打包二进制数据的存储与解析

    学习Python的过程中,遇到一个问题,在<Python学习手册>(也就是<learning python>)中,元组.文件及其他章节里,关于处理二进制文件里,有这么一段代码的 ...