对于vue、react、angular这类项目而言,seo真的是一大痛点。


为什么seo是spa项目的痛点

网络爬虫在爬取网页内容的时候,需要分析页面内容,主要有以下几点:

  1. 从 meta 标签中读取 keywords 、 description 的内容。
  2. 根据语义化的 html 的标签爬取和分析内容。一个整体都是用 div 标签的网站和正确使用了 html5 标签的效果是不一样的。
  3. 读取 a 标签里的链接,通过 a 标签的链接可以跳转到别的网站。(爬虫是先跳转,还是继续爬内容再跳转,就看算法是广度优先还是深度优先了)
  4. 像 h1 - h6 标签是具有不同程度的强调意义的。 一般将 h1 视为重要内容。同样有强调内容还有 strong 、 em 标签。

爬虫在爬取的过程中,不会去执行js,所以隐藏在js中的跳转也不会获取到。

spa正好就踩到了痛点,只有一个主要的页面(index.html),而且页面里的内容还很少,通常只有 router-view 或者带有 id 的 div 标签。其跳转和业务逻辑的行为都是靠执行js才行的。

据说google已经支持爬取spa。


技术栈

vue v2.0 + vue-router + vuex + webpack v3.6 + prerender-spa-plugin v3.2.1 + vue-meta-info + vue-cli v2

具体步骤

使用 npm 或 yarn 安装 prerender-spa-plugin 和 vue-meta-info 的过程就不描述了。

在webpack中配置 prerender-spa-plugin

配置先弄懂要配置在哪个文件里,配置是否生效。 vue-cli2 的配置文件很多,对这些文件不了解的话,很容易配置错地方。

这个配置只需要在 build 的时候可以生成预渲染好的html,所以应该配置在 build/webpack.prod.conf.js 这个文件里。

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const baseWebpackConfig = require('./webpack.base.conf')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer

const webpackConfig = merge(baseWebpackConfig, {
    plugins: [
        // vue-cli生成的配置中就已有这个了,不要动
        new HtmlWebpackPlugin({
            filename: config.build.index,
            template: 'index.html',
            inject: true,
            minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeAttributeQuotes: true
            },
            chunksSortMode: 'dependency'
        }),
        
        // 在vue-cli生成的文件的基础上,只有下面这个才是我们要配置的
        new PrerenderSPAPlugin({
            // 生成文件的路径,也可以与webpakc打包的一致。
            // 下面这句话非常重要!!!
            // 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。
            staticDir: path.join(__dirname, '../dist'),
            
            // 对应自己的路由文件,比如index有参数,就需要写成 /index/param1。
            routes: ['/', '/index', '/skin', '/slimming', '/exercise', '/alPay', '/wxPay'],
            
            // 这个很重要,如果没有配置这段,也不会进行预编译
            renderer: new Renderer({
                inject: {
                  foo: 'bar'
                },
                headless: false,
                // 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
                renderAfterDocumentEvent: 'render-event'
            })
        })
    ]
})

在 webpack.prod.conf.js 配置完成之后,然后再 main.js 里改成如下所示:

new Vue({
    el: '#app',
    router,
    store,
    render: h => h(App),
    
    /* 这句非常重要,否则预渲染将不会启动 */
    mounted () {
        document.dispatchEvent(new Event('render-event'))
    }
})

如果将上面两个地方配置好了,离成功也就近了。然后运行一下 npm run build 。看一下生成的 dist 的目录里是不是有每个路由名称对应的文件夹。然后找个 目录里 的 index.html 用IDE打开,看文件内容里是否有该文件应该有的内容。有的话,这步就成功了。

在vue中使用 vue-meta-info

vue-meta-info 的使用相对就简单多了,就是vue的一个组件而已。 在 main.js 中写上

import MetaInfo from 'vue-meta-info'

Vue.use(MetaInfo)

在 xxx.vue 文件里写上以下代码。这些代码会转换成网页的 meta 标签里的内容。

export default {
    metaInfo: {
        title: '我是一个title',
        meta: [
            {
                name: 'keywords',
                content: '关键字1,关键字2,关键字3'
            },
            {
                name: 'description',
                content: '这是一段网页的描述'
            }
        ]
    }
}

