vue单页面项目SEO优化问题(转载)

之前用vue做了一个动态官网项目,后期客户要求seo,百度上之前搜索不到官网地址,后来在项目的入口文件index.html页面加上了,固定的meta标签,加上name名为keywords、description的meta标签。

例子:

复制代码
    <meta charset="utf-8">
    //下面这个meta标签 是ie8的专用标记,指定ie8浏览器器模拟特定版本的ie浏览器渲染方式,下面指定的是edge
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    //在ie、360等浏览器打开时,默认使用极速模式,然后是兼容模式,然后是标准模式
    <meta name="renderer" content="webkit|ie-comp|ie-stand">
    //seo相关,百度收录时,的描述文字
    <meta name="description" content="第十七届xxxxx医学大会">
    //seo相关:百度搜索时的 关键字,就是用那些关键字可以搜索到此网站
    <meta name="keywords" content="中国国际xx医学大会,第十七届,xx,医学,国际">
复制代码

以上做了个简单的seo优化,这个项目有几个官网,但是其中只有一个官网要求seo,也就是在百度能够搜索到,当时为了应急,就写死了,但是,其它的网站也就会受到干扰了,也就是对于一个项目对应几个官网,写死的meta标签做seo是不科学的。

于是下面又来寻找更科学的seo优化方案,下面是一些相关连接,很感谢作者:

https://blog.csdn.net/weixin_41049850/article/details/81945201

文中提到了关于vue单页项目的一些seo优化方案:

首先想vue、react、angular三大前端框架都有spa页面,做起来seo就很麻烦,当然,不是必须要求用这三个框架就必须采用spa的开发模式,但是spa前后端分离的形式真的是太方便了,如果不采用spa方式进行开发,用古老的混合开发自然不会存在seo的问题,但是我们现在大多采用的是spa的形式开发的,

 

 方案1:服务端渲染

上面这个截图是vue作者,为解决seo优化所提出的一个方案,服务端渲染(ssr):官网链接:https://cn.vuejs.org/v2/guide/ssr.html

如果项目刚开始就考虑到seo,采用服务端渲染,那么就用服务端渲染就得了。

但是一般来讲,项目做到后期才会考虑到seo的问题,这时再去搞服务端渲染,相当于重头写项目,非常耗费人力物力。

那么,不采用服务端渲染该如何最大程度解决seo问题呢?

方案2:预渲染

原文链接:https://www.cnblogs.com/kdcg/p/9606302.html

 这比服务端渲染要简单很多,而且可以配合 vue-meta-info 来生成title和meta标签,基本可以满足SEO的需求 

TIPS: 使用预渲染vue-router必须使用history模式

// 安装
npm install prerender-spa-plugin --save-dev

然后在webpack.prod.conf.js里面添加:cli3项目在vue.config.js文件中配置

// 头部引入
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer

在plugins里面添加:

config.js

复制代码
// vue.config.js
const webpack = require('webpack');
const path = require('path');
const CompressionPlugin = require('compression-webpack-plugin');//引入gzip压缩插件
// 预渲染
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
function resolve(dir) {
  return path.join(__dirname, dir)
}


