nuxt基于vuejs的seo动静态优化

我之前的一篇文章,《Vue-cli3基于webpack + prerender-spa-plugin + vue-meta-info的seo优化》,是对静态页面的seo处理,但是也存在很多弊端。比如上线之后浏览器右键查看源代码,会看不到dom结构,比如动态加载head中的meta和title源码也会显示undefined。原因是因为浏览器处理的时候,dom还没有加载完毕。相信下面的nuxt看完之后你会放弃上面这篇文章

本文涉及的主要有,nuxt框架的初始化搭建,局域网访问配置,全局axios配置(包括asyncData(),接口请求),swiper插件使用,引入字体包,动态路由--参数配置---generate打包,cross-env开发和线上环境配置,nuxt并不是最终的优化方案

本文内容较长,没有必要全部看完,可以只看需要的,还有下面的配置信息,第二次做的时候,配置起来有少许的出入,so懂理论就好了

背景

2016 年 10 月 25 日,zeit.co 背后的团队对外发布了 Next.js,一个 React 的服务端渲染应用框架。几小时后,与 Next.js 异曲同工,一个基于 Vue.js 的服务端渲染应用框架应运而生,我们称之为:Nuxt.js

定义

Nuxt.js 是一个基于 Vue.js 的通用应用框架。为基于 Vue.js 的应用提供生成对应的静态站点的功能。通过asyncData钩子函数可以生成利于seo优化的动静态站点

搭建

Nuxt.js团队创建了脚手架工具 create-nuxt-app

确保安装了npx(npx在NPM版本5.2.0默认安装了):
安装之前请先检查一下npm的版本

$ npx create-nuxt-app <项目名>

或者用yarn :

$ yarn create nuxt-app <项目名>

npx create-nuxt-app nuxt1
npx: 341 安装成功,用时 115.532 秒
create-nuxt-app v2.10.1
✨ Generating Nuxt.js project in nuxt1
1 Project name nuxt1
2 Project description My doozie Nuxt.js project
3 Author name smook
4 Choose the package manager Npm
5 Choose UI framework None
6 Choose custom server framework None (Recommended)
7 Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to invert selection)
8 Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
9 Choose test framework None
10 Choose rendering mode Universal (SSR)
11 Choose development tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
� Successfully created project nuxt1

To get started:

    cd nuxt1
    npm run dev

To build & start for production:

    cd nuxt1
    npm run build
    npm run start

10步骤这里我的经验是选SSR,因为我本身就是奔着seo优化来的,所以SSR服务端渲染这块应该是这个

-上面是一整个安装流程-nuxt官网都有,我多此一举多安装一遍而已

安装完之后--项目目录如下:


image.png

├── assets // 资源文件。用于组织未编译的静态资源入LESS、SASS 或 JavaScript

├── components // 组件。用于自己编写的Vue组件,比如滚动组件,日历组件,分页组件
│ └── logo.vue // 默认logo组件

├── layouts // 布局。页面都需要有一个布局,默认为 default。它规定了一个页面如何布局页面。所有页面都会加载在布局页面中的 <nuxt /> 标签中。
│ └── default.vue // 默认模板页面,类似mvc中的layout

├── middleware // 中间件。存放中间件。可以在页面中调用: middleware: 'middlewareName'

├── pages   // 页面。一个 vue 文件即为一个页面。也会根据pages里面的文件结构自动生成路由
│ └── index.vue // 默认首页面

├── plugins // 用于存放JavaScript插件的地方,或者一些必要的配置,全局配置

├── static // 用于存放静态资源文件,比如图片,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。 服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下。

├── store   // 用于组织应用的Vuex 状态管理。

├── .editorconfig // 开发工具格式配置

├── .eslintrc.js // ESLint的配置文件,用于检查代码格式

├── .gitignore // 配置git不上传的文件

├── nuxt.config.js // 用于组织Nuxt.js应用的个性化配置,比如网站title,已便覆盖默认配置

├── package.json // npm包管理配置文件

└── README.md // 说明文档

上面的结构是整个新建项目的目录图,和我网上找的和自己总结的目录中每个的作用

现在我们运行下面的代码可以进行项目初体验

cd nuxt1
npm run dev

然后好像默认的是localhost:3000,在浏览器打开就可以了

修改局域网配置

使用localhost:3000访问之后,有疑问的同学会发现,你发给在同一个局域网的同事,他们是打不开你的项目的,跟vue-cli创建的项目不一样!其实这里可以通过修改一下config来修改,具体配置如下:

