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

时间:2021-05-31 09:31:36

Nuxt.js的介绍

Nuxt.js概述

nuxt.js简单的说是Vue.js的通用框架,最常用的就是用来作SSR(服务器端渲染).Vue.js是开发SPA(单页应用)的,Nuxt.js这个框架,用Vue开发多页应用,并在服务端完成渲染,可以直接用命令把我们制作的vue项目生成为静态html

通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI渲染

我们的目标是创建一个灵活的应用框架,你可以基于它初始化新项目的基础结构代码,或者在已有 Node.js 项目中使用 Nuxt.js。

Nuxt.js 预设了利用Vue.js开发服务端渲染的应用所需要的各种配置。还提供了一种命令叫:nuxt generate,为基于 Vue.js 的应用提供生成对应的静态站点的功能

作为框架,Nuxt.js 为 客户端/服务端 这种典型的应用架构模式提供了许多有用的特性:例如异步数据加载、中间件支持、布局支持等

压缩并 gzip 后,总代码大小为:57kb (如果使用了 Vuex 特性的话为 60kb)

官网地址:https://zh.nuxtjs.org/guide

服务器端渲染到底有什么好处

主要的原因时SPA(单页应用)不利于搜索引擎的SEO操作,Nuxt.js适合作新闻、博客、电影、咨询这样的需要搜索引擎提供流量的项目。如果你要作移动端的项目,就没必要使用这个框架了

什么是SSR

SSR,即服务器渲染,就是在服务器端将对Vue页面进行渲染生成html文件,将html页面传递给浏览器

SSR的两个优点

SEO 不同于SPA的HTML只有一个无实际内容的HTML和一个app.js,SSR生成的HTML是有内容的,这让搜索引擎能够索引到页面内容

更快内容到达时间 传统的SPA应用是将bundle.js从服务器获取,然后在客户端解析并挂载到dom。而SSR直接将HTML字符串传递给浏览器。大大加快了首屏加载时间

特性

基于 Vue.js。自动代码分层。服务端渲染。强大的路由功能,支持异步数据。静态文件服务。ES2015+ 语法。支持打包和压缩 JS 和 CSS。HTML头部标签管理。本地开发支持热加载。集成ESLint。支持各种样式预处理器: SASS、LESS、 Stylus等等。支持HTTP/2 推送

Nuxt.js 框架是如何运作的

基于 Vue、Webpack 和 Babel。Nuxt.js 集成了以下组件/框架,用于开发完整而强大的 Web 应用:Vue2,Vue Router,Vuex(当配置了 Vuex 状态树配置项 时才会引入),Vue Server Renderer ,vue-mate

Nuxt.js 使用 Webpack 和 vue-loader 、 babel-loader 来处理代码的自动化构建工作(如打包、代码分层、压缩等等)

Nuxt.js的工作流

下图阐述了 Nuxt.js 应用一个完整的服务器请求到渲染(或用户通过 <nuxt-link> 切换路由渲染页面)的流程:

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

Incoming Request:浏览器发出一个请求

nuxtServerInit:服务端接收到请求,检查当前有没有nuxtServerInit这个配置项,如果有的化就先执行这个方法,这个方法是用来操作vuex的

middleware:这是一个中间件,跟路由相关,这里可以做任何想要的功能

validate():验证,配合高级动态路由去做一些验证,比如A页面是否允许跳转到B页面去,如果没有验证通过可以跳转到别的页面之类的校验

asyncData()&fetch():获取数据。asyncData()获取的数据是用来渲染vue组件的,fetch通常用来修改vuex的数据

Render:渲染

Naviage:如果发起了一个非Server  的路由,那么在执行一遍middleware——Render的过程

服务端渲染

你可以使用 Nuxt.js 作为你项目的UI渲染框架。

当运行 nuxt 命令时,会启动一个支持 热加载 和 服务端渲染(基于Vue.js的 vue-server-renderer 模块)的开发服务器。

更多命令了解:https://zh.nuxtjs.org/guide/commands/

单页应用程序 (SPA)

如果您不想使用服务器端渲染或需要应用程序提供静态托管,则可以使用nuxt --spa命令即可使用SPA模式。 它为您提供了强大的SPA部署机制,无需使用Node.js来运行应用程序或任何特殊的服务器端处理。

可以查看Nuxt.js提供的各种命令来了解更多相关使用信息。

如果你的项目有自己的Web服务器(例如用 Express.js 启动的Web服务),你仍然可以将 Nuxt.js 当作是中间件来使用,负责UI渲染部分的功能。在开发通用的Web应用过程中,Nuxt.js 是可插拔的,没有太多的限制,

可通过 开发编码中使用Nuxt.js 了解更多的信息

Nuxt环境搭建

Nuxt.js 十分简单易用。一个简单的项目只需将 nuxt 添加为依赖组件即可,为了快速入门,Nuxt.js团队创建了脚手架工具 create-nuxt-app

确保安装了npx(npx在NPM版本5.2.0默认安装了)

npx create-nuxt-app <项目名>

它会让你进行一些选择

项目名称

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

项目描述

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

在集成的服务器端框架之间进行选择

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

选择一些插件

是否选择单页面应用

添加 EsLint 以在保存时代码规范和错误检查您的代码。

添加 Prettier 以在保存时格式化/美化您的代码

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

选择您喜欢的UI框架

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

当运行完时,它将安装所有依赖项,因此下一步是启动项目,应用现在运行在 http://localhost:3000 上运行

npm run dev

如果不使用 Nuxt.js 提供的 starter 模板,我们也可以从头开始新建一个 Nuxt.js 应用项目,过程非常简单,只需要 1个文件和1个目录。如下所示:

mkdir <项目名>
cd <项目名>

新建 package.json 文件,使用下面命令创建package.josn文件

npm init --yes

安装 nuxt:一旦 package.json 创建好, 可以通过以下npm命令将 nuxt 安装至项目中

npm install --save nuxt

Nuxt.js 会依据 pages 目录中的所有 *.vue 文件生成应用的路由配置

创建 pages 目录——创建我们的第一个页面 pages/index.vue

<template>
  <h1>Hello world!</h1>
</template>

然后启动项目,应用运行在 http://localhost:3000 上运行

npm run dev

项目目录结构

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

assets: 资源目录,用于组织未编译的静态资源如 LESSSASS 或 JavaScript

components:组件目录,用于组织应用的 Vue.js 组件。Nuxt.js 不会扩展增强该目录下 Vue.js 组件,即这些组件不会像页面组件那样有 asyncData 方法的特性

layouts:布局目录 layouts 用于组织应用的布局组件。该目录名为Nuxt.js保留的,不可更改

middleware:中间件目录,目录用于存放应用的中间件

pages:页面目录 用于组织应用的路由及视图。Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置。该目录名为Nuxt.js保留的,不可更改

plugins:插件目录 用于组织那些需要在 根vue.js应用 实例化之前需要运行的 Javascript 插件

static:静态文件目录 用于存放应用的静态文件,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。 服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下。该目录名为Nuxt.js保留,不可更改。

nuxt.config.js 文件:文件用于组织Nuxt.js 应用的个性化配置,以便覆盖默认配置。该文件名为Nuxt.js保留的,不可更改

Store 目录:store 目录用于组织应用的 Vuex 状态树 文件。 Nuxt.js 框架集成了 Vuex 状态树 的相关功能配置,在 store 目录下创建一个 index.js文件可激活这些配置。该目录名为Nuxt.js保留,不可更改

package.json :文件用于描述应用的依赖关系和对外暴露的脚本接口。该文件名为Nuxt.js保留的,不可更改。

别名

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

默认情况下,src目录根目录相同

vue 模板中, 如果需要引入 assets 或者 static 目录, 使用 ~/assets/your_image.png 和 ~/static/your_image.png方式

Nuxt.js配置

详情查看文档:https://zh.nuxtjs.org/guide/configuration

路由使用的示例和参数传递

将路由级别的页面都定义在pages目录下,Nuxt.js会自动配置路由,创建了如下目录和vue文件

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

index.vue中添加两个路由链接和引入一个公共的组件。search.vue页面。about.vue页面引入两个组件

<template>
  <section class="container">
    <div>
      <logo />
      <h2 class="subtitle">
        My best Nuxt.js project
      </h2>
      <div class="links">
        <nuxt-link class="search-page" to="/search">Search</nuxt-link>
        <nuxt-link class="about-page" to="/about/about">About</nuxt-link>
      </div>
    </div>
  </section>
</template>

<script>
import Logo from '~/components/Logo.vue'

export default {
  components: {
    Logo
  }
}
</script>

<style>
.container {
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

.subtitle {
  font-weight: 300;
  font-size: 42px;
  color: #526488;
  word-spacing: 5px;
  padding-bottom: 15px;
}

.links {
  padding-top: 15px;
}
</style>
<template>
  <div class="search-page">This is Search Page</div>
</template>

<script>
export default {
  name: 'search'
}
</script>

<style scoped></style>
<template>
  <div class="about-page">
    This is About Page
    <about1></about1>
    <about2></about2>
  </div>
</template>

<script>
import About1 from './components/about1'
import About2 from './components/about2'
export default {
  name: 'about',
  components: {
    About1,
    About2
  }
}
</script>

<style scoped></style>

params传递参数

路由经常需要传递参数,我们可以简单的使用params来进行传递参数,我们现在向新闻页面(news)传递个参数,然后在新闻页面进行简单的接收

下面index.vue页面跳转到search.vue页面,传递一个id参数

<template>
  <section class="container">
    <div>
      <div class="links">
        <nuxt-link
          class="search-page"
          :to="{ name: 'search', params: { Id: 3306 } }">
          Search
        </nuxt-link>
        <nuxt-link class="about-page" to="/about/about">About</nuxt-link>
      </div>
    </div>
  </section>
</template>

<script>
export default {}
</script>

<style>
.container {
  margin: 0 auto;
}
.links {
  padding-top: 15px;
}
</style>

search.vue页面中接收传递过来的参数

<template>
  <div class="search-page">
    This is Search Pages
    <div>ID:{{ $route.params.Id }}</div>
  </div>
</template>

<script>
export default {
  name: 'Search'
}
</script>

<style scoped></style>

Nuxt的动态路由和参数校验

动态路由,其实动态路由就是带参数的路由。比如我们现在新闻模块下面有很多新闻详细页

页面模板使用示例

页面模板的作用就是将一个所有页面都会引入的一个组件放在页面模板中,这样就不用在每个页面中去引入这个公共的组件

layouts下会有一个default.vue组件,如果pages里的页面组件没有指定其他页面模板,那么默认就是default.vue模板,再页面模板中也可以引入其他公共的组件

layouts/default.vue

<template>
  <div>
    我是默认的模板
    <nuxt />
  </div>
</template>

<style>
html {
  font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
    Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: 16px;
  word-spacing: 1px;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  box-sizing: border-box;
}
</style>

pages/index.vue

<template>
  <section class="container">
    <div>
      <div class="links">
        <nuxt-link class="search-page" to="/search">Search</nuxt-link>
        <nuxt-link class="about-page" to="/about/about">About</nuxt-link>
      </div>
    </div>
  </section>
</template>

<script>
export default {}
</script>

<style>
.container {
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}
.links {
  padding-top: 15px;
}
</style>

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

pages/search.vue

<template>
  <div class="search-page">This is Search Page</div>
</template>

<script>
export default {
  name: 'search'
}
</script>

<style scoped></style>

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

pages/about.vue

<template>
  <div class="about-page">
    This is About Page
    <about1></about1>
    <about2></about2>
  </div>
</template>

<script>
import About1 from './components/about1'
import About2 from './components/about2'
export default {
  name: 'about',
  components: {
    About1,
    About2
  }
}
</script>

<style scoped></style>

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

自定义页面模板

在layouts文件夹中创建一个vue文件info.vue

<template>
  <div>
    <h1 class="info-header">我是默认的头部</h1>
    <nuxt />
    <footer class="info-footer">我是默认的底部</footer>
  </div>
</template>

<style>
.info-header {
  color: red;
}
.info-footer {
  color: blue;
}
</style>

然后再需要的页面组件中使用layout:'xxx'来声明

<template>
  <div class="search-page">This is Search Page</div>
</template>

<script>
export default {
  name: 'search',
  layout: 'info'
}
</script>

<style scoped></style>

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

没有声明的还是使用default默认的页面模板

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

异步数据的使用

首先再服务端写一个接口,如果服务端使用的是别的语言或者只是前端开发可以不用这个步骤,首先创建文件和目录

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

city.js

const Router = require('koa-router')

// 接口的配置
const router = new Router({
  prefix: '/city' // 接口的前缀
})

// 具体的接口
router.get('/list',async (ctx) => {
  ctx.body = {
    list: ['北京', '深圳', '上海', '广州']
  }
})

module.exports = router

服务端入口文件index.js

引入模块并且使用use调用

const Koa = require('koa')
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
const config = require('../nuxt.config.js')
const cityInterface = require('./interface/city.js')

const app = new Koa()

// Import and Set Nuxt.js options
config.dev = !(app.env === 'production')

async function start() {
  // Instantiate nuxt.js
  const nuxt = new Nuxt(config)

  const {
    host = process.env.HOST || '127.0.0.1',
    port = process.env.PORT || 3000
  } = nuxt.options.server

  // Build in development
  if (config.dev) {
    const builder = new Builder(nuxt)
    await builder.build()
  } else {
    await nuxt.ready()
  }
  app.use(cityInterface.routes()).use(cityInterface.allowedMethods())
  app.use(ctx => {
    ctx.status = 200
    ctx.respond = false // Bypass Koa's built-in response handling
    ctx.req.ctx = ctx // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
    nuxt.render(ctx.req, ctx.res)
  })

  app.listen(port, host)
  consola.ready({
    message: `Server listening on http://${host}:${port}`,
    badge: true
  })
}

start()

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

接口已经编写好了,可以调用这个接口,使用正常的方式请求这个接口的数据

<template>
  <div class="search-page">
    This is Search Page
    <div v-for="(item, index) in list" :key="index">{{ item }}</div>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'Search',
  data() {
    return {
      list: []
    }
  },
  async created() {
    const that = this
    const {
      status,
      data: { list }
    } = await axios.get('/city/list')
    if (status === 200) {
      that.list = list
    }
  }
}
</script>

<style scoped></style>

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

接下来我们使用SSR的方式去渲染,注意asyncData是用来处理组件数据的,fetch是用来处理Vuex的,下面如果使用fetch能请求回来数据但渲染不出来

<template>
  <div class="search-page">
    This is Search Pages
    <div v-for="(item, index) in list" :key="index">{{ item }}</div>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'Search',
  data() {
    return {
      list: []
    }
  },
  async asyncData() {
    const {
      status,
      data: { list }
    } = await axios.get('/city/list')
    if (status === 200) {
      return {
        list
      }
    }
  }
}
</script>

<style scoped></style>

Vuex 状态树

Nuxt.js 会尝试找到应用根目录下的 store 目录,如果该目录存在,它将做以下的事情:引用 vuex 模块,将 vuex 模块 加到 vendors 构建配置中去,设置 Vue 根实例的 store 配置项

Nuxt.js 支持两种使用 store 的方式

普通方式: store/index.js 返回一个 Vuex.Store 实例

模块方式: store 目录下的每个 .js 文件会被转换成为状态树指定命名的子模块 (当然,index 是根模块)

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

const state = () => ({
  list: ['a', 'b']
})

const mutations = {
  add(state, text) {
    state.list.push(text)
  }
}

const action = {
  add: ({ commit }, text) => {
    commit('add', text)
  }
}

module.exports = {
  namespace: true,
  state,
  mutations,
  action
}
import Vue from 'vue'
import Vuex from 'vuex'
import city from './modules/city'

Vue.use(Vuex)

const stroe = () =>
  new Vuex.Store({
    modules: {
      city
    },
    actions: {
      // nuxtServerInit({ commit }, { req }) {
      //   if (req.session.user) {
      //     commit('city', req.session.user)
      //   }
      // }
    }
  })

export default stroe
<template>
  <div class="search-page">
    This is Search Pages
    <div v-for="(item, index) in $store.state.city.list" :key="index">
      {{ item }}
    </div>
  </div>
</template>

<script>
export default {
  name: 'Search'
}
</script>

<style scoped></style>

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