module.exports = {
  // 选项...
    //基本路径
    publicPath: process.env.NODE_ENV === 'production'? '/': '/',//部署服务器的路径 默认在根路径上(影响静态资源的引用路径)
    outputDir: 'customizationWeb',
    assetsDir: 'static',
    productionSourceMap:false,//打包时不要map文件
    // filenameHashing: true,
  devServer: {
      port: 9522,
        proxy:{
          '/qiantai':{
            target:'http://139.xxx.xxx.xx:xxxx',
            changeOrigin: true,
            pathRewrite: {
                "^/qiantai": ''
            }
          }
          
        }
    },
    configureWebpack:()=> {
      if (process.env.NODE_ENV === 'development'){
         return {
          externals: {//如果不想影响开发环境,这里也要配置externals  没用它的就不用在开发环境也配置一份了
            'vue': 'Vue',
            'vuex': 'Vuex',
            // 'vue-router': 'VueRouter',
            'Axios':'axios'
          },
         }
      }else{
        return {
          externals: {
            'vue': 'Vue',
            'vuex': 'Vuex',
            // 'vue-router': 'VueRouter',
            'Axios':'axios'
          },
          plugins: [
            new CompressionPlugin({//gzip压缩配置
              test:/\.js$|\.html$|\.css/,//匹配文件名
              threshold:10240,//对超过10kb的数据进行压缩
              deleteOriginalAssets:false,//是否删除原文件
            }),
            new PrerenderSPAPlugin({
              // 生成文件的路径,也可以与webpakc打包的一致。
              // 下面这句话非常重要!!!
              // 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。
              staticDir: path.join(__dirname, 'customizationWeb'),
      
              // 对应自己的路由文件,比如a有参数,就需要写成 /a/param1。
              routes: ['/'],
      
              // 这个很重要,如果没有配置这段,也不会进行预编译
              renderer: new Renderer({
                inject: {
                  foo: 'bar'
                },
                headless: false,
                // 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
                renderAfterDocumentEvent: 'render-event'
              })
            })
          ]
        }
      }
      
    },
    chainWebpack(config) {
      // set svg-sprite-loader
      config.module
        .rule('svg')
        .exclude.add(resolve('src/icons'))
        .end()
      config.module
        .rule('icons')
        .test(/\.svg$/)
        .include.add(resolve('src/icons'))
        .end()
        .use('svg-sprite-loader')
        .loader('svg-sprite-loader')
        .options({
          symbolId: 'icon-[name]'
        })
        .end() 
        // 打包依赖分析
      // config
      //   .plugin('webpack-bundle-analyzer')
      //   .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
    }
    
}
复制代码

 

绿色背景的代码是 预渲染相关都二代码

在main.js加上这点代码:

复制代码
new Vue({
  router,
  store,
  render: h => h(App),
  mounted: () => document.dispatchEvent(new Event("x-app-rendered")),
  mounted() {
    document.dispatchEvent(new Event('render-event'))
  },
}).$mount("#app");
复制代码

 

这种操作不需要你去添加任何一段代码,直接npm run build,dist文件中就有你写的几个html静态文件。

然后运行npm run build 

 发现打包好的index.html中多了一大堆的代码,部署到nginx上试试看看什么效果

 

 

 

 

下面的报错是之前用另一种方式 vue add 什么什么的全自动做预渲染的,网上可以查查,打包后会出现这些坑,第二次直接按照以上的步骤就没有下面的问题了

和其它人一样,一直打包不成功,有错误:

 

 然后和大佬们的一样安装

npm i puppeteer    太慢
直接用cnpm i puppeteer   就行分分钟下载好

之后再执行npm run build

还是上面的报错:

 

 查一下这个报错:

使用cnpm或者

npm config set puppeteer_download_host=https://npm.taobao.org/mirrors
npm i puppeteer

然后下载成功了,也不慢,

之后打包npm run build

打包成功,index.html中多了好多代码,但是本地运行项目出问题了,后续接着看

搞了半天,感觉对于纯动态的页面,预渲染好像并无卵用。因为动态获取的内容,预渲染是预渲染不出来的,还的看服务端渲染,。。。

这是一位大佬的文章,付费的哦:https://gitbook.cn/books/5d7f8b5b84257d2371a8babb/index.html

想了想,我掏钱了,我的把大佬的文章偷来,给大家免费看,嘻嘻:复制粘贴走起:

-------------------------------------------------------------------------------------------------------------------------------------------------------

前言

如果开发需要复杂交互的 Web 应用,我们多半会选择 SPA;如果要做提供内容资讯的网站,更有利于 SEO、加载速度更快的服务器端渲染(Server-Side Rendering,SSR)自然是大家的首选;那么,如果是一个 CMS 生成纯静态网页呢?

前阵子公司官网升级,我尝试用 Webpack 多页配置,成功的升级了工具链,收获了比较理想的效果。我还写了一篇 Chat 分享这期间的收获:《升级工具链吧!使用 Webpack 开发企业官网》。实际运行一阵子之后,我发现一些新问题:这套技术链对于非前端开发者来说,还真算不上简单,开发环境搭建、不同文件的功用,对后端同事来说仍然显得很复杂。于是,有时候虽然只是小小的文案错误,也要找我来改;或者“加入我们”页面里的岗位信息,也需要熟悉代码的前端负责增删。

于是我就想,能否把各页面以 Vue 组件的形式搭建起来,每个组件可以有“编辑/显示”两个状态,在本地启动开发环境,在浏览器里“编辑”内容,用户可以获得所见即所得的体验。最后以“显示”状态渲染成静态页,继续使用纯静态的方式部署。这就相当于,基于原先的项目开发一个所见即所得 CMS,把原先的静态 HTML(或 Pug)页面改造成 Vue 页面,然后利用 Vue 的 SSR 机制发布成纯静态 HTML。感觉上是个不错的想法。

我以前只是听说过 SSR,一直没有实操经验,有了这个想法之后,就想研究下这个过程,搞个最小用例,将来官网升级 3.0 的时候可以考虑。我本以为只是举手之劳,最多 1,2 个小时就搞定了,没想到最后折腾了大半天。所以我觉得,很有必要再写一篇 Chat,分享这个过程中的收获。

本次分享大纲如下:

  1. 网页形态的历史
  2. Vue CMS 的产品形态
  3. 了解 Nuxt.js
  4. 生成静态页的相关配置
  5. 添加 SEO 关键信息
  6. 注入专有 JS

面向读者

  1. 初中级开发者,熟悉 Vue
  2. 希望了解前端工具链
  3. 希望了解静态化和 Nuxt.js

名词及约定

我假定所有读者都是有一定经验的开发者,大家至少都具备:

  1. 能读懂 JavaScript
  2. 了解 Vue,使用过 Vue 开发项目
  3. 知道 Webpack,了解前端工具链中各工具的角色和基础用法

其它约定:

  1. 为节省时间,范例代码中的 HTML 会以 pug 书写,这种语言很容易阅读,文中也用不到高级语法,应该问题不大。另外,如果你还在写原生 HTML 或 CSS,我建议你尽快切换到新语言。
  2. 范例代码以 ES6+ 为基础,如果你对这些“新”语法不熟悉,附录里有一些资源方便你学习。

名词:

  1. SEO:搜索引擎优化。指改进网页,让搜索引擎更容易理解它的内容,提高页面的排名。
  2. CMS:发布系统。没有特指的话,特指本文中发布静态页的工具。

文中代码的目标环境:

  1. Vue >= 2.6
  2. Vue-router >= 3.1
  3. Nuxt.js >= 2.8

作者介绍

大家好,我叫翟路佳,花名“肉山”,这个名字跟 Dota 没关系,从高中起伴随我到现在。

我热爱编程,喜欢学习,喜欢分享,从业十余年,投入的比较多,学习积累到的也比较多,对前端方方面面都有所了解,希望能与大家分享。

我兴趣爱好比较广泛,尤其喜欢旅游,欢迎大家相互交流。

我目前就职于 OpenResty Inc.,定居广州。

你可以在这里找到我:

或者通过 邮件 联系我。


限于个人能力、知识视野、文字技术不足,文中难免有疏漏差错,如果你有任何疑问,欢迎在任何时间通过任何形式向我提出,我一定尽快答复修改。

再次感谢大家。


网页形态的发展

在正式开始之前,先介绍一下网页形态发展的历史,方便大家理解。

远古时期:纯静态

发明 HTML 的目的是方便大家看论文文献,所以早期的 HTML 都是静态的,放在服务器上,直接映射本地文件目录结构。

当然那个时候也没多少网页,很多服务并没有网页版本,比如论坛,是以 Telnet 形式提供服务的;文件共享,大多使用 FTP。

古典时期:动态网站

所谓“动态网站”,就是根据用户请求,返回合适的内容。或者换用技术的说法,接受请求之后,从数据库中读取数据,生成页面,返回给用户。其实现在没什么网站不是动态网站了,感觉这是个上个世纪的词,比如去京东上搜“动态网站”,能搜到一大堆书,大多有着深刻的时代印记,比如《ASP+Dreamweaver动态网站开发》,简直辣眼睛。

文艺复兴:伪静态

相比于纯静态网站而言,动态网站通常需要更长的响应时间,而且 URL 也经常难以阅读。比如:/post.php?id=157,没人知道它是什么意思。这个时期的搜索引擎也比较弱,面对类似的网页,会降低权重。所以网站运营方要想办法改进 SEO,就用服务器 rewrite 的方式,把形似静态页的路径指向动态路径,提升搜索排名。

伪静态常常和真静态共同工作,这个阶段 CDN 还不够普及,所以生成静态页面并部署到终端服务器的操作也很常见。

近代:SPA

随着各项技术的发展,浏览器在整个互联网产品中的地位越来越重要,承担的职责也越来越多,最终单页应用(Single Page Application,简称 SPA)脱颖而出,成为最流行的产品形态。关于它的好处我就不一一叙说了,相信大家都了解。

而接下来,MVVM 框架横空出世,一统江湖,更是大大提升了 SPA 的开发体验,同时大大降低了入门门槛。然后,类似的产品如雨后春笋搬涌现。

现代:SPA + SSR

SEO 的问题在 SPA 这里更明显:对于一些不思进取的搜索引擎爬虫来说,SPA 应用里什么内容都没有。而不思进取,是很多统治级公司、统治级产品的常态。所以没办法,它们不适配我们,我们就得去适配它们。然后,服务器端渲染(Server-Side Rendering,简称 SSR)就显得非常必要。

SPA 的 SSR 和以前的伪静态不同。伪静态时期,业务逻辑都是通过后端完成的,前端主要用来收集数据;SPA 时期,大部分业务逻辑已经移到前端,后端只负责数据校验和必要的存储。此时 SSR 的目的是让用户尽快看到第一波需要的数据,接下来的操作仍然由 SPA 负责。所以我们就面临两种选择:

  1. 前后端共用一套模板,渲染后的页面拥有全部功能,可以继续与用户交互
  2. 部分页面生成纯静态内容,可以部署在服务器上;其它重交互的部分保持 SPA,独立工作

现代分支:在本地写作的纯静态网站

随着云服务发展,现在很多网站都提供基础的静态网站托管服务,比如 GitHub Pages,我们可以使用一些工具在本地完成静态页面的批量创建工作,然后上传到服务器,拥有自己的网站。

本文的工作实际应该算到这个分支。

Vue CMS 的产品形态

回顾完历史,我们来看看业务需求。

看过我上一次 Chat 《升级工具链吧!使用 Webpack 开发企业官网》的同学应该知道,一切都源自我厂的官网要改版。

我厂官网 v1.0 采用的是前文中“远古时期”的模式,由设计师设计、制作完网页之后,直接把纯静态资源上传到服务器,然后提供服务。这样的好处是:

  1. 简单好操作,对人员要求低
  2. 访问速度快,对机器配置要求低
  3. 对搜索引擎友好

但是也有很多不足:

  1. 纯静态,不利于调整内容
  2. 不利于 i18n,没有多语言版

这些问题,我在 v2.0 时进行了修正。首先,我引入 Gulp 做批处理,增加了“发布”环节,虽然最终还是部署纯静态内容,但是内容可以简单调整,也通过 DOM 查找,实现了 i18n,同步提供中英两个语言。

新版本上线一年半,效果不错,但也有很多不足:

  1. 需要开发者手动管理所有资源,很累
  2. 发布脚本很多很复杂,不便于理解;Gulp 采用 stream 模式,没搞过的新人很难接手
  3. 没有好的开发环境

于是,当整个页面内容都要更新时,我决定升级到 v3.0。这次使用 Webpack 工具链,采用多页面模式,配合 Pug 的可编程特性,更好的实现了最终效果,还可以配合 webpack-dev-server 方便的在本地开发。新版本大大改善了开发效率。新同事打开项目看了看 Webpack 的配置文件,很快就搞明白它是怎么工作的,如果要做工作,应该从哪里入手。

不过正如《矛盾论》所说,当主要矛盾被解决,次要矛盾就会变成新的主要矛盾。此刻,新的问题出现:专业的前端开发能很快摸清项目结构,但对于后端同事来说,未知的新概念还是很多。教会他们理解所有框架、类库、工具没什么意义,如果能够给他们一个简单可操作的 UI,那才能真正提高效率。比如 npm run edit,然后浏览器就打开一个页面。他们按需编辑后,保存,提交仓库,发布。这样的流程才是真正要追求的流程。