在package.json中新添加

这个就是你本地的地址,同一局域网是访问不到
"config": {
    "nuxt": {
      "host": "127.0.0.1",
      "port": "8080"
    }
  }

这个是0.0.0.0配置之后,重新运行,同一局域网可以访问
"config": {
    "nuxt": {
      "host": "0.0.0.0",
      "port": "8080"
    }
  }

具体的效果,可以本地测试一下

全局axios配置

个人见解

根据前面安装的步骤,已经安装了axios,或者可以去package.json中去检查一下

npm install axios --save

安装好之后在/plugins目录下新建baseUrl.js

// 这里是一个默认的url,可以没有
let baseUrl = ''

// window对象要在.vue文件的mounted生命周期之后才可以获取,mounted之前就不行
// let hostnames = location.hostname   
// process
switch (process.env.NODE_ENV) {
  case 'development':
    // 开发环境url
    baseUrl = 'https://开发域名'
    break
  case 'production':
    // 生产环境url
    baseUrl = 'https://生产域名'
    break
}

export default baseUrl

然后再在/plugins目录下新建axios.js

import * as axios from 'axios'
import qs from 'qs'
import baseUrl from './baseUrl'

axios.defaults.baseURL = baseUrl
axios.defaults.timeout = 20000
// 默认是否允许携带cookie
axios.defaults.withCredentials = true
axios.defaults.headers = {
  'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
}

axios.interceptors.request.use(
  config => {
    // 若是有做鉴权token , 就给头部带上token
    // var token = localStorage.getItem('token')
    // if (token !== null) {
    //   if (token !== '') {
    //     config.headers.token = token
    //   }
    // }
    // store.dispatch('showLoading', '加载中')
    return config
  },
  error => {
    // store.dispatch('hideLoading')
    return Promise.reject(error)
  }
)

axios.interceptors.response.use(
  response => {
    // let getVisitor = localStorage.getItem('getVisitor')
    // if (getVisitor === '游客') {
    //   if (response.data.code === 400) {
    //     return Promise.reject({
    //       message: response.data.msg
    //     })
    //   }
    // } else if (getVisitor === null) {
    //   if (response.data.code === 400) {
    //     router.push({
    //       name: 'auth'
    //     })
    //     return Promise.reject({
    //       message: response.data.msg
    //     })
    //   }
    // }
    return response
  },
  error => {
    if (!error.response) {
      return Promise.reject ({
        message: '请求无响应'
      })
    }
    let message
    switch (error.response.status) {
      case 400:
        message = '错误请求'
        break
      case 401:
        message = '未授权'
        break
      case 403:
        message = '拒绝访问'
        break
      case 404:
        message = '请求路径未找到'
        break
      case 500:
        message = '服务器异常'
        break
      default:
        message = '未知错误'
    }
    return Promise.reject(error)
  }
)

