SEO相关知识
简介
搜索引擎优化(Search engine optimization,简称SEO),指为了提升网页在搜索引擎自然搜索结果中(非商业性推广结果)的收录数量以及排序位置而做的优化行为,是为了从搜索引擎中获得更多的免费流量,以及更好的展现形象。
原理
搜素引擎过程非常复杂,简略的表达一下就是:
- 爬行和抓取:搜索引擎通过跟踪链接访问网页、获得页面 HTML 代码并存入数据库。所以提升自己网站的外链很重要,比如跟某些导航网站合作,添加外链
- 数据处理:各个爬虫对HTML的内容进行抓取,对抓取的数据进行文字提取、中文分词、索引等处理,存入数据库
- 排名:用户输入关键词后,排名程序调用索引数据库,计算相关性,然后按照一个的格式生成搜索结果页面。
提升SEO的优化手段
- 砸钱,对搜索引擎(例如百度)等进行商务洽谈,给钱提升SEO排名
- 多使用HTML语义化标签
- 良好的网站结构,最多三层,层次分明,以便爬虫更好的爬取
- 设置良好的 meta ,title 等
- 提交 Sitemap
- 对于前后端分离的项目,做到预渲染
上述优化策略来源于网络与谷歌SEO优化指南
下面我们针对第六点进行实现
预渲染
对于前后端分离的项目我们有两种方法进行预渲染
- 服务端预渲染:对于 vue 我们使用 nuxt 框架进行预渲染,对于 react 我们使用 nest 框架进行预渲染
- SPA项目预渲染:对于 SPA 应用我们采取的是打包预渲染,即在打包时对关键页面进行渲染
下面我们针对 第二点进行 具体的实现
使用 prerender-spa-plugin SPA项目预渲染
项目要求
- 项目路由必须为
history模式 - 项目打包工具必须为 webpack
依赖
npm i prerender-spa-plugin -D
or
yarn add prerender-spa-plugin -D
vue.config.js 设置
// vue.config.js
const path = require('path')
const PrerenderSPAPlugin = require('crd-prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
function resolve (dir) {
return path.join(__dirname, dir)
}
module.exports = {
publicPath: './',
configureWebpack: () => {
if (process.env.NODE_ENV === 'production') {
return {
plugins: [
new PrerenderSPAPlugin({
staticDir: resolve('dist'),
routes: ['/', '/test'], // 你需要预渲染的路由
renderer: new Renderer({
inject: {
_m: 'prerender'
},
// 渲染时显示浏览器窗口,调试时有用
headless: true,
renderAfterDocumentEvent: 'render-event'
})
})
]
}
}
}
}
main.js 设置
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App),
mounted(){
// 当 挂载之后,通过 发布订阅 通知 触发 render-event 这里的 render-event 需要与 vue.config.js 中保持一致
document.dispatchEvent(new Event('render-event'))
},
}).$mount('#app')
原理
prerender-spa-plugin 通过 谷歌提供的无头浏览器puppeteer,提前渲染了页面之后,抓包生成对应路由的 html
优缺点分析
优点
- 利于单页面的SEO
- 代码改动不大,相对于 nuxt 方案来说
- 减少白屏时间
缺点
- 对于 hash 模式的 spa项目改动较大
- 无法适用于新型打包工具 vite
踩坑
[prerender-spa-plugin] Unable to prerender all routes,webpack版本过高导致失败,我们来深入 prerender-spa-plugin 的源码看看到底是什么原因,在 /node_modules/crd-prerender-spa-plugin/index.js 中
// Is there a better way to check versions? Haven't really looked into it.
if (parseInt(process.versions.node.split('.')[0]) >= 8) {
// Native (Node 8+) ES6. (Requires async / await.)
module.exports = require('./es6/index.js')
} else {
// Transpiled through babel to target Node 4+.
module.exports = require('./es5-autogenerated/index.js')
}
由代码我们可以知道,当 Node 版本大于 8的时候,走的就是 ./es6.index.js;小于的话,走的就是./es5-autogenerated/index.js
./es6.index.js全局搜索错误:[prerender-spa-plugin] Unable to prerender all routes 发现:
.then(r => {
const { successCb } = this._options
successCb && successCb()
PrerendererInstance.destroy()
done()
})
.catch(err => {
PrerendererInstance.destroy()
const msg = `[prerender-spa-plugin] Unable to prerender all routes! [Error: ${err}]`
console.error(msg)
compilation.errors.push(new Error(msg))
done()
})
原来是错误被吞掉了,没有导致控制台打印error错误,我们添加 console.error 打印错误,执行yarn build 发现错误原来是:TypeError: compilerFS.mkdirp is not a function
我们查看 compilerFS.mkdirp
PrerenderSPAPlugin.prototype.apply = function (compiler) {
const compilerFS = compiler.outputFileSystem
const mkdirp = function (dir, opts) {
return new Promise((resolve, reject) => {
compilerFS.mkdirp(dir, opts, (err, made) => (err === null ? resolve(made) : reject(err)))
})
}
.....
}
问题就出现在这里, compiler.mkdirp方法已经被 webpack5.x 弃用,改为compiler.mkdir即可,webpack4.x 版本使用则没有问题
下面给出解决方法:
- 替换包
prerender-spa-plugin为crd-prerender-spa-plugin,此包的作者兼容了 webpack5.x
const mkdirp = function (dir, opts) {
return new Promise((resolve, reject) => {
try {
compilerFS.mkdirp(dir, opts, (err, made) => (err === null ? resolve(made) : reject(err)))
} catch (e) {
// mkdirp removed in Webpack 5
compilerFS.mkdir(dir, { ...opts, recursive: true }, (err, made) => (err === null ? resolve(made) : reject(err)))
}
})
}
但是这个方法也有缺点,毕竟是 fork prerender-spa-plugin 的代码,当 prerender-spa-plugin包继续更新时,会不能享受新的特性。
2. 编写脚本 手动变更 mkdirp 为 mkdir
3. webpack 降级处理
异步数据无法渲染:我们发现在预渲染之后,异步数据无法被渲染出来,经过排查之后我们发现,主要是有两个。
- 触发时机:即使是我们在
$nextTick之后再触发renderAfterDocumentEvent,也获取不到异步数据 - 异步请求网络问题,异步数据一般都是通过ajax获取,可能涉及到跨域的问题,接口的cookie鉴权等等。 解决办法:
- 将
renderAfterDocumentEvent触发更换renderAfterTime触发,注意当两者都存在时,renderAfterTime将被忽略。 prerender-spa-plugin包内置了express和http-proxy-middleware包,向外暴露的配置为serve选项,为了解决跨域的问题,我们增加一项serve的proxy配置:
server:{
proxy:{
'/v1/supplier/': {
target:"https://xxx.com/",
changeOrigin:true
},
}
},
根据上方的配置,我们针对此包获取异步渲染数据做一个总结:能进行异步数据渲染,但是缺点比较大,一、由于我们只能使用 renderAfterTime 延时触发,所以接口响应时间与renderAfterTime配合好,但是renderAfterTime过长又会导致打包时间过长;二、由于是在node端进行ajax的请求,所以我们的接口鉴权机制需要讨论(不能使用cookie方式) 顺便提一句,这个包的错误处理方式不是很好,很多错误都需要去看源码debugger才能了解 ==