Vue 开辟一个单页面运用,置信许多前端工程师都已学会了,然则单页面运用有一个致命的瑕玷,就是 SEO 极不友爱。除非,vue 能在服务端衬着(ssr)并直接返回已衬着好的页面,而并不是只是一个纯真的 <div id="app"></div>
。
Nuxt.js 就是一个极简的 vue 版的 ssr 框架。基于它,我们可以疾速开辟一个基于 vue 的 ssr 单页面运用。
装置
Nuxt.js 官方供应了一个模板,可以运用 vue-cli 直接装置。
$ vue init nuxt-community/starter-template <project-name>
目次构造
.
├── README.md
├── assets
├── components
├── layouts
├── middleware
├── node_modules
├── nuxt.config.js
├── package.json
├── pages
├── plugins
├── static
├── store
└── yarn.lock
个中:
- assets: 资本文件。安排须要经由 webpack 打包处置惩罚的资本文件,如 scss,图片,字体等。
- components: 组件。这里寄存在页面中,可以复用的组件。
- layouts: 规划。页面都须要有一个规划,默以为 default。它划定了一个页面怎样规划页面。一切页面都邑加载在规划页面中的
<nuxt />
标签中。假如须要在平常页面中运用下级路由,则须要在页面中增加<nuxt-child />
。该目次名为Nuxt.js保存的,不可变动。 - middleware: 中间件。寄存中间件。可以在页面中挪用:
middleware: 'middlewareName'
。 pages: 页面。一个 vue 文件即为一个页面。index.vue 为根页面。
- 若须要二级页面,则增加文件夹即可。
- 假如页面的称号相似于
_id.vue
(以_
开首),则为动态路由页面,_
后为婚配的变量(params)。 - 若变量是必需的,则在文件夹下竖立空文件
index.vue
。更多的设置请移步至 官网 。
- plugin: 插件。用于构造那些须要在
根vue.js运用
实例化之前须要运转的 Javascript 插件。须要注重的是,在任何 Vue 组件的生命周期内, 只要beforeCreate
和created
这两个钩子要领会在 客户端和服务端均被挪用。其他钩子要领仅在客户端被挪用。 - static: 静态文件。安排不须要经由 webpack 打包的静态资本。如一些 js, css 库。
- store: 状况治理。详细运用请移步至 官网。
- nuxt.config.js:
nuxt.config.js
文件用于构造Nuxt.js 运用的个性化设置,以便掩盖默许设置。详细设置请移步至 官网。
Nuxt 特有函数
起首,相识一下在 nuxt 的页面中独占的函数/变量:
asyncData(context)
asyncData
要领使得你可以在衬着组件之前异步猎取数据。该要领在服务端中实行的,所以,要求数据时,不存在跨域题目。返回的数据将与 data()
返回的数据举行兼并。由于asyncData
要领是在组件 初始化 前被挪用的,所以在要领内是没有办法经由历程 this
来援用组件的实例对象。
context
变量的可用属性一览:
属性字段 | 范例 | 可用 | 形貌 |
---|---|---|---|
isClient | Boolean | 客户端 & 服务端 | 是不是来自客户端衬着 |
isServer | Boolean | 客户端 & 服务端 | 是不是来自服务端衬着 |
isDev | Boolean | 客户端 & 服务端 | 是不是是开辟(dev) 情势,在临盆环境的数据缓存中用到 |
route | vue-router 路由 | 客户端 & 服务端 | vue-router 路由实例。 |
store | vuex 数据流 | 客户端 & 服务端 | Vuex.Store 实例。只要vuex 数据流存在相干设置时可用。 |
env | Object | 客户端 & 服务端 | nuxt.config.js 中设置的环境变量, 见 环境变量 api |
params | Object | 客户端 & 服务端 | route.params 的别号 |
query | Object | 客户端 & 服务端 | route.query 的别号 |
req | http.Request | 服务端 | Node.js API 的 Request 对象。假如 nuxt 以中间件情势运用的话,这个对象就依据你所运用的框架而定。nuxt generate 不可用。 |
res | http.Response | 服务端 | Node.js API 的 Response 对象。假如 nuxt 以中间件情势运用的话,这个对象就依据你所运用的框架而定。nuxt generate 不可用。 |
redirect | Function | 客户端 & 服务端 | 用这个要领重定向用户要求到另一个路由。状况码在服务端被运用,默许 302。redirect([status,] path [, query]) |
error | Function | 客户端 & 服务端 | 用这个要领展现毛病页:error(params) 。params 参数应当包括 statusCode 和 message 字段。 |
fetch(context)
fetch 要领用于在衬着页眼前添补运用的状况树(store)数据, 与 asyncData 要领相似,差别的是它不会设置组件的数据。为了让猎取历程可以异步,你须要返回一个 Promise,Nuxt.js 会等这个 promise 完成后再衬着组件。
fetch 会在组件每次加载前被挪用(在服务端或切换至目的路由之前)。
head
Nuxt.js 运用了 vue-meta
更新运用的 头部标签(Head)
和 html 属性
。
用于更新 头部信息。如 title,descripe 等。在 head
要领里可经由历程 this
关键字来猎取组件的数据。
layout
指定该页面运用哪一个规划文件。默许值为 default
。
middleware
须要实行的中间件,如鉴权的 auth
等。
transition
指定页面切换时的动画结果。支撑传入 String
, Object
, Function
。详细设置请移步至 官网 。
validate
Nuxt.js 可以让你在动态路由对应的页面组件中设置一个校验要领用于校验动态路由参数的有用性。
返回 true
申明路由有用,则进入路由页面。返回不是 true
则显现 404 页面。
Begin Coding
前置事情
API
在这里,我们运用 CNode API 举行开辟 Demo.
axios
要求数据,我们运用 Nuxt 官方供应的 @nuxtjs/axios 装置后,在 nuxt.config.js 中加上:
export default {
...
modules: [
'@nuxtjs/axios'
],
axios: {
baseURL: 'https://cnodejs.org/api/v1',
// or other axios configs.
}
...
}
就可以在页面中经由历程 this.$axios.$get
来猎取数据,不须要在每一个页面都零丁引入 axios.
scss
须要先装置 sass-loader 和 node-sass
$ yarn add sass-loader node-sass --dev
假如须要在项目中全局运用某个 scss 文件(如 mixins, vars 等),须要借助 sass-resources-loader : yarn add sass-resources-loader —dev
, 还须要在 nuxt.config.js 的 build 设置中调解导出的 loader 设置:
export default {
...
build: {
extend(config, { isDev, isClient }) {
const sassResourcesLoader = {
loader: 'sass-resources-loader',
options: {
resources: [
// 填写须要全局注入 scss 的文件。引入后,一切页面均有用。
'assets/styles/mixins.scss'
]
}
}
// 修正 scss sass 援用的 loader。
config.module.rules.forEach((rule) => {
if (rule.test.toString() === '/\\.vue$/') {
rule.options.loaders.sass.push(sassResourcesLoader)
rule.options.loaders.scss.push(sassResourcesLoader)
}
if (['/\\.sass$/', '/\\.scss$/'].indexOf(rule.test.toString()) !== -1) {
rule.use.push(sassResourcesLoader)
}
})
}
}
...
}
首页
首页平常只须要简朴的猎取首页数据并衬着即可。
重要 代码:
asyncData({app, query}) {
console.log(query)
// 依据不必的标签猎取差别的数据,末了返回话题列表。
return app.$axios.$get(`topics?tab=${query.tab || ''}`).then(res => {
// console.log(res)
// console.log(JSON.parse(res))
return {list: res.data}
})
}
当进入首页时,该函数会被实行, nuxt 会比及猎取数据后再和组件的 data 兼并,进而衬着数据。在模板中,可以直接运用 list 变量猎取数据。
<div class="card fluid topic" v-for="topic in list" :key="topic.id" >
<div class="section">
<h3><nuxt-link :to="{name: 'topic-id', params: {id: topic.id}}" class="topic-title">{{topic.title}}</nuxt-link></h3>
<p class="topic-info">
<mark v-if="topic.top" class="tertiary">英华</mark>
<mark v-else>{{tabsObj[topic.tab]}}</mark>
<span class="avatar">
<img :src="topic.author.avatar_url" alt="">
</span>
<span class="username">
{{topic.author.loginname}}
</span>
</p>
</div>
</div>
在这里说起一下, <nuxt-link />
和 <a />
的区分是: nuxt-link
走的是 vue-router 的路由,即网页已为单页面,而且浏览器不会重定向。而 a
标签走的是 window.location.href
,每一次点击 a
标签后的页面,都邑举行一次服务端衬着,和平常的 PHP 夹杂开辟没有太大的区分。
在这里运用了 nuxt-link
是由于 CNode 的 API 不存在跨域题目,因而可以作为一个单页面运用,体验更好。
由于列表页数据范例有多种,该页面能够会被复用,所以当路由对象发生变化时,须要从新猎取数据,这时刻可以监听路由的变化以做出相应:
watch: {
'$route': function() {
console.log('$route has changed.')
this.getData()
}
}
设置 seo 优化(这里只是纯真的复制罢了,demo 运用,侵删):
head() {
return {
title: '首页' + (this.$route.query.tab ? `- ${this.tabsObj[this.$route.query.tab]}` : ''),
meta: [{
hid: 'description',
name: 'description',
content: 'CNode:Node.js专业中文社区'
}]
}
}
话题概况
一样的,运用 asyncData
函数举行猎取数据,再衬着页面。
asyncData({app, params}) {
console.log(params)
return app.$axios.$get('topic/' + params.id).then(res => {
// let data = res.data instanceof String ? JSON.parse(res.data) : res.data
let data = res.data
// console.log(res)
// let div = document.createElement('div')
// div.innerHTML = res.data.data.content
// res.data.summary = div.innerText.substr(0, 120)
data.summary = data.content.replace(/<[^>]+>/g,"").substr(0, 120).replace(/\s+/g, '')
return {detail: data}
}).catch(err => {
console.log('axios.get failed.')
console.error(err)
})
}
在这里,踩过坑。想运用 div 的 innerText 来过滤掉正文中的 HTML 标签,然则,假如用户是直接进入这个页面的时刻,实行 asyncData
时,document
对象是不存在的,从而会报错。也就是说,当 asyncData
在服务端实行时,是没有 document
和 window
对象的,请人人注重一下。
作为一个社区,seo 尤为重要,倘使每一个页面都须要写一大堆的 head 对象,就会显得特别的烦琐。所以可以借助 nuxt 的 plugin 机制,将其封装成一个函数,并注入到每一个页面当中:
// plugins/global.js
import Vue from 'vue'
Vue.mixin({
methods: {
// 必传 题目,形貌。其他的 meta 标签经由历程 payload 注入,个中,每一个 meta 的 hid 须如果唯一的。
$seo(title, content, payload = []) {
return {
title,
meta: [{
hid: 'description',
name: 'description',
content
}].concat(payload)
}
}
}
})
在 nuxt.config.js 中加上:
export default {
plugins: [
'~plugins/global.js'
]
}
如许,只须要在页面的 head
的函数中,返回该函数即可:
head() {
return this.$seo(this.detail.title, this.detail.summary)
}
可见,概况页已胜利的设置了部份 seo 的标签。
以上是 Nuxt 的一些基本设置及运用。
我再去研究一下, fetch 和 store 的连系,将该 demo 继承完美。