const http = {
  get (url, payload = undefined) {
    return axios({
      method: 'get',
      url: url,
      params: payload,
      paramsSerializer: params => {
        return qs.stringify(params, { indices: false })
      }
    })
  },
  post (url, payload = undefined) {
    return axios({
      method: 'post',
      url: url,
      data: payload,
      transformRequest: [
        data => {
          return qs.stringify(data, { indices: false })
        }
      ]
    })
  },
  put (url, payload = undefined) {
    return axios({
      method: 'post',
      url: url,
      data: payload
    })
  },
  delete (url, payload = undefined) {
    return axios({
      method: 'delete',
      url: url,
      data: payload
    })
  },
  postJson (url, payload = undefined) {
    return axios({
      method: 'post',
      url: url,
      data: payload,
      headers: {
        'Content-Type': 'application/json;charset=UTF-8'
      }
    })
  },
  postFile (url, payload = undefined) {
    return axios({
      method: 'post',
      url: url,
      data: payload,
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
  },
  all (promises) {
    return Promise.all(promises)
  },
  getBase () {
    return baseUrl
  }
}

export default http

这里还需要安装qs

npm install qs --save

然后再在/plugins目录下新建main.js


// 设置全局变量
import Vue from 'vue' // vue 文件引入 - 方便在vue方法内容直接 this 调取
import http from './axios'

// 全局http
let mainHttp = {
    install(Vue){
        Vue.prototype.$http = http  // 变量的内容 后期可以在vue中 this->$api.xxx 使用
    }
}

Vue.use(mainHttp)
 
// 这里是 为了在 asyncData 方法中使用
export default ({ app }, inject) => {
    // Set the function directly on the context.app object
    app.$http = http
}

现在配置完了,看一下/plugins目录的文件结构


image.png

然后在nuxt.config.js中plugins注册一下

plugins: [
    {
      src: '~/plugins/main',
      ssr: true
    }
  ],

然后我们来看.vue文件中,接口的使用和调用
asyncData

<template>
  <div
     class="recent_lists"
     @mouseenter="moveB($event)"
     @mouseleave="moveO($event)"
     v-for="(item, index) of testList"
     :key="index">
      <div class="lists_info">
        <li>{{item.exam_name}}</li>
        <a :href="item.enroll_url" v-if="item.enroll_url">{{item.exam_enroll}}</a>
        <li v-else>{{item.exam_enroll}}</li>
        <a :href="item.result_url" v-if="item.result_url">{{item.exam_result}}</a>
        <li v-else>{{item.exam_result}}</li>
        <a :href="item.face_url" v-if="item.face_url">{{item.exam_face}}</a>
        <li v-else>{{item.exam_face}}</li>
        <li v-show="item.course_ids" @click="getTestInfo(item.id, item.course_ids)">课程推荐</li>
      </div>
      <div
         class="lists_map"
         :class="item.key">
          <li>{{item.city_names}}</li>
          <li>{{item.cate_name}}</li>
       </div>
      <div class="recent_line"></div>
  </div>
</template>

export default{
  async asyncData (params) {
  //  这里通过params.app 就可以找到我们注册的全局$http
 //  因为asyncData方法里面不能读取和使用this关键字,所以我们只能使用
 //  params这个全局属性来获取全局变量
    // console.log(params.app.$http)
    let { data } = await params.app.$http.post('/course/getExamLists', ({
      is_top: 1,
      page: 1,
      page_count: 6
    }))
    console.log(data.data.length)
    return { testList: data.data }
  },
}

//我测试的是接口这里的传参的参数,我没办法从data()中获取,
//所以我是写死的,然后就是我这里的接口和下面正常接口是一起加载的,
//当然这个asyncData是在dom渲染之前就已经加载了,
//然后这里的testList也不用在data()中定义,因为它运行的时候,data()压根就还没开始运行
//如果不写这个asyncData函数的话,项目在浏览器运行的时候,右键查看源代码,压根源代码里就没有这个接口渲染的dom节点,所以这个方法是必然
//而下面的正常接口方法,是实现功能的正常接口,asyncData在dom渲染之前就执行了,
//所以后续的逻辑操作根绝压根用不到它,我个人理解,他的作用就是为了渲染源码dom
//特指接口渲染的dom

正常的接口调用

methods: {
    Course () {
      // this.$qs.stringify
      //  这里接口调用只用this.$http就可以随意使用post或者get等等
      this.$http.post('/course/getExamLists', {
        is_top: 1,
        page: this.page,
        page_count: this.page_count
      }).then((res) => {
        this.testList = res.data.data
        this.allNum = res.data.all_num
        if (this.allNum > 0) {
          this.allNum = Math.ceil(Number(this.allNum) / Number(this.page_count))
        }
        this.testList.map((r, index) => {
          for (const key in this.list[0]) {
            if (Number(key) === Number(r.city_id)) {
              this.testList[index].city_names = this.list[0][key]
              this.testList[index].key = 'sx' + key
            }
            if (Number(key) === Number(r.province_id) && Number(r.city_id) === 0) {
              this.testList[index].city_names = this.list[0][key]
              this.testList[index].key = 'sx' + key
            }
          }
        })
      })
    },
}

在这里要提一句,上面这种方式,前提是在你设置了同一局域网可以访问的那个配置之后才可以的,不然会报错的,错误好像是什么127.0.0.1:80,所以上面文章不仔细看的,到这里运行报错我只能说,啊,好爽,走我的老路

还有就是子组件,在子组件中是不可以使用asyncData函数的,因为子组件搭建的时候是放在components目录下面的,而不是pages目录下,所以子组件不可以使用,但是我们可以在父组件中使用asyncData函数,然后通过props函数接收。但是这种有一个问题就是通过props渲染子组件,浏览器查看源代码的时候是没有渲染的这一块的dom结构的,所以不利于seo优化,除非是这个组件的内容是不准备让爬虫爬取的

这是官方的解释---仅限于页面组件


image.png

swiper组件安装

因为nuxt里面window对象必须在mounted生命周期之后使用,所以这里使用的轮播插件是vue-awesome-swiper

安装

npm install vue-awesome-swiper --save

安装完之后在/plugins目录下新建swiper.js--文件名字随意

import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper/dist/ssr'

Vue.use(VueAwesomeSwiper)

然后再在nuxt.config.js中注册一下

module.exports = {
  // some nuxt config...
  plugins: [
    { src: '~/plugins/swiper.js', ssr: false },
  ],
  // some nuxt config...
  css: [
    'swiper/dist/css/swiper.css'
  ],
  // some nuxt config...
}
这里的ssr要设置成false,如果为true的话可能会报window is undefined类似的错误

然后就是在.vue的文件中写轮播了

<template>
  <!-- You can find this swiper instance object in current component by the "mySwiper"  -->
  <div v-swiper:mySwiper="swiperOption">
     <div class="swiper-wrapper">
       <div class="swiper-slide" v-for="(item, index) in banner" :key="index">
         <a :href="item.link" target="_blank">
           <img :src="item.image_url" :alt="item.description">
         </a>
       </div>
     </div>
     <div class="swiper-pagination" slot="pagination"></div>
   </div>
</template>

<script>
  export default {
    data () {
      return {
        swiperOption: {
          loop: true,
          autoplay: {
            delay: 3000
          },
          pagination: {
            el: '.swiper-pagination',
            clickable: true,
            // 自定义分页器, bulletClass 是常规的分页名字, bulletActiveClass是active时候的名字
            bulletClass: 'my-bullet',
            bulletActiveClass: 'my-bullet-active'
          }
        },
        banners: []
      }
    },
    created () {
      this.Infor()
    }
    mounted() {
      Infor () {
        this.$http.post('/index/wwwIndex').then((result) => {
          this.banner = result.data.data.pc_www_top_banner.img_lists
        })
      }
    }
  }
</script>

//这里的样式可能不太对,所以不要太在意,我这里写是为了说明一个原因
//是style中的scoped要去掉,如果要自定义按钮的话
//我这里的.my-bullet和.my-bullet-active是自定义的按钮,如果scoped不去掉的话,是没办法在轮播中显示的!唯一的遗憾
<style lang="scss">
.swiper-pagination{
  height: 20px;
  display: flex;
  flex-direction: row;
  align-items: center;
  // justify-content: center;
  z-index: 9;
  box-sizing: border-box;
  padding-left: 400px;
}
.swiper-pagination .my-bullet{
  // border-radius: 50%;
  width: 30px;
  height: 4px;
  margin: 4px;
  background: rgba(0,0,0,0.35);
  display: block;
}
.swiper-pagination .my-bullet-active{
  display: block;
  background: rgba(0,0,0,0.75);
  width: 30px;
  height: 4px;
  // border-radius: 2px;
}
.my-swiper {
   height: 300px;
   width: 100%;
   .swiper-slide {
     text-align: center;
     font-size: 38px;
     font-weight: 700;
     background-color: #eee;
     display: flex;
     justify-content: center;
     align-items: center;
  }
  .swiper-pagination {
     > .swiper-pagination-bullet {
       background-color: red;
     }
   }
}
</style>

问题1:

有时候点击分页器,轮播就不能继续了,根本原因是是少了一个属性

autoplay: {
  delay: 5000,
//加上这句就好了
  disableOnInteraction: false
},

asyncData一次请求多个接口怎么办,asyncData多并发请求如下:

async asyncData (ctx) {
    let [data, data1, data2, data3] = await Promise.all([
      ctx.app.$http.post('/index/wwwIndex'),
      ctx.app.$http.post('/course/getExamLists', {
        is_top: 1,
        page: 1,
        page_count: 6
      }),
      ctx.app.$http.post('/news/newsLists', {
        news_type: 1,
        is_top: 1
      })
    ])
    // console.log(data2.data)
    // testList: data.data,
    return {
      banner: data.data.data.pc_www_top_banner.img_lists,
      testList: data1.data.data,
      newList: data2.data.data,
      teacher_lists: data.data.data.teacher_lists,
      knowList: data.data.data.link_article_list
    }

swiper插件也可以单纯的使用swiper组件,这个不需要上面swiper的Vue.user(swiper)注入,
只需要在nuxt.config.js中把css注入就好了,然后在.vue文件中直接import swiper from 'swiper'
就可以正常使用swiper了,我写这篇文章的页面有很多轮播,就不多做介绍了,自己摸索,都差不多

引入字体包

在/assets目录下新建 font文件夹把字体文件放进去,同事新建一个css/scss文件font
font.scss

@font-face {
  font-family: 'DIN Alternate Bold';
  src: url('DINAlternateBold.ttf');
  font-weight: normal;
  font-style: normal;
}

然后在nuxt.config.js中注入路径

css: [
    '@/assets/font/font.scss'
]

这样在.vue文件中,就可以使用了

.foot_right .top1 li p{
  font-size: 20px;
  font-family:'DIN Alternate Bold';
  margin-bottom: 4px;
}
image.png

这里有个地方需要注意一下,在vue-cli脚手架下面,font文件夹下面的字体文件名字是可以识别空格的,例如'DIN Alternate Bold.ttf。但是在nuxt中要重命名,把空格去掉,不然会一直报错

路由配置传参(动态路由)--以及generate打包

这个路由配置比较麻烦一点,由于不熟悉花了将近2天的时间来搞这个

前面介绍了/pages目录的设计会自动生成路由,去.nuxt目录下的route.js就可以看到,今天要说的就是参数的传递(动态路由)和generate打包生成静态页面

以我项目为例
http://192.168.0.150:8080/exam/2340/0这是本地的链接,/exam/2340/0其中的2340和0是传入的参数id和cid,/exam/是要跳转的路由

配置如下

就是在/pages目录下新建exam文件夹,然后在exam中新建_id文件夹,然后在_id文件夹中新建_cid.vue文件,保存之后可以去/.nuxt目录下打开route.js,里面的路由已经自动生成,文件路径和路由如下图

image.png

image.png

然后在_cid.vue中随便写几个字,打开/exam/2340/0就可以看到了,文件配置完成

这里要注意下,this.$route.name获取的路由名称不再是exam,而是exam-id-cid

路由页面获取参数

这里做的功能是城市和类型选择,遍历拼接的地址

在_cid.vue中,路由的跳转,在nuxt中是使用<nuxt-link></nuxt-link>,用法跟<router-link></router-link>一样
本地的项目是如下图


image.png

image.png

上面的路由跳转是:to=""拼接的路由路径,参数就是接口的id,cid。id是地区的标志,cid是类型的标志

咱们做的项目优化nuxt的是以seo优化为前提的,所以要使用asyncData()来渲染dom和head(),但是我们前面知道asyncData()是不能获取data中的参数的,

但是如果是路由传参,这里可以是可以接受参数的,如下图

image.png

用上图的这种写法,传入参数{app, params},app这里是前面ctx下面的app结构。params就是要接受的参数
使用console.log(params)打印可以看到浏览器控制台打印出{id: 2340, cid: 0},这样前面遍历的地区就可以获取参数了,这样的话,我不管是点击地区或者类型,参数都会传过来,这样浏览器查看源代码就可以看到渲染了dom。

开发中遇到一个问题,就是刷新浏览器的时候报错,request to....500,记不太清楚了!这个错误的原因是asyncData()方法在项目运行的时候,请求到的接口有问题,跟前端无关,跟前端无关,跟前端无关,说三遍。不要傻乎乎的在前端配置文件或者代码中找错误,是接口的问题!把asyncData()方法注释,在methods钩子函数中走一遍方法,F12,network中查看接口请求,就可以看到接口报错了!这时候通知后台修改接口!

generate打包

动态传参完成之后,我们要验证我们之前写的asyncData()渲染的dom是否发布到线上也能正常渲染

//运行
npm run generate

预期的打包之后的结构是exam->下面有很多id子文件夹,id子文件夹里面有cid文件夹,cid文件夹里面是index.html,结构如下:


image.png

我们看到项目中多了一个dist文件夹,。这就是generate之后的打包的文件,但是我们在项目中真正看到的结果并不是这个样子的,并没有exam文件夹,或者/exam里面一个exam.vue

不能显示的真正原因是因为没有配置generate属性

在nuxt.config.js中设置generate属性配置

我们这里先写死这几个
export default {
  generate: {
    routes: [
      '/exam/2340/0',
      '/exam/2341/0',
      '/exam/2352/0'
    ]
  }
}

上面设置了这三条之后,再运行npm run generate . 生成的dist文件夹中的路径,就跟我前面的那张图结构一样了,这样你把dist文件夹内容放到线上,这三个路由切换的时候,右键查看源代码就可以看到渲染的dom了,下图是线上查看源代码之后的dom结构

image.png

然后如果有成百上千路由的话,可以和后台配合合作,大致就是后台通过接口给你返回所有的可配置的路由,你配置到generate里面,然后就可以了,这块我项目还没做到,过两天再更新

下面来说一下动态路由打包的利弊

上面说过,nuxt.config.js文件中配置了generate属性,打包上线的时候,会根据这个配置生成静态文件,从而实现seo的dom和head渲染,这种方法是与后台接口配合,大致就是,接口提供给你要做的所有的需要seo优化的页面的路由,通过generate配置,打包的时候请求接口,返回路由路径,生成静态文件,如下图


image.png

然后这个接口返回的路由数据,如下图


image.png

这样npm run generate打包之后,会生成dist文件夹,dist文件夹格式如下图


image.png

image.png

这样发布到线上,打开页面右键---查看源代码,就可以看到dom结构和head了。

开发(本地)和生产(线上)的环境配置

上面看到我的generate配置里面使用了process.env.BASE_URL,这个其实是配置了开发和生产环境的部署,需要安装一个依赖cross-env

npm install cross-env --save

安装完成之后先去package.json中配置一下scripts属性

"scripts": {
    "dev": "cross-env BASE_URL=https://测试.cn NODE_ENV=development nuxt",
    "test": "cross-env BASE_URL=https://测试.cn NODE_ENV=production nuxt generate",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "cross-env BASE_URL=https://生产.cn NODE_ENV=production nuxt generate"
  },

最前面配置的全局axios中/plugins/axios.js要去掉一些语句和修改下

注释掉baseUrl的加载和默认baseURL配置
import * as axios from 'axios'
import qs from 'qs'
// import baseUrl from './baseUrl'

// axios.defaults.baseURL = baseUrl
axios.defaults.timeout = 20000
// 默认是否允许携带cookie
axios.defaults.withCredentials = true
axios.defaults.headers = {
  'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
}

然后axios.js后面的请求方法中添加上process.env.BASE_URL

get (url, payload = undefined) {
    return axios({
      method: 'get',
      url: process.env.BASE_URL + url,
      params: payload,
      paramsSerializer: params => {
        return qs.stringify(params, { indices: false })
      }
    })
  },
  post (url, payload = undefined) {
    return axios({
      method: 'post',
      url: process.env.BASE_URL + url,
      data: payload,
      transformRequest: [
        data => {
          return qs.stringify(data, { indices: false })
        }
      ]
    })
  },

然后再在nuxt.config.js中添加一下env配置

export default {
  mode: 'universal',
  env: {
    BASE_URL: process.env.BASE_URL,
    NODE_ENV: process.env.NODE_ENV
  }
}

这样就好了,.vue文件中的请求接口方式不变,这样项目运行或者打包的话,就可以把package.json中的配置的cross-env进行全局适配,要知道nuxt中配置文件中window和location.name是访问不到的,所以这种办法是很方便的!

我上面配置的不全,感兴趣的可以去搜搜别的攻略
使用npm run dev这是运行的本地(开发)环境,接口域名对应的是测试接口
使用npm run test这是打包测试的,然后发布到测试环境,同样的接口也是使用的测试接口---注意后面的nuxt generate
使用npm run generate这是打包正式的,然后发布到测试,测试没问题之后,再发布到正式,接口域名是正式(线上)接口

并不是最终的优化方案

但是上面这个也有一个弊端,并不能做到真正的动态化!你想,如果我的后台操作系统,添加了一篇文章,那么在前台显示的时候,这篇文章是一个新的路由/路径,那么这个新增的文章,后台添加完成之后,前台是看不到seo的效果的,要再重新打包一次,发布线上才可以,还有就是如果文件,和路径少了还好说,如果有上千,上万,几百万的时候,生成的静态文件我觉得服务器会爆炸,所以我的建议如果要使用nuxt,就要考虑你是否是要真正的实现动态化seo优化。看了b站的,还有掘金的,目前还没搞懂他们的技术点。所以建议是服务端渲染,通过后台或者node渲染生成html,渲染到前台!

后续配置和操作优化seo持续更新

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容