nuxt基于vuejs的seo動靜態(tài)優(yōu)化

我之前的一篇文章,《Vue-cli3基于webpack + prerender-spa-plugin + vue-meta-info的seo優(yōu)化》,是對靜態(tài)頁面的seo處理,但是也存在很多弊端。比如上線之后瀏覽器右鍵查看源代碼,會看不到dom結(jié)構(gòu),比如動態(tài)加載head中的meta和title源碼也會顯示undefined。原因是因?yàn)闉g覽器處理的時候,dom還沒有加載完畢。相信下面的nuxt看完之后你會放棄上面這篇文章

本文涉及的主要有,nuxt框架的初始化搭建,局域網(wǎng)訪問配置,全局axios配置(包括asyncData(),接口請求),swiper插件使用,引入字體包,動態(tài)路由--參數(shù)配置---generate打包,cross-env開發(fā)和線上環(huán)境配置,nuxt并不是最終的優(yōu)化方案

本文內(nèi)容較長,沒有必要全部看完,可以只看需要的,還有下面的配置信息,第二次做的時候,配置起來有少許的出入,so懂理論就好了

背景

2016 年 10 月 25 日,zeit.co 背后的團(tuán)隊對外發(fā)布了 Next.js,一個 React 的服務(wù)端渲染應(yīng)用框架。幾小時后,與 Next.js 異曲同工,一個基于 Vue.js 的服務(wù)端渲染應(yīng)用框架應(yīng)運(yùn)而生,我們稱之為:Nuxt.js。

定義

Nuxt.js 是一個基于 Vue.js 的通用應(yīng)用框架。為基于 Vue.js 的應(yīng)用提供生成對應(yīng)的靜態(tài)站點(diǎn)的功能。通過asyncData鉤子函數(shù)可以生成利于seo優(yōu)化的動靜態(tài)站點(diǎn)

搭建

Nuxt.js團(tuán)隊創(chuàng)建了腳手架工具 create-nuxt-app。

確保安裝了npx(npx在NPM版本5.2.0默認(rèn)安裝了):
安裝之前請先檢查一下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步驟這里我的經(jīng)驗(yàn)是選SSR,因?yàn)槲冶旧砭褪潜贾鴖eo優(yōu)化來的,所以SSR服務(wù)端渲染這塊應(yīng)該是這個

-上面是一整個安裝流程-nuxt官網(wǎng)都有,我多此一舉多安裝一遍而已

安裝完之后--項目目錄如下:


image.png

├── assets // 資源文件。用于組織未編譯的靜態(tài)資源入LESS、SASS 或 JavaScript

├── components // 組件。用于自己編寫的Vue組件,比如滾動組件,日歷組件,分頁組件
│ └── logo.vue // 默認(rèn)logo組件

├── layouts // 布局。頁面都需要有一個布局,默認(rèn)為 default。它規(guī)定了一個頁面如何布局頁面。所有頁面都會加載在布局頁面中的 <nuxt /> 標(biāo)簽中。
│ └── default.vue // 默認(rèn)模板頁面,類似mvc中的layout

├── middleware // 中間件。存放中間件。可以在頁面中調(diào)用: middleware: 'middlewareName'

├── pages   // 頁面。一個 vue 文件即為一個頁面。也會根據(jù)pages里面的文件結(jié)構(gòu)自動生成路由
│ └── index.vue // 默認(rèn)首頁面

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

├── static // 用于存放靜態(tài)資源文件,比如圖片,此類文件不會被 Nuxt.js 調(diào)用 Webpack 進(jìn)行構(gòu)建編譯處理。 服務(wù)器啟動的時候,該目錄下的文件會映射至應(yīng)用的根路徑 / 下。

├── store   // 用于組織應(yīng)用的Vuex 狀態(tài)管理。

├── .editorconfig // 開發(fā)工具格式配置

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

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

├── nuxt.config.js // 用于組織Nuxt.js應(yīng)用的個性化配置,比如網(wǎng)站title,已便覆蓋默認(rèn)配置

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

└── README.md // 說明文檔

上面的結(jié)構(gòu)是整個新建項目的目錄圖,和我網(wǎng)上找的和自己總結(jié)的目錄中每個的作用

現(xiàn)在我們運(yùn)行下面的代碼可以進(jìn)行項目初體驗(yàn)

cd nuxt1
npm run dev

