服務端預渲染之Nuxt (使用篇)

服務端預渲染之Nuxt - 使用

如今大多數開發都是基於Vue或者React開發的,可以達到快速開發的效果,也有一些不足的地方,Nuxt可以在服務端作出渲染,而後讓搜索引擎在爬取數據的時候可以讀到當前頁面。javascript

首先要說明一點,咱們能夠認爲咱們所編寫的Vue項目是一個服務端的項目,雖然編寫的仍是Vue項目,可是Nuxt是基於服務器環境的。html

就簡單的說一下Nuxt使用。基礎只是仍是以官方文檔爲主,若是博客中哪裏有問題,歡迎留言指正。vue

說了這麼多,進入正題。java

路由

與傳統的Vue項目不一樣的是,咱們在使用Vue的時候須要配置Vue-Router信息,在Nuxt有很關鍵的一點就是約定優於配置page目錄下的全部*.vue文件會自動生成路由配置。ios

在項目初始化以後,在pages下面默認有一個index.vue文件,因此當咱們使用npm run dev啓動項目,而且使用http://localhost:3000/訪問的時候可以正常訪問路由。vue-router

爲了證明上面這一點,在pages下面建立一個信息about.vue文件,而且http://localhost:3000/about去訪問剛剛寫的頁面。咱們能夠按照正常的Vue頁面去開發就行了。npm

page目錄element-ui

├─page
│   ├─index.vue
└───└─about.vue

about.vueaxios

<template>
  <div>
      <h2>This About</h2>
  </div>
</template>

建立完成以後使用http://localhost:3000/about訪問該頁面,頁面可以正常的渲染出來了。就會看到This About顯示在頁面中。api

作到這一步以後就應該實現路由之間的跳轉了。Vue開發過程當中,都是使用router-link標籤完成路由之間的跳轉,在Nuxt也一樣可使用router-link,可是Nuxt仍然推薦使用nuxt-link,nuxt-linkrouter-link的功能是等效的。

可能會有一些疑問,既然是等效的,爲何要使用nuxt-link呢?官方文檔中是這樣說的:未來咱們會爲nuxt-link組件增長更多的功能特性,例如資源預加載,用於提高nuxt.js應用的響應速度。顯然嘛,官方不會平白無故的就作出這麼一個東西來,確定實在其中作了不少的優化工做的。

稍微的改動一下剛纔的about.vue在裏面添加兩個標籤,一個使用nuxt-link,一個使用router-link看下可否正常完成跳轉。

about.vue - 更改後

<template>
  <div>
      <h2>This About</h2>
      <nuxt-link to="/">首頁</nuxt-link>
      <router-link to="/">首頁</router-link>
  </div>
</template>

既然從路由開始那麼就不得不說到子路由,全局路由守衛這些都些在路由中常常用到的應該怎麼處理?該怎麼解決這些問題。

前面既然說到了Nuxt會把pages文件夾下面的全部*.vue文件編譯成路由,那麼子路由須要使用文件夾嵌套才行。

接下來就嘗試一下。首先要更改一下pgeas目錄結構。

page目錄

├─page
│   ├─about
│   │  ├─detail.vue
│   │  └─index.vue
└───└─index.vue

注意上面的about目錄,是index.vue而並不是about.vue,這裏的index.vue指的是about路由下的首頁,也就是最開始放在與index.vue同級的那個about.vue是同樣的效果。

about/index.vue

<template>
  <div>
      <h2>This About</h2>
      <nuxt-link to="/">首頁</nuxt-link>
      <router-link to="/">首頁</router-link>
  </div>
</template>

about/detail.vue

<template>
  <div>
    <h2>This Detail</h2>
  </div>
</template>

如今若是咱們想要訪問剛纔的那兩個路由地址分別就是http://localhost:3000/abouthttp://localhost:3000/about/detail就能看到剛纔編寫的page頁面了。

