menu Chancel's Blog
rss_feed lightbulb_outline

前后端分离(Vue-cli)博客SEO优化

warning 这篇文章距离上次更新于657天前,文中部分信息可能已失效,请自行甄别无效内容。

博客网站以及一些扩展页面都是基于Vue全家桶开发的,后端直接使用Flask作为API提供数据,这段时间发现了低版本浏览器访问首页白屏以及百度收录的网站无法正确显示等问题

SEO

SEO全称搜索引擎优化(search engine optimization)

搜索引擎优化是一种透过了解搜索引擎的运作规则来调整网站,以及提高目的网站在有关搜索引擎内排名的方式。由于不少研究发现,搜索引擎的用户往往只会留意搜索结果最前面的几个条目,所以不少网站都希望透过各种形式来影响搜索引擎的排序,让自己的网站可以有优秀的搜索排名。当中尤以各种依靠广告维生的网站为甚。

从现实例子来看影响搜索引擎收录你网站的根据主要有如下几个因子

  • 站点年龄
  • 内容年龄
  • 关键字(meta tag),注意不是越多越好,太多关键字反而适得其反会导致搜索引擎的“反感”
  • 外链质量
  • 专业术语
  • 站点路径结构深度
  • 从第三方数据分享协议中收集的网格数据(检测站点流量的统计程序供应商)
  • 子网的内容质量
  • 文件增加或改动的频率
  • 合适的robots.txt文件
  • cloaking行为(针对不同用户显示不同内容,可能被认为是需要登录的网页从而不进入收录)
  • 不安全的内容
  • HTML的代码质量
  • 搜索到网站之后用户是否点击该网站
  • ……

从上面我们可以看到影响SEO的因素非常多且很多不单纯是技术问题,这里不讨论非技术问题,可以参考上面的列表对非技术问题做相关处理,技术问题上主要是当前搜索引擎并不能很好地识别”SPA应用“

SPA

spa是放松保养疗法,中文也称水...这段划掉

SPA应用也称单页应用,single-page application(缩写SPA)