然后好像默認(rèn)的是localhost:3000,在瀏覽器打開就可以了

修改局域網(wǎng)配置

使用localhost:3000訪問之后,有疑問的同學(xué)會發(fā)現(xiàn),你發(fā)給在同一個局域網(wǎng)的同事,他們是打不開你的項目的,跟vue-cli創(chuàng)建的項目不一樣!其實(shí)這里可以通過修改一下config來修改,具體配置如下:

在package.json中新添加

這個就是你本地的地址,同一局域網(wǎng)是訪問不到
"config": {
    "nuxt": {
      "host": "127.0.0.1",
      "port": "8080"
    }
  }

這個是0.0.0.0配置之后,重新運(yùn)行,同一局域網(wǎng)可以訪問
"config": {
    "nuxt": {
      "host": "0.0.0.0",
      "port": "8080"
    }
  }

具體的效果,可以本地測試一下

全局axios配置

個人見解

根據(jù)前面安裝的步驟,已經(jīng)安裝了axios,或者可以去package.json中去檢查一下

npm install axios --save

安裝好之后在/plugins目錄下新建baseUrl.js

// 這里是一個默認(rèn)的url,可以沒有
let baseUrl = ''

// window對象要在.vue文件的mounted生命周期之后才可以獲取,mounted之前就不行
// let hostnames = location.hostname   
// process
switch (process.env.NODE_ENV) {
  case 'development':
    // 開發(fā)環(huán)境url
    baseUrl = 'https://開發(fā)域名'
    break
  case 'production':
    // 生產(chǎn)環(huán)境url
    baseUrl = 'https://生產(chǎn)域名'
    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
// 默認(rèn)是否允許攜帶cookie
axios.defaults.withCredentials = true
axios.defaults.headers = {
  'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
}

axios.interceptors.request.use(
  config => {
    // 若是有做鑒權(quán)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: '請求無響應(yīng)'
      })
    }
    let message
    switch (error.response.status) {
      case 400:
        message = '錯誤請求'
        break
      case 401:
        message = '未授權(quán)'
        break
      case 403:
        message = '拒絕訪問'
        break
      case 404:
        message = '請求路徑未找到'
        break
      case 500:
        message = '服務(wù)器異常'
        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


// 設(shè)置全局變量
import Vue from 'vue' // vue 文件引入 - 方便在vue方法內(nèi)容直接 this 調(diào)取
import http from './axios'

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

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

現(xiàn)在配置完了,看一下/plugins目錄的文件結(jié)構(gòu)


image.png

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

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

然后我們來看.vue文件中,接口的使用和調(diào)用
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
 //  因?yàn)閍syncData方法里面不能讀取和使用this關(guān)鍵字,所以我們只能使用
 //  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 }
  },
}

//我測試的是接口這里的傳參的參數(shù),我沒辦法從data()中獲取,
//所以我是寫死的,然后就是我這里的接口和下面正常接口是一起加載的,
//當(dāng)然這個asyncData是在dom渲染之前就已經(jīng)加載了,
//然后這里的testList也不用在data()中定義,因?yàn)樗\(yùn)行的時候,data()壓根就還沒開始運(yùn)行
//如果不寫這個asyncData函數(shù)的話,項目在瀏覽器運(yùn)行的時候,右鍵查看源代碼,壓根源代碼里就沒有這個接口渲染的dom節(jié)點(diǎn),所以這個方法是必然
//而下面的正常接口方法,是實(shí)現(xiàn)功能的正常接口,asyncData在dom渲染之前就執(zhí)行了,
//所以后續(xù)的邏輯操作根絕壓根用不到它,我個人理解,他的作用就是為了渲染源碼dom
//特指接口渲染的dom

正常的接口調(diào)用

methods: {
    Course () {
      // this.$qs.stringify
      //  這里接口調(diào)用只用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
            }
          }
        })
      })
    },
}

在這里要提一句,上面這種方式,前提是在你設(shè)置了同一局域網(wǎng)可以訪問的那個配置之后才可以的,不然會報錯的,錯誤好像是什么127.0.0.1:80,所以上面文章不仔細(xì)看的,到這里運(yùn)行報錯我只能說,啊,好爽,走我的老路

還有就是子組件,在子組件中是不可以使用asyncData函數(shù)的,因?yàn)樽咏M件搭建的時候是放在components目錄下面的,而不是pages目錄下,所以子組件不可以使用,但是我們可以在父組件中使用asyncData函數(shù),然后通過props函數(shù)接收。但是這種有一個問題就是通過props渲染子組件,瀏覽器查看源代碼的時候是沒有渲染的這一塊的dom結(jié)構(gòu)的,所以不利于seo優(yōu)化,除非是這個組件的內(nèi)容是不準(zhǔn)備讓爬蟲爬取的

這是官方的解釋---僅限于頁面組件


image.png

swiper組件安裝

因?yàn)閚uxt里面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要設(shè)置成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 是常規(guī)的分頁名字, 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:

有時候點(diǎn)擊分頁器,輪播就不能繼續(xù)了,根本原因是是少了一個屬性

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

asyncData一次請求多個接口怎么辦,asyncData多并發(fā)請求如下:

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文件夾把字體文件放進(jìn)去,同事新建一個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中要重命名,把空格去掉,不然會一直報錯

路由配置傳參(動態(tài)路由)--以及generate打包

這個路由配置比較麻煩一點(diǎn),由于不熟悉花了將近2天的時間來搞這個

前面介紹了/pages目錄的設(shè)計會自動生成路由,去.nuxt目錄下的route.js就可以看到,今天要說的就是參數(shù)的傳遞(動態(tài)路由)和generate打包生成靜態(tài)頁面

以我項目為例
http://192.168.0.150:8080/exam/2340/0這是本地的鏈接,/exam/2340/0其中的2340和0是傳入的參數(shù)id和cid,/exam/是要跳轉(zhuǎn)的路由

配置如下

就是在/pages目錄下新建exam文件夾,然后在exam中新建_id文件夾,然后在_id文件夾中新建_cid.vue文件,保存之后可以去/.nuxt目錄下打開route.js,里面的路由已經(jīng)自動生成,文件路徑和路由如下圖

image.png

image.png

然后在_cid.vue中隨便寫幾個字,打開/exam/2340/0就可以看到了,文件配置完成

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

路由頁面獲取參數(shù)

這里做的功能是城市和類型選擇,遍歷拼接的地址

在_cid.vue中,路由的跳轉(zhuǎn),在nuxt中是使用<nuxt-link></nuxt-link>,用法跟<router-link></router-link>一樣
本地的項目是如下圖


image.png

image.png

上面的路由跳轉(zhuǎn)是:to=""拼接的路由路徑,參數(shù)就是接口的id,cid。id是地區(qū)的標(biāo)志,cid是類型的標(biāo)志

咱們做的項目優(yōu)化nuxt的是以seo優(yōu)化為前提的,所以要使用asyncData()來渲染dom和head(),但是我們前面知道asyncData()是不能獲取data中的參數(shù)的,

但是如果是路由傳參,這里可以是可以接受參數(shù)的,如下圖

image.png

用上圖的這種寫法,傳入?yún)?shù){app, params},app這里是前面ctx下面的app結(jié)構(gòu)。params就是要接受的參數(shù)
使用console.log(params)打印可以看到瀏覽器控制臺打印出{id: 2340, cid: 0},這樣前面遍歷的地區(qū)就可以獲取參數(shù)了,這樣的話,我不管是點(diǎn)擊地區(qū)或者類型,參數(shù)都會傳過來,這樣瀏覽器查看源代碼就可以看到渲染了dom。

開發(fā)中遇到一個問題,就是刷新瀏覽器的時候報錯,request to....500,記不太清楚了!這個錯誤的原因是asyncData()方法在項目運(yùn)行的時候,請求到的接口有問題,跟前端無關(guān),跟前端無關(guān),跟前端無關(guān),說三遍。不要傻乎乎的在前端配置文件或者代碼中找錯誤,是接口的問題!把a(bǔ)syncData()方法注釋,在methods鉤子函數(shù)中走一遍方法,F(xiàn)12,network中查看接口請求,就可以看到接口報錯了!這時候通知后臺修改接口!

generate打包

動態(tài)傳參完成之后,我們要驗(yàn)證我們之前寫的asyncData()渲染的dom是否發(fā)布到線上也能正常渲染

//運(yùn)行
npm run generate

預(yù)期的打包之后的結(jié)構(gòu)是exam->下面有很多id子文件夾,id子文件夾里面有cid文件夾,cid文件夾里面是index.html,結(jié)構(gòu)如下:


image.png

