草庐IT

基于NUXT.JS搭建一款VUE版SSR前端框架(解决SPA应用的SEO优化优化问题)

<a href="http://weibo.com/qdjianghao" target="_blank"><b>北堂墨染 2023-03-28 原文

小仙男·言在前

关于框架:为了解决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.jswebpack开发环境。
  • 框架推荐Node.js版本为v16.15.0,最低版本不得低于12,推荐安装nvmn等node版本管理工具。

3、分支要求

  • 遵循[前端团队git仓库及版本管理规范],即master分支只用于拉取框架代码,xxx_dev为开发分支,xxx_test为开发分支,xxx为生产分支。

3、关于本文档

【二、启动与部署】

# 安装框架以来
$ npm install

# 启动本地开发环境,默认端口号:3000
$ npm run dev

# 编译并在生产环境启动
$ npm run build
$ npm run start

# 将网站打包成静态化页面
$ npm run generate

【三、框架结构】

-- 框架根目录
  -- .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后续声明周期)
  

几点说明:

  1. beforeCreate/created 是Vue的生命周期,但是会在服务端和客户端各执行一次,但这两个钩子,仅供了解,不能用于数据请求。
  2. asyncDatafetch都是Nuxt提供的声明周期,都可用于数据请求。只是写法略有不同(参考后续章节【五、数据请求】)。
  3. @/plugins/all.client.js 并非Nuxt声明周期,是只在客户端运行的插件。但是Nuxt框架去掉了app.vue,此插件的声明周期,近似于原来的app.vue,故暂时用于替代原框架中在App.vue中进行的全局初始化操作(是否恰当暂时不知)。

【五、数据请求】

1. 数据请求钩子

1.1 钩子相关说明

  • asyncDatafetch都是Nuxt提供的声明周期,都可用于数据请求,都会在服务端预请求数据进行组装;
  • asyncData只能在pages级别的页面中调用,在子组件内部不能调用;fetch则可以同时在页面和子组件中调用;
  • 官方建议数据请求均采用asyncData,但为了保持与老框架写法的一致,本框架暂时建议采用fetch(后果未知)
  • fetch请求相比于asyncData的已知缺陷有:
    • ① 数据请求较慢,本框架Demo,从index页进入Detail页,当使用fetch请求时,可明显看到浏览器选项卡的title出现一瞬间undefined
  • 尽管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. 其他注意事项

  • 原则上,所有初始化渲染数据的请求,都要在服务端渲染函数(asyncDatafetch)中进行,极个别无法在服务端渲染的请求,可以在Vue的生命周期(createdmounted)中初始化;
  • 服务端渲染的生命周期(即asyncData/fetch),不能使用任何浏览器专属的对像(如DOM对象),也就是documentwindow,以及window的各种对象和方法,例如setTimeoutsetIntervallocalStoragesessionStorage等;
    有上述需求的初始化逻辑,可以放到createdmounted中初始化。

【六、其他规范与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还有很多,例如:readyerrorrender:beforebuild: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)(未完待续…)其他任何框架问题,详询小仙男

有关基于NUXT.JS搭建一款VUE版SSR前端框架(解决SPA应用的SEO优化优化问题)的更多相关文章

  1. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  2. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  3. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  4. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  5. ruby-on-rails - (Ruby,Rails) 基于角色的身份验证和用户管理...? - 2

    我正在寻找用于Rails的优质管理插件。似乎大多数现有的插件/gem(例如“restful_authentication”、“acts_as_authenticated”)都围绕着self注册等展开。但是,我正在寻找一种功能齐全的基于管理/管理角色的解决方案——但不是简单地附加到另一个非基于角色的解决方案。如果我找不到,我想我会自己动手......只是不想重新发明轮子。 最佳答案 RyanBates最近做了两个关于授权的railscast(注意身份验证和授权之间的区别;身份验证检查用户是否如她所说的那样,授权检查用户是否有权访问资源

  6. ruby-on-rails - Assets 管道损坏 : Not compiling on the fly css and js files - 2

    我开始了一个新的Rails3.2.5项目,Assets管道不再工作了。CSS和Javascript文件不再编译。这是尝试生成Assets时日志的输出:StartedGET"/assets/application.css?body=1"for127.0.0.1at2012-06-1623:59:11-0700Servedasset/application.css-200OK(0ms)[2012-06-1623:59:11]ERRORNoMethodError:undefinedmethod`each'fornil:NilClass/Users/greg/.rbenv/versions/1

  7. ruby-on-rails - Rails - 理解 application.js 和 application.css - 2

    rails新手。只是想了解\assests目录中的这两个文件。例如,application.js文件有如下行://=requirejquery//=requirejquery_ujs//=require_tree.我理解require_tree。只是将所有JS文件添加到当前目录中。根据上下文,我可以看出requirejquery添加了jQuery库。但是它从哪里得到这些jQuery库呢?我没有在我的Assets文件夹中看到任何jquery.js文件——或者直接在我的整个应用程序中没有看到任何jquery.js文件?同样,我正在按照一些说明安装TwitterBootstrap(http:

  8. ruby - 在 Rakefile 中动态生成 Rake 测试任务(基于现有的测试文件) - 2

    我正在根据Rakefile中的现有测试文件动态生成测试任务。假设您有各种以模式命名的单元测试文件test_.rb.所以我正在做的是创建一个以“测试”命名空间内的文件名命名的任务。使用下面的代码,我可以用raketest:调用所有测试require'rake/testtask'task:default=>'test:all'namespace:testdodesc"Runalltests"Rake::TestTask.new(:all)do|t|t.test_files=FileList['test_*.rb']endFileList['test_*.rb'].eachdo|task|n

  9. ruby - 如何使用 Ruby 基于字母数字字符串生成颜色? - 2

    我想要像“嘿那里”这样的东西变成,例如,#316583。我希望将任意长度的字符串“归结”为十六进制颜色。我不知道从哪里开始。我在想,每个字符串的MD5散列都是不同的-但如何将该散列转换为十六进制颜色数字? 最佳答案 你可以只取几位前几位:require'digest/md5'color=Digest::MD5.hexdigest('Mytext')[0..5] 关于ruby-如何使用Ruby基于字母数字字符串生成颜色?,我们在StackOverflow上找到一个类似的问题:

  10. node.js - 如何在 Travis CI 上的一个项目中运行 Node.js 和 Ruby 测试 - 2

    我有一个包含多个组件的存储库,其中大部分是用JavaScript(Node.js)编写的,一个是用Ruby(RubyonRails)编写的。我想要一个.travis.yml文件来触发一个运行每个组件的所有测试的构建。根据thisTravisCIGoogleGroupthread,目前还没有官方支持。我的目录结构是这样的:.├──构建服务器├──核心├──扩展├──网络应用├──流浪文件├──package.json├──.travis.yml└──生成文件我希望能够运行特定版本的Ruby(2.2.2)和Node.js(0.12.2)。我已经有了一个make目标,所以maketest在每

随机推荐