若是想要看路由生成究竟是什麼樣子的?能夠在根目錄下有一個.nuxt文件夾,在裏面能夠看到一個router.js,這個文件夾下面就是Nuex生成好的路由信息。

打開文件後翻到最後會有一段這樣的代碼,是否是很眼熟?這是不就是在編寫Vue項目的時候配置的哪些路由文件麼?

router.js

export function createRouter() {
  return new Router({
    mode: 'history',
    base: decodeURI('/'),
    linkActiveClass: 'nuxt-link-active',
    linkExactActiveClass: 'nuxt-link-exact-active',
    scrollBehavior,
    routes: [{
      path: "/about",
      component: _9ceb4424,
      name: "about"
    }, {
      path: "/about/detail",
      component: _18146f65,
      name: "about-detail"
    }, {
      path: "/",
      component: _d3bf5a4e,
      name: "index"
    }],
    fallback: false
  })
}

有了這個文件的話咱們就能夠清楚的知道,路由的結構了。不只僅這樣,還可使用name去實現路由的跳轉了。

須要注意的是,若是你的路由是有文件夾嵌套的話,Nuxt是用使用-來拼接路由的name名稱的(如:about-detail),可是文件夾內部的index.vue會直接已文件夾的名字做爲name。一旦知道了路由的name,這樣咱們就可使用命令的方式跳轉路由了。

再次更改一下about/index.vue

about/index.vue

<template>
  <div>
      <h2>This About</h2>
      <nuxt-link :to="{name:'about-detail'}">詳情</nuxt-link>
      <router-link :to="{name:'index'}">首頁</router-link>
      <button @click="onClick">跳轉到詳情</button>
  </div>
</template>
<script>
export default {
  methods: {
    onClick() {
      this.$router.push({name:"about-detail"})
    }
  }
}
</script>

使用路由訪問http://localhost:3000/about地址,分別點擊詳情、首頁與button,都是可以正常跳轉的,與以前的Vue開發是徹底沒有任何區別的。在vue-router中有一個很重要的一個點就是動態路由的概念,若是想要實現動態路由應該怎麼處理呢?

若是想要在Nuxt中使用動態路由的話,須要在對應的路由下面添加一個_參數名.vue的文件,在about文件下面添加一個_id.vue

page目錄

├─page
│   ├─about
│   │  ├─detail.vue
│   │  ├─_id.vue
│   │  └─index.vue
└───└─index.vue

新建完成以後在去router.js中看一下更改後的路由結構

export function createRouter() {
  return new Router({
    mode: 'history',
    base: decodeURI('/'),
    linkActiveClass: 'nuxt-link-active',
    linkExactActiveClass: 'nuxt-link-exact-active',
    scrollBehavior,
    routes: [{
      path: "/about",
      component: _9ceb4424,
      name: "about"
    }, {
      path: "/about/detail",
      component: _18146f65,
      name: "about-detail"
    }, {
      path: "/about/:id",
      component: _6b59f854,
      name: "about-id"
    }, {
      path: "/",
      component: _d3bf5a4e,
      name: "index"
    }],
    fallback: false
  })
}

能夠明顯的看到在/about/:id這個路由,明顯的變化不止這些變更的還有name: "about-id"再也不是以前的name:about了。若是想要使用這個id的話必須在_id.vue中才能獲取到。

**_id.vue**

<template>
  <div>
    {{$route.params.name}}
    {{$route.params.id}}
  </div>
</template>

_id.vue中編寫以上代碼並使用http://localhost:3000/about/ABC,能夠看到在頁面中已經展現了當前的id值。

在實際開發過程中可能params可能會有多個參數,又應該怎麼處理呢?

調整目錄結構

//  id爲可選參數
├─page
│   ├─about
│   │  ├─_name
|   |  |    └─_id
|   |  |        └─index.vue
│   │  └─index.vue
└───└─index.vue

**about - _name - _id.vue**

<template>
  <div>
    {{$route.params.name}}
    {{$route.params.id}}
  </div>
</template>

弄完以後看下router.js的變化

export function createRouter() {
  return new Router({
    mode: 'history',
    base: decodeURI('/'),
    linkActiveClass: 'nuxt-link-active',
    linkExactActiveClass: 'nuxt-link-exact-active',
    scrollBehavior,
    routes: [{
      path: "/about",
      component: _9ceb4424,
      name: "about"
    }, {
      path: "/about/detail",
      component: _18146f65,
      name: "about-detail"
    }, {
      path: "/about/:name",
      component: _2ec9f53c,
      name: "about-name"
    }, {
      path: "/about/:name/:id",
      component: _318c16a4,
      name: "about-name-id"
    }, {
      path: "/",
      component: _d3bf5a4e,
      name: "index"
    }],
    fallback: false
  })
}

這裏展現的是第二種狀況,id爲必選參數的狀況,路由被編譯的結果。

雖然路由已經添加了參數,可是id屬性不是必填屬性,這樣的話不能知足項目需求又要如何處理呢?很簡單的,在_id.vue文件同目錄下添加一個index.vue文件就能夠了。

//  id爲必選參數
├─page
│   ├─about
│   │  ├─_name
|   |  |    ├─_id.vue
|   |  |    └─index.vue
│   │  └─index.vue
└───└─index.vue

須要注意的是,必定要在_id.vue文件中使用傳入的參數,直接獲取在index.vue中是拿不到任何信息的。可是若是訪問http://localhost:3000/about/ABC這樣的路由的話,實在index.vue中是能夠獲取到name參數的。

在剛纔的router.js文件中生成的全部的路由都是平級的,如何實現路由的嵌套,若是想要實現嵌套路由的話,必須有和當前路由同名的文件夾存在,才能完成路由的嵌套。

page目錄

├─page
│   ├─about
|   |  ├─_id.vue
|   |  └─detaile.vue
│   ├─about.vue
└───└─index.vue

router.js

export function createRouter() {
  return new Router({
    mode: 'history',
    base: decodeURI('/'),
    linkActiveClass: 'nuxt-link-active',
    linkExactActiveClass: 'nuxt-link-exact-active',
    scrollBehavior,
    routes: [{
      path: "/about",
      component: _76687814,
      children: [{
        path: "",
        component: _9ceb4424,
        name: "about"
      }, {
        path: ":id",
        component: _6b59f854,
        name: "about-id"
      }]
    }, {
     path: "/",
      component: _d3bf5a4e,
      name: "index"
    }],
    fallback: false
  })
}

更改完目錄結構,那咱們嵌套的路由應該如何展現?在vue.js中開發的時候使用router-view這個標籤完成的。爲了性能的優化Nuxt也提供了一個對應的標籤nuxt-child

若是想實現嵌套路由傳參須要稍微的改動一下目錄結構,按照上面的方法實現就行了,下面是一個路由結構的例子。

page目錄

├─page
│   ├─about
│   │  ├─detail
|   |  |    ├─_id.vue
|   |  |    └─index.vue
│   │  └─index.vue
└───└─index.vue

router.js

export function createRouter() {
  return new Router({
    mode: 'history',
    base: decodeURI('/'),
    linkActiveClass: 'nuxt-link-active',
    linkExactActiveClass: 'nuxt-link-exact-active',
    scrollBehavior,
    routes: [{
      path: "/about",
      component: _76687814,
      name: "about",
      children: [{
        path: "detail",
        component: _0a09b97d,
        name: "about-detail"
      }, {
        path: "detail/:id?",
        component: _fa7c11b6,
        name: "about-detail-id"
      }]
    }, {
      path: "/",
      component: _d3bf5a4e,
      name: "index"
    }],
    fallback: false
  })
}

_id.vue中則可使用id這個參數了。訪問路由http://localhost:3000/about/detail/123,依然能夠拿到傳入的id123的這個參數。

說了這麼多了,還有不少問題沒得說完,關於路由的全局守衛又應該如何去使用?在Nuxt根目錄下有個plugins文件夾。首先要作的是在裏面建立一個名爲router.js文件。

plugins-router.js

export default ({app}) => {
  app.router.beforeEach((to,form,next) => {
    console.log(to)
    next();
  });
}

導出了一個函數,在這個函數中能夠經過結構拿到vue的實例對象名叫app。須要注意的是,這個beforeEach函數的執行,有可能會在服務端也會有可能在客戶端輸出。客戶端首次訪問的頁面會在服務端作輸出,一旦渲染完成以後,則不會再在服務端輸出,則會一直在客戶端進行輸出了。

說到這裏作個小插曲,那麼又該怎麼區分當前是在客戶端環境仍是服務端環境呢?可使用process.server獲取到當前的運行環境,其獲得的是Boolean值,true服務端,fasle客戶端。

作了這些以後去訪問路由,彷彿沒有任何輸出,不管實在客戶端仍是在服務端,都沒有任何打印輸出,中間缺乏了步驟,須要在根目錄下找到nuxt.config.js對插件進行配置。

nuxt.config.js

const pkg = require('./package')
module.exports = {
  plugins: [
    '@/plugins/element-ui',
    '@/plugins/router'
  ]
}

因爲更改了Nuxt配置須要重啓一下服務,才能正常執行剛剛寫入的插件。而後訪問剛剛寫入的路由,會看在服務端初次渲染的時候,會輸出咱們想要的那些東西,進行路由跳轉的話,會在客戶端輸出,這也就證實了Nuxt只會作首屏的服務器渲染。

路由說了這麼接下來須要說一下Nuxt是如何爲指定的路由配置數據作渲染的。其實Nuxt在作渲染的時候包裹了不少層。首先有一個Document做爲其模板,而後再去尋找其佈局的頁面,找到對應的頁面以後,再根據引用去找到相關的組件進行渲染,數據請求與數據掛載,一系列完成以後,把剩餘的路由信息返還給客戶端,渲染完成,這個就是Nuxt簡單的渲染流程。

在上面提到了一個佈局頁面,這個東西應該去哪裏找?又應該怎麼作呢?它對於項目而言對於開發又有什麼好處?在Nuxt根目錄下有一個layouts文件夾,下面有一個default.vue這個文件就是上面提到的渲染頁面,也就同等於vue開發中的App.vue,在這裏能夠作不少事情。例如添加一個全局的導航。

layouts文件夾添加一個about.vue文件寫入以下內容,接下來須要在pages下面的about.vue中通知,對應pages使用哪一個佈局頁面,不寫則使用默認,而後訪問http://localhost:3000/about相關的頁面,只要是和about相關的頁面,都會展現這個內容。

layouts - about.vue

<template>
  <div>
    <h2>Aaron 我的博客主頁</h2>
    <nuxt></nuxt>
  </div>
</template>

pages - about.vue

<template>
  <div>
      <h2>About</h2>
      <nuxt-child></nuxt-child>
  </div>
</template>
<script>
export default {
  layout:"about"
}
</script>

訪問一下全部與about頁面有關的頁面,都會看到Aaron我的博客主頁這個字樣,若訪問根路由則沒法看到的。

若是作過mvc開發的話,若是頁面發生錯誤會跳轉到一個錯誤頁面的。Nuxt也是有默認的錯誤頁面的,可是全是英文並且樣式也不太好看,不能自定義樣式。如何自定義錯誤頁面呢?

layouts文件夾中新建一個error.vue文件。

layouts - error.vue

<template>
  <div>
    <h1>這裏是錯誤頁面</h1>
    <h2 v-if="error.statusCode == 404">404 - 頁面不存在</h2>
    <h2 v-else>500 - 服務器錯誤</h2>
    <ul>
        <li><nuxt-link to="/">HOME</nuxt-link></li>
    </ul>
  </div>