下面是踩的坑

  1. 这里使用的 prerender-spa-plugin 的版本是 3.2.1 ,在 2.x 的版本中有个写法如下

    var PrerenderSpaPlugin = require('prerender-spa-plugin')
    
    const webpackConfig = merge(baseWebpackConfig, {
        plugins: {
            //配置 prerender-spa-plugin
            new PrerenderSpaPlugin(
                // 生成文件的路径,此处与webpack打包地址一致
                path.join(config.build.assetsRoot), //config.build.assetsRoot为vue cli生成的配置,打包后的文件地址
                // 配置要做预渲染的路由,只支持h5 history方式
                [ '/', '/test']
            )
        }
    })
    

    以上 2.x 的写法,在 3.x 的版本会提示一个警告,让你采用对象的形式

  2. 网上也有些说法,让预渲染方式下的 route 采用 history 模式,经过试验,没有必要!没有必要!没有必要!(真香)。留言中的大佬说的很好,实验了一下,确实还是得采用history模式,否则每个index.html文件的内容都会是一样的。

    route 的 history 模式就是如下代码中配置

    export default new Router({
        mode: 'history',
        routes: [
            // ...
        ]
    })
    

    history 模式下的route,将不会以 hash 的形式展示,也就是说,URL里没有 # 了。

  3. 配置写错了文件,一开始一直写在了 /config/index.js 这个文件里。一启动,没效果。再启动,还是没效果。直到我冷静下来,喝了一口水,思考了一下人生,想了一想,这里是10楼,每层楼高3.5米,重力加速度是9.8,地面是水泥的,撞击地面的时候会发生轻微的弹性形变,然后享年24岁。

    经过一系列的思想斗争,我觉得,肯定是我的配置压根就没有被运行。然后我被我的机智折服了。打开 package.json 文件,看了看 npm run build 指令时执行的js文件的顺序,发现了惊天的秘密。

    原来 /config 目录下的文件,只是给webpack的配置项提供值而已。真正配置webpack插件应该在 /build 下配置。

    因为是要在 npm run build 的时候生成那些html,所以应该配置在 webpack.prod.conf.js 文件里。

  4. 一个习惯引起的坑,在打包的过程中,习惯将 html 和 resources 分开成两个文件夹。一直没想到,多了一层 html 会导致 prerender-spa-plugin 插件卡在预渲染(自动打开一个Chrome,开始编译)。

  5. 用了 vue-cli 构建的项目,在配置之后然后开心的 npm run build 时,满心期待,心情愉悦的等待中。发现,诶,不成功!好气,很气,非常气!

    而且啊,没有任何错误提示,没有任何错误提示,没有任何错误提示。

    然后就这么点东西,花了我三天的时间

其他SPA的SEO方案

除了预渲染,当然还有别的解决方案。实质上方案只有两种,预渲染就是使用的 phantomjs 先运行的。

  1. 采用ssr(服务端渲染)
  2. phantomjs

vue项目提供了ssr的方案,还有集成ssr方案的 nuxt.js 可以使用。不过ssr方案需要在node上调用 renderToString 来渲染。如果不介意在系统结构中增加一层 node 的话,就可以使用ssr的方式,这种方式会改变前后端交互的方式。

总结

总结还是一条一条来吧

  1. 不要以为会搬运代码,就可以当程序员了,得会思考。在发现有问题的时候,先停下尝试的手,把已经可以确定的东西分析清。找到产生问题的原因。就比如第一个坑,找到的很多代码都是采用的非对象的写法,在这个写法里, main.js 里也不需要加上那句 mounted() {// ...} 。这个时候首先应该想到版本是不是会影响配置文件的写法。

    发生问题的时候,确认一下自己的方向是否正确。从发现配置写在 webpack.prod.conf.js 的时候,已经是第三天了。方向对了之后,花了半天就找到了剩下的问题所在。

  2. 代码的心思要多猜,在你看不懂源码,没有那个时间成本去了解深层原理的时候,你需要猜。猜测引起这个问题的原因,但是不要瞎猜。这里就猜测了版本带来的差异,猜测了配置文件没有起作用,猜测了文件夹目录会引起问题。

  3. 学会尝试。预渲染这个确实是文档描述的不够仔细,每个属性的说明只有简单的两句描述。网上的例子,大多和自己的环境不匹配。用 vue-cli 的肯定用的 2.x 的 prerender-spa-plugin。没用 vue-cli 的,配置文件都和自己不一样。只有去尝试更多例子,然后确定哪部分配置才是有效的。

最后,如果这篇文章有说明不仔细或者有纰漏的地方,可以留言告诉我。

Logo

前往低代码交流专区

更多推荐