单页应用是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,而非传统的从服务器重新加载整个新页面。这种方法避免了页面之间切换打断用户体验,使应用程序更像一个桌面应用程序。在单页应用中,所有必要的代码(HTML、JavaScript和CSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面。尽管可以用位置散列或HTML5历史API来提供应用程序中单独逻辑页面的感知和导航能力,但页面在过程中的任何时间点都不会重新加载,也不会将控制转移到其他页面。与单页应用的交互通常涉及到与网页服务器后端的动态通信。

这是2003年就讨论过的概念,但国内直到2010年稍晚之后才逐渐兴起这个话题,目前支持单页应用的前端框架已经非常多了,主要有如下几个

  • AngularJS
  • Ember.js
  • Meteor.js
  • Aurelia
  • Vue.js
  • React
  • Fulcro

他们所采用的通信技术基本都是Ajax/Websocket,通信所采用的数据格式通常是Json/XML,即服务端的MVC下放到了浏览器上执行,这样做会带来很多优点与弊端,着重说说弊端

  • 浏览器的历史记录:根据SPA模型的定义,狭义上理解它只有”单个页面“,这打破了浏览器原本Shel所设计的”前进/后退"功能,当用户按下后退时,会引发不可知的结果,传统的解决方案是不断更改浏览器网址的散列片段标识符,做到每一个模型一个标识符,从而达到用户按下后退时候可以保证不被打断,这种方案可以通过JavaScript来实现。后来随着HTML5规范引入了pushState和replaceState来提供代码对实际网址和浏览器历史的访问各大框架逐渐摒弃了传统的Hash跳转方案,但这样也引入低版本的浏览器访问会出现问题。
  • 初次加载速度:诚然单页应用可以提供强大的类似本地应用的体验,但也导致了初次加载缓慢的问题,而传统的加载方案秩序获取服务端已经渲染完毕的HTML代码,显然初次加载速度是SPA应用的劣势
  • SEO问题:由于一些流行的网络搜索引擎的爬虫缺乏JavaScript执行能力,搜索引擎最优化(SEO)已成为面向公众的网站采用单页应用模型必须面对的一个问题

SPA的SEO优化方案

解决SPA的SEO问题目前而言只有一个思路,那就是 判断爬虫行为并返回给渲染完成的网页,也就是当识别到爬虫行为的时候将SPA转换为传统方案 目前业界的解决方案也的确是沿着这种思路进行解决的,如果你采用的是VUE/AngularJS/React方案基本都是通用的 由于我的应用的后端Flask+前端Vue全家桶的,所以这里着重看看VUE的解决方案

SSR方案

后端直接渲染呈现给用户的最终HTML页面,关于SSR方案可以参考Vue.js 服务器端渲染指南

SSR方案可以有效减少网络请求速度,提升弱网环境下的体验(弱网环境下减少HTTP请求能减少大量等待时间)同时也兼顾了更好的SEO,但缺点一样很明显,需要引入NodeJS做服务端解析,引起更大的服务端负载,以及更高的维护成本,在并发量大的情况下也会面临普通应用所必须解决的诸多问题,私认为除非 为了解决低版本兼容与弱网环境问题否则单单为了SEO引入SSR并不值得

如果为了SEO引入SSR,那为什么还要做单页应用呢?VUE本身就是低侵入性的JavaScript框架,这不自相矛盾,该方案Pass

Nuxt.js方案

Nuxt.js是一个基于Vue.js的应用框架,对客户端/服务端基础架构进行抽象,Nuxt.js关注的是应用的UI渲染,其实就是VUESSR的集成方案,同样依赖于NodeJS

关于Nuxt.js方案可以参考关于 Nuxt.js

总体上来看Nexu.js方案性价比也是非常低,在后端已然做完的情况下是无法更换后端框架与语言的,更 适合于项目伊始就采用的技术框架,这样相对性价比就很高了,该方案一样也pass

Puppeteer爬虫方案

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

Puppteer是一个NodeJS的工具库,提供一系列基于Devtools协议的高级API来控制Chrome/Chromium,Puppteer默认没有界面运行,但她可以通过配置运行在完整(有界面)的Chrome/Chromium上

从SEO的原理考虑,其实就是因为部分搜索引擎不执行javascript从而导致页面是无意义的(一堆Meta标签),那么我们只需要返回有意义的HTML就行了,服务端渲染的思路显然除了SSR外,爬虫也可以解决这个问题,这个时候我们可以利用Puppeteer方案, Puppeteer比起其他方案的优点

  • Google Chrome出品
  • 无界面运行(服务器大部分没界面)
  • 稳定跟进Chrome内核版本(phantomJs多久没更新了?)

这个展开尝试一下又是一篇文章,这里仅仅提供这个思路,具体的可以参考下下面这些资料

GoogleChrome/puppeteer - github.com Getting Started with Headless Chrome

PrerenderSpaPlugin预渲染方案

如果你的SPA部分页面是数据变动不大,例如/about /contact等少数营销页面的SEO,那么预渲染就非常适合你的情况了

预渲染的工作原理是 在使用webpack打包VUE项目的时候简单地生成针对特定路由的静态HTML页面,也就是在生成的时候将页面数据拉一遍并保存好静态页面,自动添加到VUE项目内部结构去,这样每一次打包都会生成一份新的静态页面。

即:用户请求静态页面的时候会获取到一份事先准备好的静态页面,然后用户自己会再次获取到新的数据覆盖旧的数据

这个方案相对实时性来说可以接受,同时也兼顾了SEO优化,对于 博客类网站也还是比较合适的,但对某些实时性要求很高的网站可能就不太合适了(比赛直播之类的),并且方案本身不会破坏当前后端分离的体系

该方案的原理其实也是Puppeteer原理

最终方案 - PrerenderSpaPlugin预渲染方案

个人博客类网站变动不大,也不存在太多复杂的路由,显然PrerenderSpaPlugin是最合适的方案,

  1. 切换到我们的项目目录,安装prerender-spa-plugin以及依赖项

    npm install prerender-spa-plugin --save-dev
    npm install puppeteer --save-dev
  2. 修改项目中关于 build/webpack.prod.conf.js文件,添加如下sample配置(复杂配置参考prerender-spa-plugin - github)

    ...
     const PrerenderSpaPlugin = require('prerender-spa-plugin')
     const Renderer = PrerenderSpaPlugin.PuppeteerRenderer;
     ...
     const webpackConfig = merge(baseWebpackConfig, {
       module: {
           ...
       },
       plugins: [
         ...
    
         // PerenderSpaPlugin - 预渲染
         new PrerenderSpaPlugin({
           // 输出目录
           staticDir: path.join(__dirname, "../dist"),
         // 需要预渲染的路由
           routes: [
             "/",
             "/books",
           ],
           // 渲染配置
           renderer: new Renderer({
             inject: {},
             // 等待页面加载10秒之后采集
             renderAfterTime: 10000
           })
         })
       ]
    
    • routes:你需要实现预渲染的路由
    • renderer:预渲染的三种方式(renderAfterDocumentEvent,renderAfterTime,renderAfterElementExists)
      • renderAfterDocumentEvent:等待某个事件
      • renderAfterTime:等待N秒之后
      • renderAfterElementExists:等待某个节点出现
  3. 验证是否渲染成功

    • 方法1. 查看dist文件夹中是否生成对应路由的文件夹
    • 方法2. 使用chrome devtools查看Perview是否已经正常加载

项目地址/文档可以参考:prerender-spa-plugin - github.com

总结

项目伊始可以考虑使用SSR方案,Nuxt.js是个不错考虑方案 如果不想替换已经写完的后端应用,显然引入预渲染是最好的方案,另外有兴趣也可以参考Allows your Javascript website to be crawled perfectly by search engines -PRERENDER

下次还是试试有意思的Puppeteer方案吧

博文目录

[[replyMessage== null?"发表评论":"@" + replyMessage.m_author]]

account_circle
email
web_asset
textsms

评论列表([[messageList.data.items.length]])

[[messageItem.m_author]] [[messageItem.m_author]]
[[messageItem.create_time]]
[[messageItem.m_environ.browser]] [[messageItem.m_environ.os]] [[messageItem.m_environ.device]]
[[subMessage.m_author]] [[subMessage.m_author]] @ [[subMessage.parent_message.m_author]] [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[subMessage.m_environ.browser]] [[subMessage.m_environ.os]] [[subMessage.m_environ.device]]