所以,最合适的做法就是用 Vue 实现一些编辑器组件,它们有两种形态:编辑,静态。编辑态就不用解释了;我们只要把静态的 HTML 保存下来,生成静态页,即可。

好的,现在需求已经出来了:

  1. 已有一个 Vue 项目
  2. 这个项目大部分功能由 SPA 提供,不需要静态化,不需要预渲染
  3. 这个项目部分路由需要生成静态页
  4. 部署的时候,只需要部署静态页和其引用的资源

看到这个需求,我想大多数关注前端的同学可能跟我一样,第一反应就是:应该找一个 SSR 框架,添加到现有项目中。那么,就选最出名的 Nuxt.js 吧,这个框架的作者还跟 Vue 的作者一起看 NBA 呢。

了解 Nuxt.js

Nuxt.js 的官方网站在此:https://nuxtjs.org,大家可以先看一下。有中文版。不过需要注意,我看的时候,中文翻译并不全,而且不是一半中文一半英文的那种不全,是少了一大部分内容。后来通过 Google 搜索找到了需要的内容,我才发现中文翻译有缺失,浪费我不少时间。所以我建议,大家尽量看英文,或者,先看一遍英文,再看中文。

言归正传,接下来,我们来了解下 Nuxt.js。

定位

相信大家都用 Vue 开发过不止一个项目,各种组件库、工具库也都用过不少。那么 Nuxt.js 在整个 Vue 生态里,大约是个什么定位呢?

读罢官方文档的介绍,我们明白,Nuxt.js,定位于在服务器端提供 UI 渲染的通用框架。它的应用层级高于 Vue,并不打算对 Vue 的功能进行补强,而是利用 Vue 的数据双向绑定,提供更强的 B/S 架构中服务器(Server)一端的开发环境。换句话说,它就是 Node.js 版的 Laravel。而静态页发布,也就是 nuxt generate 功能,只是服务器端渲染衍生出的一个子功能。

坦率的说,这并不是一个好消息。看过前面产品设计的同学应该明白,我想要的,实际上是类似 Vue-Router 或者 Webpack 插件一样的东西,我只要引用它,然后启动一个开关,然后 npm run build,就能生成我需要到的静态页,还有静态需要的各种静态资源。

但是在服务器端实现 Vue 模板渲染太过复杂,为了这样简单的目的,开发 Nuxt.js 这种规模的工具,实在太不划算。所以无论是 Next.js 也好,Nuxt.js 也好,他们的团队都渴望更有挑战性更有成长空间的项目,比如挑战 Laravel。(偷偷说一句,Nuxt.js 的网站上广告真多……)

作为一般用户,我只能去适应他们。

结构

Nuxt.js 里面集成了 Vue 全家桶和 Webpack 全家桶。好消息是这些组件我都常用,应该不会遭遇什么困难。Nuxt.js 团队还提供了脚手架创建工具,方便初始化项目。不过我的项目是现成的,所以作用不大,这个部分跳过不看。

直接在项目根目录安装 Nuxt.js:npm i nuxt -D,完成之后可以使用 nuxt -h 查看 nuxt 命令的各项参数。nuxt 命令和 vue-cli-service 一样,提供启动开发环境、build 等功能。nuxt 使用 nuxt.config.js 作为配置文件,它的地位和 vue.config.js 一样,包含了 Webpack 的默认配置,和一大堆私有配置。

所以,使用 Vue CLI 创建项目时,最好选择把所有工具的配置分散在各自的配置文件里,比如 babel 就是 .babelrc,这样复用起来更加方便。

Nuxt.js 有一套默认目录结构,使用这套目录结构可以最大限度的利用 Nuxt.js 的工作机制,不过对于我们来说,暂时排不上用场,也不需要关注。

nuxt generate

这就是最终我们要使用的命令,其实只能算作 Nuxt.js 的衍生功能。大家可以先看一下它的文档:静态应用部署以及generate 属性配置,后者需要写在 nuxt.config.js 里。

 

使用这个命令,会在指定的文件夹里生成预渲染文件。它的过程大概是这样:

  1. nuxt 启动一个本地服务
  2. 根据配置中的预渲染路径,生成静态页面
  3. 把页面保存成 .html 文件,其中引用的静态资源也都保存下来
  4. 预渲染的页面当中,包含完整的 JS 功能代码

