Nuxt.js服务端渲染

1,989 阅读4分钟

SPA(单页面应用)项目对于SEO(搜索引擎优化)非常不利,爬虫爬SPA应用的时候,所有的页面源码都是下面的样子(Ctrl +U 就可以查看网页源代码)。

<html>
<head>
  <title>Single Page Application</title>
  <link rel="stylesheet" href="app.css" type="text/css">
</head>
<body>
  <div id="app"></div>
  <script src="app.js"></script>
</body>
</html>

在index.html入口文件中设置title,meta信息等是整个项目所有页面共用的,有SEO需求页面的title,meta信息是无法定制化的,网页中的内容如商品信息等爬虫更无从知道。
因此想支持SEO,SPA需要SSR。
SSR可以在访问特定页面的时候,返回特定页面的title,meta信息以及html源码。

那么SPA做SSR,有几种办法:

  • 1.预渲染 使用 webpack prerender-spa-plugin组件
  • 2.vue ssr 使用 vue-server-renderer package
  • 3.Nuxt.js 框架

一、Nuxt.js简介

现在 Vue.js 大多数用于单页面应用,随着技术的发展,单页面应用已不足以满足需求。并且一些缺点也成为单页面应用的通病,单页面应用在访问时会将所有的文件进行加载,首屏访问需要等待一段时间,也就是常说的白屏,另外一点是周知的 SEO 优化问题。 Nuxt.js 的出现正好来解决这些问题,如果我们的网站是偏向社区需要搜索引擎提供流量的项目,就再合适不过了。

创建一个Nuxt.js项目

1.安装

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

npx create-nuxt-app <项目名>
npm install
npm run dev

经过以上三个步骤,打开浏览器,访问localhost:3000,这跟创建一个Vue项目没太多不同。项目目录结构如下:

  • 资源目录 assets: 用于组织未编译的静态资源如 LESS、SASS 或 JavaScript。
  • 组件目录 components: 用于组织应用的 Vue.js 组件。Nuxt.js 不会扩展增强该目录下 Vue.js 组件,即这些组件不会像页面组件那样有 asyncData 方法的特性。
  • 布局目录 layouts: 用于组织应用的布局组件。
  • middleware : 目录用于存放应用的中间件。
  • 页面目录 pages : 用于组织应用的路由及视图。Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置。
  • 插件目录 plugins: 用于组织那些需要在 根vue.js应用 实例化之前需要运行的 Javascript 插件。
  • 静态文件目录 static : 用于存放应用的静态文件,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。 服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下。
  • store: 目录用于组织应用的 Vuex 状态树 文件。 Nuxt.js 框架集成了 Vuex 状态树 的相关功能配置,在 store 目录下创建一个 index.js 文件可激活这些配置。
  • nuxt.config.js : 文件用于组织Nuxt.js 应用的个性化配置,以便覆盖默认配置。
  • package.json : 文件用于描述应用的依赖关系和对外暴露的脚本接口。

Nuxt常用页面生命周期

我们要在服务器端获取并渲染数据。Nuxt.js添加了asyncData方法使得你能够在渲染组件之前异步获取数据。

asyncData是最常用最重要的生命周期,同时也是服务端渲染的关键点。该生命周期只限于页面组件调用,第一个参数为 context。它调用的时机在组件初始化之前,运作在服务端环境。所以在 asyncData 生命周期中,我们无法通过 this 引用当前的 Vue 实例,也没有 window 对象和 document 对象,这些是我们需要注意的。
一般在 asyncData 会对主要页面数据进行预先请求,获取到的数据会交由服务端拼接成 html 返回前端渲染,以此提高首屏加载速度和进行 seo 优化。

export default {
  async asyncData({ app }) {
    let list = await app.$axios.getIndexList({
      pageNum: 1,
      pageSize: 20
    }).then(res => res.s === 1 ? res.d : [])
    // 返回数据
    return {
      list
    }
  },
  data() {
    return {
      list: []
    }
  }
}

syncData 只在首屏被执行,其它时候相当于 created 或 mounted 在客户端渲染页面。
什么意思呢?举个例子:
现在有两个页面,分别是首页和详情页,它们都有设置 asyncData。进入首页时,asyncData 运行在服务端。渲染完成后,点击文章进入详情页,此时详情页的 asyncData 并不会运行在服务端,而是在客户端发起请求获取数据渲染,因为详情页已经不是首屏。当我们刷新详情页,这时候详情页的 asyncData 才会运行在服务端。

head

Nuxt.js 使用了 vue-meta 更新应用的 头部标签(Head) 和 html 属性。

组件中

export default {
  head () {
    return {
      title: this.articInfo.title,
      meta: [
        { hid: 'description', name: 'description', content: this.articInfo.content }
      ]
    }
  }
}

为了避免子组件中的 meta 标签不能正确覆盖父组件中相同的标签而产生重复的现象,建议利用 hid 键为 meta 标签配一个唯一的标识编号。

在 nuxt.config.js 中,我们还可以设置全局的 head:

module.exports = {
  head: {
    title: '掘金',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover' },
      { name: 'referrer', content: 'never'},
      { hid: 'keywords', name: 'keywords', content: '掘金,稀土,Vue.js,微信小程序,Kotlin,RxJava,React Native,Wireshark,敏捷开发,Bootstrap,OKHttp,正则表达式,WebGL,Webpack,Docker,MVVM'},
      { hid: 'description', name: 'description', content: '掘金是一个帮助开发者成长的社区'}
    ],
  }
}

配置启动端口

第一种

nuxt.config.js :
module.exports = {
  server: {
    port: 8000,
    host: '127.0.0.1'
  }
}

第二种

package.json :
"config": {
  "nuxt": {
    "port": "8000",
    "host": "127.0.0.1"
  }
},

加载外部资源

nuxt.config.js :
module.exports = {
  head: {
    link: [
      { rel: 'stylesheet', href: '//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/styles/atom-one-light.min.css' },
    ],
    script: [
      { src: '//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/highlight.min.js' }
    ]
  }
}

环境变量

nuxt.config.js提供env选项进行配置环境变量。

创建环境变量

nuxt.config.js :
module.exports = {
  env: {
    baseUrl: process.env.NODE_ENV === 'production' ? 'http://test.com' : 'http://127.0.0.1:8000'
  },
}

上配置我们创建了一个 baseUrl 环境变量,通过 process.env.NODE_ENV 判断环境来匹配对应的地址

使用环境变量

我们可以通过以下两种方式来使用 baseUrl 变量:

通过process.env.baseUrl
通过context.env.baseUrl

举个例子, 我们可以利用它来配置 axios 的自定义实例。

/plugins/axios.js:
export default function (context) {
	$axios.defaults.baseURL = process.env.baseUrl
	// 或者 $axios.defaults.baseURL = context.env.baseUrl
	$axios.defaults.timeout = 30000
	$axios.interceptors.request.use(config => {
		return config
	})
	$axios.interceptors.response.use(response => {
		return response.data
	})
}

路由配置

在Nuxt.js中,路由是基于文件结构自动生成,无需配置。自动生成的路由配置可在 .nuxt/router.js 中查看。

axios

安装

Nuxt 已为我们集成好 @nuxtjs/axios,如果你在创建项目时选择了 axios,这步可以忽略。

npm i @nuxtjs/axios --save
配置文件nuxt.config.js可以查看是否有安装 :
module.exports = {
  modules: [
    '@nuxtjs/axios'
  ],
}

SSR使用Axios

服务器端获取并渲染数据, asyncData 方法可以在渲染组件之前异步获取数据,并把获取的数据返回给当前组件。

export default {
  async asyncData(context) {
    let data = await context.app.$axios.get("/test")
    return {
      list: data
    };
  },
  data() {
    return {
      list: []
    }
  }
}

非SSR使用Axios

这种使用方式就和我们平常一样,访问 this 进行调用

export default {
  data() {
    return {
      list: []
    }
  },
  async created() {
    let data = await this.$axios.get("/test")
    this.list = data
  },
}

css预处理器

以 scss 为例子

安装

npm i node-sass sass-loader scss-loader --save--dev

使用

无需配置,模板内直接使用

<style lang="scss" scoped>
.box{
    color: $theme;
}
</style>

全局样式

在编写布局样式时,会有很多相同共用的样式,此时我们可以将这些样式提取出来,需要用到时只需要添加一个类名即可。 定义

新建一个文件global.scss :

.shadow{
  box-shadow: 0 1px 2px 0 rgba(0,0,0,.05);
}
.ellipsis{
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
.main{
  width: 960px;
  margin: 0 auto;
  margin-top: 20px;
}

使用

nuxt.config.js :

module.exports = {
  css: [
    '~/assets/scss/global.scss'
  ],
}