</template>

<script>
export default {
  props:["error"]
}
</script>

error.vue中能夠經過props拿到一個error對象,獲取到error錯誤信息以後能作任何想要作的事情。須要注意的一點是,自定意的錯誤頁面,只能在客戶端訪問失效的時候纔會響應到該頁面,若在服務端的話,是沒法直接渲染這個頁面的。

更改頁面配置Nuxt中有些全局的配置,配置信息在nuxt.config.js更改其全局配置,pages文件夾中的*.vue文件也是能夠配置的,頁面私有的配置會覆蓋掉全局的配置。

舉例:

export default {
  layout:"about",
  head: {
    title:"About"
  }
}

在這些全局配置中最重要的一個就是asyncData這個屬性。asyncData究竟是用來作什麼的呢?這個數據能夠在設置組件的數據以前能一步獲取或者處理數據。也就是說在組件渲染以前先獲取到數據,而後等待掛載渲染。

舉個例子:

<template>
  <div>
      <h2>姓名:{{userInfo.name}}</h2>
      <h2>年齡:{{userInfo.age}}</h2>
      <nuxt-child></nuxt-child>
  </div>
</template>
<script>
let getUserInfo = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      let data = {"name":"Aaron","age":18};
      resolve(data);
    })
  })
}
export default {
  layout:"about",
  head: {
    title:"About"
  },
  async asyncData(){
    const userInfo = await getUserInfo();
    return {userInfo}
  }
}
</script>

必定要return出去獲取到的對象,這樣就能夠在組件中使用,這裏返回的數據會和組件中的data合併。這個函數不光在服務端會執行,在客戶端一樣也會執行。

注意事項:

  1. asyncData 方法會在組件(限於頁面組件)每次加載以前被調用
  2. asyncData 能夠在服務端或路由更新以前被調用
  3. 第一個參數被設定爲當前頁面的上下文對象
  4. Nuxt會將 asyncData 返回的數據融合到組件的data方法返回的數據一併返回給組件使用
  5. 對於 asyncData 方式實在組件初始化前被調用的,因此在方法內飾沒辦法經過this來引用組件的實例對象

剛剛提到了一點就是上下問對象,在上線文對象中能夠獲取到不少東西,如路由參數,錯誤信息等等等,這裏就不做太多贅述了,有了這些能夠作一些頁面的重定向或者其餘工做,好比參數校驗,安全驗證等工做。

路由扯了一大堆,接下來講一下如何在Nuxt中融入axios的使用。

安裝axios

npm install @nuxtjs/axios --save-dev

安裝完成後更改配置信息:

nuxt.config.js

module.exports = {
    modules: [
        // Doc: https://axios.nuxtjs.org/usage
        '@nuxtjs/axios',
    ],
    axios: {
        proxy:true  //  代理
    },
    proxy: {
        "/api/":"http://localhost:3001/"    //  key(路由前綴):value(代理地址)
    }
}

主要說名一下proxy這裏,/api/在請求的時候遇到此前綴則會只指向的代理地址去請求數據。

既然說到了axios,就不得不提到的一個東西就是攔截器,非常有用在項目開發過程當中必不可少的。

舉個例子:

module.expores{
  plugins: [
    '@/plugins/axios'
  ],
  modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
  ],
}

plugins/axios.js

export default ({ $axios, redirect }) => {
  $axios.onRequest(config => {
    console.log('Making request to ' + config.url)
  })

  $axios.onError(error => {
    const code = parseInt(error.response && error.response.status)
    if (code === 400) {
      redirect('/400')
    }
  })
}

總結

說了這麼多也許會有些紕漏,或者遺漏的知識點,如有什麼錯誤的地方能夠在下方留言,儘快作出改正。謝謝你們花費這麼長時間閱讀這篇文章。

相關文章
相關標籤/搜索