我們看到項目中多了一個dist文件夾,。這就是generate之后的打包的文件,但是我們在項目中真正看到的結(jié)果并不是這個樣子的,并沒有exam文件夾,或者/exam里面一個exam.vue

不能顯示的真正原因是因?yàn)闆]有配置generate屬性

在nuxt.config.js中設(shè)置generate屬性配置

我們這里先寫死這幾個
export default {
  generate: {
    routes: [
      '/exam/2340/0',
      '/exam/2341/0',
      '/exam/2352/0'
    ]
  }
}

上面設(shè)置了這三條之后,再運(yùn)行npm run generate . 生成的dist文件夾中的路徑,就跟我前面的那張圖結(jié)構(gòu)一樣了,這樣你把dist文件夾內(nèi)容放到線上,這三個路由切換的時候,右鍵查看源代碼就可以看到渲染的dom了,下圖是線上查看源代碼之后的dom結(jié)構(gòu)

image.png

然后如果有成百上千路由的話,可以和后臺配合合作,大致就是后臺通過接口給你返回所有的可配置的路由,你配置到generate里面,然后就可以了,這塊我項目還沒做到,過兩天再更新

下面來說一下動態(tài)路由打包的利弊

上面說過,nuxt.config.js文件中配置了generate屬性,打包上線的時候,會根據(jù)這個配置生成靜態(tài)文件,從而實(shí)現(xiàn)seo的dom和head渲染,這種方法是與后臺接口配合,大致就是,接口提供給你要做的所有的需要seo優(yōu)化的頁面的路由,通過generate配置,打包的時候請求接口,返回路由路徑,生成靜態(tài)文件,如下圖


image.png

然后這個接口返回的路由數(shù)據(jù),如下圖


image.png

這樣npm run generate打包之后,會生成dist文件夾,dist文件夾格式如下圖


image.png

image.png

這樣發(fā)布到線上,打開頁面右鍵---查看源代碼,就可以看到dom結(jié)構(gòu)和head了。

開發(fā)(本地)和生產(chǎn)(線上)的環(huán)境配置

上面看到我的generate配置里面使用了process.env.BASE_URL,這個其實(shí)是配置了開發(fā)和生產(chǎn)環(huán)境的部署,需要安裝一個依賴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://生產(chǎn).cn NODE_ENV=production nuxt generate"
  },

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

注釋掉baseUrl的加載和默認(rèn)baseURL配置
import * as axios from 'axios'
import qs from 'qs'
// import baseUrl from './baseUrl'

// axios.defaults.baseURL = baseUrl
axios.defaults.timeout = 20000
// 默認(rèn)是否允許攜帶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文件中的請求接口方式不變,這樣項目運(yùn)行或者打包的話,就可以把package.json中的配置的cross-env進(jìn)行全局適配,要知道nuxt中配置文件中window和location.name是訪問不到的,所以這種辦法是很方便的!

我上面配置的不全,感興趣的可以去搜搜別的攻略
使用npm run dev這是運(yùn)行的本地(開發(fā))環(huán)境,接口域名對應(yīng)的是測試接口
使用npm run test這是打包測試的,然后發(fā)布到測試環(huán)境,同樣的接口也是使用的測試接口---注意后面的nuxt generate
使用npm run generate這是打包正式的,然后發(fā)布到測試,測試沒問題之后,再發(fā)布到正式,接口域名是正式(線上)接口

并不是最終的優(yōu)化方案

但是上面這個也有一個弊端,并不能做到真正的動態(tài)化!你想,如果我的后臺操作系統(tǒng),添加了一篇文章,那么在前臺顯示的時候,這篇文章是一個新的路由/路徑,那么這個新增的文章,后臺添加完成之后,前臺是看不到seo的效果的,要再重新打包一次,發(fā)布線上才可以,還有就是如果文件,和路徑少了還好說,如果有上千,上萬,幾百萬的時候,生成的靜態(tài)文件我覺得服務(wù)器會爆炸,所以我的建議如果要使用nuxt,就要考慮你是否是要真正的實(shí)現(xiàn)動態(tài)化seo優(yōu)化??戳薭站的,還有掘金的,目前還沒搞懂他們的技術(shù)點(diǎn)。所以建議是服務(wù)端渲染,通過后臺或者node渲染生成html,渲染到前臺!

后續(xù)配置和操作優(yōu)化seo持續(xù)更新

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容