然后我们就可以把这些静态资源整个部署到服务器上。

接下来,我们先来重构下老项目。

重构现有项目

理解了 Nuxt.js 的定位,就不难判断,如果我们想集成 Nuxt.js 到现有项目,必须进行一些重构。

生命周期钩子

服务器端渲染没有挂载(amount)DOM 的过程,所以自然也不支持 beforeMount 和 mounted 钩子。如果你跟我一样,习惯把初始化代码写在 beforeMount 里,那么可能有不少地方要修改。所以,以后如果有对时机要求不严的操作,最好放在 created 甚至 beforeCreated 里。

如果页面需要异步加载数据,就需要用 asyncData 函数。这个函数的用法和钩子函数类似,它需要返回 Promise 实例,真正的模板渲染会在这个 Promise 完成之后才开始。

不过需要注意,正常来说,静态页面里会包含完整的 JS,即包含完整的页面逻辑,所以钩子函数在浏览器里会照常执行。所以要避免同样的代码在 asyncData 和钩子函数里重复运行。

入口

一般来说,如果使用 Vue CLI 创建项目,入口文件是 src/main.js,这个文件会 importsrc/App.vue,并且 mount 到 #app 元素上。

我们当然可以继续使用这个入口,不过考虑到静态页面的依赖跟 CMS 页面的需求不同,我认为重新定义一个入口比较好。在本项目中,我把两者都需要的依赖放在 src/App.vue 里,比如 Bootstrap 的样式;只有 CMS 需要的依赖放在 src/main.js 里,比如 CMS 路由和 Vuex。然后在 nuxt.config.js 里重新配置路由,以 src/App.vue 作为入口。

网页的头信息,包括 <title>,关键词(keywords)、描述(description)对 SEO 非常重要。即使不考虑 SEO,只是方便用户使用浏览器前进后退,显示正确的 <title> 也是应该的。在 Vue 项目中,我们使用 vue-meta 满足这个需求(vue-meta 也是 Nuxt.js 团队开发的)。

但是在 Nuxt.js 生成的静态页面里,我们需要使用 head 属性。它的用法和 vue-meta 基本一致,等下我会在具体配置里详细介绍。

生成静态页的相关配置

接下来讲解配置项。其实配置本身可说的部分不多,之所以铺垫这么久,正是因为我踩了很多坑。原本打算一两个小时搞定的事情,最后花费5、6个小时才摸索出来。后来我想,如果这些知识点我以前就知道,那该多好。所以才有了前面的内容。

好吧,言归正传,最终的配置如下:

复制代码
const path = require('path');
const {
  promises: {
    copyFile,
  },
} = require('fs');
const {DefinePlugin} = require('webpack');
const pkg = require('./package');
const {POST_TABLE} = require('./src/model/Post');

module.exports = {
  // 用来输出 `<head>` 里面的信息
  head: {
    titleTemplate: '%s - Meathill',
    meta: [
      {charset: 'utf-8'},
      {name: 'viewport', content: 'width=device-width, initial-scale=1, user-scalable=no'},
    ],
  },

  // build 配置,其实就是封装起来的 webpack 配置
  build: {
    extend(config) {
      config.resolve.alias['@'] = path.resolve(__dirname, 'src');
    },
    extractCSS: true,
    plugins: [
      new DefinePlugin({
        VERSION: JSON.stringify(pkg.version),
      }),
    ],
  },

  // 重新构建路由。因为静态页对 URL 的需求和 CMS 完全不同,所以这里我只针对静态页添加了简单的路由设定。
  // 前文说过,这里我用 `src/App.vue` 作为入口
  router: {
    extendRoutes(routes, resolve) {
      routes.push({
        name: 'home',
        path: '/',
        component: resolve(__dirname, 'src/App.vue'),
        children: [
          {
            name: 'page.view',
            path: ':path',
            component: resolve(__dirname, 'src/modules/page/page.vue'),
          },
        ],
      });
    },
  },

  // 渲染配置,因为是纯静态页,所以我选择不注入业务逻辑(即 CMS 里的 JS)
  // 那么也就不需要 prefetch
  render: {
    injectScripts: false,
    resourceHints: false,
  },

  // `nuxt generate` 的配置,只有只作用于生成的配置才写在这里,所以其实是远远不够的
  generate: {
    dir: 'static',
    fallback: false,
    async routes() {
      const posts = await getAllPages();
      return result.map(item => `/${item.get('permanentLink')}`);
    },
  },

  // 钩子函数,复制文件,后面会解释
  hooks: {
    generate: {
      async done() {
        const from = path.resolve(__dirname, 'src/footer.js');
        const to = path.resolve(__dirname, 'static/footer.js');
        await copyFile(from, to);
        console.log('[CMS : Nuxt] Static files copied.');
      },
    },
  },
};
复制代码

这里需要介绍一下 generate 属性,它的详细文档在这里:Nuxt.js > Configuration > The generate Property。它里面的信息是专门为 nuxt generate 准备的,但是只配置这个属性是不够的。

它的 routes 属性很重要,里面是所有要渲染的静态页。如我的例子所示,这里也可以使用异步函数,动态获取要渲染的页面列表,然后逐个渲染。Nuxt 还提供了一个优化方案,可以批量渲染页面,而不需要每次都访问数据源,不过我暂时没有用到。

添加 SEO 关键信息

SEO,中文全称“搜索引擎优化”,英文全称 Search Engeine Optimization,简写即 SEO。SEO 虽然名为“搜索引擎优化”,但其实优化的并不是搜索引擎,而是我们自己的页面。目的是提升我们的网页在搜索引擎中的排名。

要知道,流量是很贵的,最便宜的也要好几块/人,那些消费能力强、消费欲望高的用户,一个可能要卖300+。所以对于老板来说,如果能够通过 SEO,让我们的网页排到更靠前的位置,引来更多的用户,那么就等于省去了很大一笔费用。所以显而易见,SEO 对他们来说吸引力很大。

对于我们前端来说,SEO 的需求不可避免,那么如何做呢?这需要我们对搜索引擎的原理有一些理解。我简单介绍一下:

  1. 搜索引擎会抓取所有页面
  2. 然后搜索引擎会分析每个页面,对内容进行分词,对每个词进行打分
  3. 最终得到“所有网页”里“所有内容”的评分
  4. 当用户输入搜索关键词的时候,寻找评分最高的页面倒序显示

如果想了解更详细的搜索引擎知识,我推荐大家阅读吴军博士的《数学之美》,虽然我觉得他的其它作品水平不佳,但这本书科学内容比较多,还是蛮值得看的。

换言之,SEO,就是要让我们的网页针对某个关键词,得分更高。一般来说,有以下工作:

  1. <title> 里应该包含关键词
  2. <meta name="keywords"> 里应该包含关键词
  3. 页面内的标题,即 <h1><h2> 等,应该包含关键词
  4. 图片等元素的 alt 属性,应该包含关键词
  5. 其它正文中,应保持一些关键词密度,比如每一段,每一百个字等,都要出现至少一次关键词

具体到网站生成工作中,(3)(4)(5)通常都由运营/编辑/内容团队负责,我们真正能做的,就是(1)(2),也就是接下来的组件使用。

组件

首先,在页面组件里,添加 head 属性,用来返回头信息。在本次项目中,它必须是个函数,根据页面数据动态返回值:

复制代码
export default {
  head() {
    if (!this.meta) {
      return;
    }
    return {
      title: this.meta.title,
      meta: [
        {
          vmid: 'keywords',
          name: 'keywords',
          content: this.meta.keywords,
        },
        {
          vmid: 'description',
          name: 'description',
          content: this.meta.description,
        },
      ],
      script: [
        {
          body: true,
          defer: true,
          src: 'https://unpkg.com/swiper@4.5.0/dist/js/swiper.min.js',
        },
        {
          body: true,
          defer: true,
          scr: '/footer.js',
        },
      ],
    };
  },
}
复制代码

有些时候,我们会在 nuxt.config.js 里配置默认的 meta 信息,为了避免页面的 meta 信息和默认 meta 信息重复出现,所以要用到 vmid(在组件里) 和 hid(在配置里)。这样同样 id 的头信息就只出现一个,权重当然是页面更高。

接下来 script 的部分,可以通过 body 属性控制 <script> 插入的位置,默认为 false,插入 <head>。这里当然应该放在 </body> 之前。静态网页不需要 Vue 那些很复杂的交互,所以在上一章中,我通过 render 属性把它们去掉了。但是有一些其它交互要添加进来,比如头图切换用 swiper,还有统计代码。所以要插入一个 footer.js 进去。

这里需要注意,Nuxt.js 并不会调用 webpack 去处理这里的 JS,所以我们需要人工控制它们的路径。下一章你会看到,我是直接复制文件到 static 文件夹的,所以它的路径也就写成固定的 /footer.js。如果你有 publicPath 之类的需求,还要自己处理一下哦。

配置文件

配置文件里的内容上一章展示过:

复制代码
module.exports = {
  head: {
    titleTemplate: '%s - Meathill',
    meta: [
      {charset: 'utf-8'},
      {name: 'viewport', content: 'width=device-width, initial-scale=1, user-scalable=no'},
    ],
  },
}
复制代码

好像没什么可说的……我暂时只用到标题模板,比如一个页面标题是“今天晚上吃什么?”,就会渲染成:“今天晚上吃什么? - Meathill”。其它选项大家可以参考 Vue Meta > API > metaInfo properties

渲染静态页的时候,vue-meta 似乎不是必须的;换言之,我一开始用了 vue-meta,没有配 head,也没有输出需要的 meta 信息。

利用钩子注入 JS

Nuxt.js 提供了很多钩子,方便我们在特定的环节进行定制化操作。这些钩子跟不同的操作绑定,接受不同的参数,返回不同的结果,具体钩子列表请参考 Nuxt.js > API > Hooks > List of hooks

在本项目中,我的需求是生成静态页面需要的 JS。这个 JS 包含两个功能:

  1. 初始化 swiper
  2. 启动统计代码

因为功能非常简单,我暂时不打算用 webpack 打包,只想复制到目标文件夹里。启动复制操作的时机并不敏感,因为和主要的生成静态页工作不存在相互依赖的关系。不过 Nuxt.js 默认会清理生成目录,所以我觉得晚一些复制会比较好,最后选择 generate:done 这个钩子(基本是最后才会执行)。代码如下:

复制代码
const {
  promises: {
    copyFile,
  },
} = require('fs');

module.exports = {
  hooks: {
    generate: {
      async done() {
        const from = path.resolve(__dirname, 'src/footer.js');
        const to = path.resolve(__dirname, 'static/footer.js');
        await copyFile(from, to);
        console.log('[CMS : Nuxt] Static files copied.');
      },
    },
  },
}
复制代码

这段代码应该很容易看懂吧,就是一个简单的复制。不过需要注意,copyFile 函数是 v10 之后添加到 Node.js 里的,而预 promise 化的 fs.promises 是 v12 之后添加的。

后记 & 附录

先回顾一下本文的主要观点:

  1. Nuxt.js 的目标是覆盖完整的网站开发场景,这个场景更有前(钱)途
  2. 要达成这个目标,支持 Vue 模板的服务器渲染是必经之路
  3. 所以生成静态页只是 Nuxt.js 的一个衍生功能
  4. 所以在现有项目中集成 Nuxt.js ,渲染静态页的成本比较高,很多文章也提供类似的观点
  5. 但是如果有必要,这仍然是最好操作的方案

接下来,关于技术选型:

  1. 如果是新项目,必须 SSR,那么建议从开始就用 Nuxt.js 创建项目
  2. 如果是老项目,部分页面需要静态化,请参考本文

最后,如果要在现有项目中解成 Nuxt.js,我们应该:

  1. 重构现有项目,一般要重构入口和路由
  2. 新建 nuxt.config.js,添加基础配置
  3. 配置 generate 属性,生成所有要静态化的路径
  4. 如果不需要复杂的交互,可以用 render 属性移除老的 JS,然后手动添加其它的。

希望可以帮大家节省学习尝试踩坑的时间。

 

 

vue单页面项目SEO优化问题 - 古墩古墩 - 博客园
https://www.cnblogs.com/fqh123/p/12436292.html

posted @ 2021-06-01 13:45  时间观测者  阅读(671)  评论(0编辑  收藏  举报