

基于NUXT.JS搭建一款VUE版SSR前端框架(解决SPA应用的SEO优化优化问题) - 北堂墨染
source link: https://www.cnblogs.com/qdjianghao/p/17178646.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

小仙男·言在前
关于框架:为了解决VUE的SPA单页应用对SEO搜索引擎优化不友好的问题,这几天一直在调研各种SSR框架。比如doc.ssr-fc.com/ 和 fmfe.github.io/genesis-do 都是比较不错,且有自己理念和想法的框架。但是对于公司来说技术规范差异太大,团队学习成本比较高,思来想去,还是基于NUXT.JS自己搭建一套SSR框架慢慢完善吧。
关于本文档:本文档是从官网文档中摘录的一些重点内容,以及加入了自己的一些调整和对官网内容的理解和解释。
关于官网:NUXT中文网 特别适合新手学习,文档及案例十分清楚详尽,可以说有手就行。但是,中文网的更新不及时,有些章节(比如fetch钩子中不能使用this)甚至存在明显错误,所以有一定技术水平的宝子,建议直接查看 NUXT英文官网 。
【一、框架概述】
1、框架介绍
SSR
技术(即服务端渲染
技术),区别于原先纯Vue框架的SPA
应用(即单页应用
)。SPA
应用只有一个index.html的入口文件,页面显示的所有内容均靠客户端JS进行渲染,对于搜索引擎(SEO
)优化来说,整个网站只有一个空页面,十分不友好。而服务端渲染
技术,是借助node.js
作为框架服务端,在初次访问一个页面的时候,先在服务端预请求接口,并在服务端组装完成的html页面后,返回给客户端呈现。Nuxt.js
是基于Vue
框架的一款服务端渲染
框架,提供了特有的框架结构和服务端渲染声明周期。
2、开发环境
- 本框架基于
Node.js+Webpack+vue+Nuxt.js
进行开发,提供ElementUI
作为UI框架。开发前需全局安装Node.js
与webpack
开发环境。 - 框架推荐
Node.js
版本为v16.15.0
,最低版本不得低于12
,推荐安装nvm
或n
等node版本管理工具。
3、分支要求
- 遵循[前端团队git仓库及版本管理规范],即
master
分支只用于拉取框架代码,xxx_dev
为开发分支,xxx_test
为开发分支,xxx
为生产分支。
3、关于本文档
- 本文档所述内容,已经是从官网中摘录的【重中之重】,开发前请【详尽】【仔细】【通读】本文档!!!尤其是【五、数据请求】 与 【六-8、重要Q&A】!!!
- 文档描述存在错误的地方,请以【NUXT英文官网】为准。
【二、启动与部署】
# 安装框架以来
$ npm install
# 启动本地开发环境,默认端口号:3000
$ npm run dev
# 编译并在生产环境启动
$ npm run build
$ npm run start
# 将网站打包成静态化页面
$ npm run generate
【三、框架结构】
- 详细目录结构介绍及使用,参照【六、其他规范与Q&A】
-- 框架根目录
-- .nuxt Nuxt运营和编译自动生成
-- dist 执行Nuxt静态化时生成
-- api 全局通用的Api请求函数(非Nuxt提供)
-- assets 静态资源目录,存放全局css、image等
-- components 自定义组件目录,此目录下组件无需引入,按需使用即可
-- layout 布局文件,参考https://www.nuxtjs.cn/guide/views
-- middleware 中间件,类似于路由守卫
-- modules 模块,用于设置全局监听等,参考https://www.nuxtjs.cn/guide/modules
-- pages 页面目录,Nuxt会根据此目录自动生成路由,参考https://www.nuxtjs.cn/guide/routing
-- plugins 插件目录,自定义各种插件,参考https://www.nuxtjs.cn/guide/plugins
> global.js (全局变量与全局方法)
> plugin.js (全局引入第三方组件)
> request.js (全局请求封装)
> filter.js (全局过滤器封装)
> util.js (全局工具函数封装)
> all.client.js(仅在客户端执行插件,暂时替代原app.vue)
-- static 不需要webpack编译的静态文件,一般存放ico等文件
-- store Vue状态树,与原写法有所不同,参考https://www.nuxtjs.cn/guide/vuex-store
-- utils 工具类包 (非Nuxt提供)
.editorconfig
.gitignore
env.js 环境变量配置,分dev、test、pro三种环境
nux.config.js Nuxt的所有配置项,参考https://www.nuxtjs.cn/api/configuration-build
package-lock.json
package.json
README.md 框架使用文档
ReleaseNote.md 版本更新说明
【四、生命周期】
-- Nuxt完整声明周期
【服务端渲染】
-- 全局
nuxtServerInit 第一个:nuxt中第一个运行的生命周期
RouteMiddleware 第二个:中间件,类似于原框架的路由导航守卫
-- 组件
validate 是用来校验url参数符不符合
asyncData Nuxt专属声明周期,可用于数据请求,只有page可用,子组件内部不可用
beforeCreate Vue声明周期,但是服务端会执行(不可用于数据请求,数据请求相关操作会在客户端执行)
created Vue声明周期,但是服务端会执行(同上)
fetch Nuxt专属声明周期,可用于数据请求, page和子组件都可用
【客户端渲染】
-- 全局
* `@/plugins/all.client.js` (并非Nuxt声明周期,是只在客户端运行的插件。此框架中用于暂时替代原框架中在App.vue中进行的全局初始化操作。)
-- 组件
beforeCreate
created
beforeMount
mounted
... (其他Vue后续声明周期)
几点说明:
beforeCreate/created
是Vue的生命周期,但是会在服务端和客户端各执行一次,但这两个钩子,仅供了解,不能用于数据请求。asyncData
和fetch
都是Nuxt提供的声明周期,都可用于数据请求。只是写法略有不同(参考后续章节【五、数据请求】)。@/plugins/all.client.js
并非Nuxt声明周期,是只在客户端运行的插件。但是Nuxt
框架去掉了app.vue
,此插件的声明周期,近似于原来的app.vue
,故暂时用于替代原框架中在App.vue中进行的全局初始化操作(是否恰当暂时不知)。
【五、数据请求】
1. 数据请求钩子
1.1 钩子相关说明
asyncData
和fetch
都是Nuxt提供的声明周期,都可用于数据请求,都会在服务端预请求数据进行组装;asyncData
只能在pages
级别的页面中调用,在子组件内部不能调用;fetch
则可以同时在页面和子组件中调用;- 官方建议数据请求均采用
asyncData
,但为了保持与老框架写法的一致,本框架暂时建议采用fetch
(后果未知) fetch
请求相比于asyncData
的已知缺陷有:- ① 数据请求较慢,本框架Demo,从index页进入Detail页,当使用
fetch
请求时,可明显看到浏览器选项卡的title出现一瞬间undefined
- ① 数据请求较慢,本框架Demo,从index页进入Detail页,当使用
- 尽管
beforeCreate/created
也可以在服务端渲染,但是这两个钩子的数据请求操作只会在客户端执行,非特殊情况,切勿用于页面初始化。
1.2 asyncData
- asyncData 中不能访问this,但是可以在第一参数中,拿到context上下文,使用context.app访问Vue根示例;
- context上下文还包含store、route、params、query等数据,详见context上下文
- asyncData中无法拿到组件实例,不能访问组件实例中的data method等方法。
- 详细介绍:asyncData
- 【请求示例】
// ① 使用return返回的对象,将直接初始化到组件`data`中
async asyncData({app, params}) {
const { code, data } = await app.$get('/policy/findById/'+params.id)
return {detail: data}
},
// ② return一个Promise,将在Promise执行完成后,将数据初始化到组件`data`中
asyncData({app, params}) {
return app.$get('/policy/findById/'+params.id).then(res => {
return {detail: data}
})
},
// ③ 第二个参数为callback回调函数,可直接传入数据,初始化到组件`data`中
asyncData({app, params}, callback) {
app.$get('/policy/findById/'+params.id).then(res => {
callback(null, {detail: data})
})
},
1.3 fetch
- fetch 分两种情况(新版本后支持第二种情况):
- ① 第一个参数接受context上下文,则与asyncData一样,不能访问this和组件实例; (这种情况,也不支持像asyncData一样通过return或者回调函数修改data内容)
- ② 不接受任何参数时,则可以正常访问this。(可以近似的看成created的用法,区别是 必须要使用await 或者return一个primary)
- 详细介绍:fetch英文文档 (中文文档严重延迟,存在错误)
- 【请求示例】
// ① 使用return返回一个Promise
fetch() {
return this.getDetail()
},
// ② 使用await/async
async fetch() {
await this.getDetail()
},
methods: {
// ① 使用await编写methods方法
async getDetail(id){
const { code, data } = await this.$get('/policy/findById/'+this.$route.params.id)
this.detail = data
}
// ② 使用return Promise编写methods方法
getDetail(id){
return this.$get('/policy/findById/'+this.$route.params.id).then(resw => {
this.detail = res.data
})
}
}
2. 数据请求方式
2.1 【框架推荐】 使用vue实例直接调用
- 本框架会将
$request/$get/$post
挂在到vue根示例,建议直接只用this
或上下文context.app
调用 - 【请求示例】
// 以this调用为例,如果是在`asyncData`中,需要使用上下文`context.app`调用
// ① get
this.$get('/policy/findById/'+this.$route.params.id)
// ② post
this.$post('/policy/findAll/',{page:1,size:10,params:{}})
// ③ request
this.$request({
url: '/policy/findAll/',
method: 'post',
data: {page:1,size:10,params:{}}
})
2.2 兼容老框架的api分离式调用
- 本框架推荐使用
五 2.1
的方式调用,但是也兼容了老框架的api分离式调用,用于提取可复用的公共请求
。 - 公共请求的api文件,统一放在
@/api/*.js
管理。 - 【请求示例】
/**
* @/api/index.js
*/
import request from '@/utils/request'
export function getPageList(data) {
return request.post('/policy/findAll', data)
}
/**
* @/pages/index.vue
*/
import { getPageList } from "@/api/index.js"
export default {
fetch() {
return this.getPageList(this.pageDto)
},
methods: {
getPageList(pageDto) {
return getPageList(pageDto).then(res => {
this.pageList = res.data.result
})
}
},
}
3. 其他注意事项
- 原则上,所有初始化渲染数据的请求,都要在服务端渲染函数(
asyncData
或fetch
)中进行,极个别无法在服务端渲染的请求,可以在Vue的生命周期(created
或mounted
)中初始化; - 服务端渲染的生命周期(即
asyncData/fetch
),不能使用任何浏览器专属的对像(如DOM
对象),也就是document
和window
,以及window
的各种对象和方法,例如setTimeout
、setInterval
、localStorage
、sessionStorage
等;
有上述需求的初始化逻辑,可以放到created
或mounted
中初始化。
【六、其他规范与Q&A】
1. 关于pages
- 本框架路由采用
约定式路由
,即不再使用route.js
进行路由声明,而是由框架根据pages
目录自动生成路由,详见路由 - 文件夹或者文件,如果以
_
开头,表示此为动态路由,可以传入不同参数,在组件内容,可以使用上下文或者this.$router取到路由参数;- 例如:
/pages/news/detail/_id.vue
、/pages/news/detail/_id/index.vue
- 访问:
http://domain.com/pages/news/detail/12345
(上述两种写法均为这一路径)
- 例如:
- ① 使用
_id.vue
的写法,表示id
为可选参数,即可以通过http://domain.com/pages/news/detail
访问。如果要对id进行限制或验证,可以在组件内使用validate()
验证; - ② 使用
/_id/index.vue
的写法,表示id
为必选参数,访问http://domain.com/pages/news/detail
会报404。如果只要求id必填,而没有其他格式限制,可以使用此方式。 - ③
validate()
验证示例
// return true表示验证通过,return false表示验证失败 404
validate({ params }) {
return /^\d+$/.test(params.id)
},
2. 关于plugins
- 用于自定义框架所需的各种插件,声明插件后在
nuxt.config.js
中引入插件即可,类似于原框架main.js
相关功能。详见插件 - 框架已有的插件包(具体用户参照各插件的
顶部注释
):plugin.js
用于全局引入各种npm包;global.js
用于声明全局变量与全局方法;request.js
实现了全局请求封装(对应@/utils/request.js
);filter.js
实现了全局请求封装(对应@/utils/filter.js
);util.js
实现了全局请求封装(对应@/utils/util.js
);all.client.js
只在客户端引入,用于替代原框架中app.vue
中的各种初始化操作;
- 其他插件可根据需要自行定义,
*.js
表示服务端客户端均导入;*.client.js
表示仅在客户端导入;*.server.js
表示只在服务端导入;
3. 关于layout
- 用于定义框架中的各种布局文件,可根据需要自行定义,详见布局与视图
- 默认视图为
default.vue
,默认所有页面都将调用;error.vue
是错误视图,当页面出现问题时,自动调用; - 其他视图,可根据需要自行定义,并在组件内部声明引用。
- 【组件调用示例】
export default {
// 需要调用的视图名称,不写默认调用default.vue
layout: 'onlyBody',
data(){
return {}
}
}
4. 关于components
- 用于定义框架中的各种自定义组件,可根据需要自行定义。
- 自定义组件中的数据,一般应从页面传入,如果需要再组件内部获取数据,应该使用
fetch
(子组件中不支持asyncData
)。 components
中声明的各种组件,在使用时,无需import
导入。直接使用组件名按需调用即可。- 【使用示例】
<template>
<div>
// Header组件
<Header />
</div>
</template>
5. 关于store
store
文件夹为Nuxt提供用于定义Vuex状态树的文件夹,详细文档参照:Vuex状态树。- 此文件夹下面的xxx.js,分别表示一个模块,例如
index.js
对应$store.state.xxx
,而user.js
对应$store.state.user.xxx
- 本框架中
store
中模块的定义与普通Vue框架大体相同,只是Nuxt框架会自动引用Vuex并加载到构建配置重,无需我们自己new Vuex()
- 【使用示例】
/**
* 【注意区别】
* state mutations action不再是包裹在一个对象中,并在new Vuex()的时候传入。 而是分别作为单独模块使用export导出即可。
*/
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state) {
state.counter++
}
}
6. 关于middleware
middleware
是框架中用于声明中间件的文件夹,声明后在nuxt.config.js
中配置中间件即可,详细文档参照:中间件。@/middleware/router.js
为已经升级声明好的路由守卫中间件
,可替代原框架中router.beforeEach
中的路由守卫功能;
7. 关于modules
- 用于自定义模块的文件夹,可以在模块中对Nuxt启动部署的各种声明周期设置监听,详细文档参照:模块。
@/modules/generator.ts
实现了一个对静态化结束generate:done
时进行监听并处理的示例。
const generator: any = function () {
this.nuxt.hook('generate:done', (context: any) => {
// TODO samething
})
}
export default generator
- 类似
this.nuxt.hook('generate:done',() => {})
的Nuxt框架hooks
还有很多,例如:ready
、error
、render:before
、build:compile
等等……详细参见INTERNALS
8. 其他Q&A
1)每个页面,必须使用head
设置title
,必要时还需在详情页设置description
。(!!!切记!!!)
export default {
head() {
return {
// title必须设置!!! 列表可以直接写“xxx列表”,详情页等有不同标题的,要用新闻标题、商品标题等作为title前缀。
title: this.detail.title + '_新闻详情',
meta: [
// 详情页,需要设置不同的description。 this.$getDesc 为全局封装的从富文本中截取100字符的description
{ hid: 'description', name: 'description', content: this.$getDesc(this.detail.details) },
],
}
}
}
2)pages
目录中的层级结构,务必按照功能梳理清楚,比如“news(新闻)”
的列表、详情都要在一个文件夹中。
(!!!目录结构一旦确定,原则上不可再调整!!!)
3)框架中的其他重要文件之【CSS
篇】!!
- 框架各种
css
文件,位于@/assets/css/
中。框架推荐使用scss
语言,使用"sass": "~1.32.13"
进行编译; common.scss
为全局公共CSS,请将全局样式表声明于此。或自行定义CSS文件,并在此文件中import
导入;font.scss
用于定义本框架各种字体、图标库等;variables.scss
声明了框架的各种全局Scss变量,可以在所有页面使用。- 注意:全局主题色,请用
$mainColor
表示,不要在各自文件中自行声明!
- 注意:全局主题色,请用
element-variables.scss
是ElementUI的主题声明文件,如需全局调整ElementUI的配色,请在此调整;
4)(未完待续…)其他任何框架问题,详询小仙男
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK