SSR到SEO

1,287 阅读13分钟

好久没更新 对关注我的小伙伴说声抱歉!这篇文章灵感来自做公司官网的项目。

做官网,当时只知道Nuxt是Vue的SSR框架,也知道Vue的项目要求SEO优化需要用到SSR技术,于是就开发了。在叙事前还是列一个提纲,可以让大家有选择性的阅读

  1. SSR原理应用
  2. SEO优化处理

SSR原理应用

SSR(server side render)服务器端渲染,前端开发人员都知道,但具体的概念及如何实现整个配置很多人还比较模糊。本文从以下几点阐述

  • 为什么要用SSR,它与浏览器渲染的区别以及都做了些什么
  • 如何配置一个可以实现SSR的项目

为什么要用SSR,它与浏览器渲染的区别以及都做了些什么

SSR概念

将一个Vue组件在服务器端渲染为HTML字符串并发送到浏览器,最后将这些静态标记“激活”为可交互应用的过程被称为 服务器端渲染。

在传统的web渲染技术asp jsp等都是客户端向服务器发送请求,服务器经过查数据库等操作,拼出HTML,返回给客户端并进行浏览器渲染。
而现在的SPA 例如Vue框架,当客户端发出请求时,服务器端只返回一个HTML的结构:

  1. 经过执行Vue.js才能将配置的数据渲染
  2. 执行过程中有些代码又进行了Axois异步请求,又向服务器发送请求
  3. 服务器再进行相应返回数据继续渲染。

所以对于SPA项目,1.首屏渲染速度慢 2.对于SEO不友好

screenshot-20220302-182937.pngSSR是对传统的web渲染技术和SPA取了一个折中方案:

  • 在服务器上不用asp生成html,而是用Vue模板,通过解析生成html
  • 请求接口工作也在服务器端完成最终拼成html
  • node做一个中间层,浏览器请求服务器,node在中间进行模版拼接生成html
  • 把解析好的html发送给客户端,直接进行首屏渲染
  • 通过客户端相关代码转换为SPA
  • 但开发会受限,编码时注意(mounted钩子,对缓存操作,在window上获取的api
  • 服务器负载会变大,每次请求都会创建一个vue的实例。
  • 构建不是要求变多,开发难度会大,需要懂node服务语言进行模版渲染。

image.png

如何构建一个可以实现SSR的项目

1.创建一个vue ssr的项目

vue create ssr

安装依赖
渲染器:vue-server-renderer
node.js服务器:express

npm i vue-server-renderer express -D

2.在创建好的项目中添加server文件夹并创建index.js文件

image.png

const express = require('express');
const Vue = require('vue');

const app = express();
const renderer = require('vue-server-renderer').createRenderer();
const port = 3000;

const page = new Vue({
    data(){
        return{
            title:'技术直男星辰'
        }
    },
    template:`<div>
                <h1>{{title}}</h1>
                <div>hello, vue ssr!</div>
              </div>`
});

app.get('/',async (req,res)=>{
    try {
        const html = await renderer.renderToString(page)
        res.send(html)
    } catch(err){
        res.status(500).send('服务器内部错误');
    }
});
app.linsten(port,()=>{
    console.log(`渲染服务器在${port}端口成功运行`)
})

在server目录下执行 一个简单的ssr就搭建好了

node ./index.js

在开发中肯定会用到路由,所以需要创建router文件夹,注意这与以往我们创建路由方式不同,不是直接实例化一个路由进行配置,而是要写一个工厂函数,去返回实例化的路由,因为当用户每次请求,就要创建一个新路由,如果不重新创建就会发生混乱,这也是为什么会使服务器负载增大的原因。

image.png

    import Router from 'vue-router'
    
    import Index form '@/components/Index';
    import Detail form '@/components/Detial';
    
    export default createRouter()=> {
        return new Router({
            mode:'history',
            routes:[
                {path:'/',component:Index},
                {path:'/detail',component:Detail},
            ]
        })
    }

但是这还不够,我们看ssrwebpack的构建流程中是需要两个入口的Serverentry Cliententry还有一个通用入口,可以理解成项目中的main.js

image.pngsrc下创建app.js entry-serve.js entry-client.js

app.js
app为通用入口,由于是在服务器端,不需要在Vue实例后添加$mount进行挂载

image.png entry-server.js
服务器端入口 从app.js中引入createApp,用Promise处理下router的异步问题,就可以在用户输入url时,把成功后的Vue实例返回出去

image.png entry-client.js
客户端入口,只需要把成功后的实例去挂载到#app上即可

image.png 配置vue.config.js 需要安装些依赖
vue-server-renderer/server-plugin 服务器渲染插件 生成服务端的包
vue-server-renderer/client-plugin 客户端渲染插件 生成客户端的包
cross-env 跨平台设置和使用环境变量的脚本

const VueSSRServerPlugin = require("vue-server-renderer/server-plugin");
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin");
const TARGET_NODE = process.env.WEBPACK_TAGET === 'node';
const target = TARGET_NODE ? 'server' :'client';

module.exports = {
    outputDir: './dist/' + target,
    configureWebpack: ()=>({
        entry: `./src/entry-${target}.js`,
        devtool: 'source-map',
        target: TARGET_NODE ?'node' :'web',
        node: TARGET_NODE ?undefined : false,
        output: {
            libraryTarget: TARGET_NODE ? 'commonjs2' :undefined
        },
        plugins: [TARGET_NODE ?new VueSSRServerPlugin() : new VueSSRClientPlugin()]
    })
}

配置打包命令 package.json

"scripts":{
    "build:client":"vue-cli-service build",
    "build:server":"cross-env WEBPACH_TARGET=node vue-cli-service build --mode server",
    "build":"npm run build:client && npm run build:server"
}

改造之前server文件夹中的index.js

const fs = require('fs')
//创建渲染器
const {createBundleRenderer} = require('vue-server-renderer');
const serverBundle = require('../dist/server/vue-ssr-server-bundle.json');
const clientManifest = require('../dist/client/vue-ssr-client-manifest.json');
const renderer = createBundleRenderer(serverBundle,{
    runInNewContext:false,
    template:fs.readFileSync('../public/index.temp.html','utf-8'), 
    clientManifest,
})
// 中间件处理静态文件请求
app.use(express,static('../dist/client'),index:false))

app.get('*',async (req,res)=>{
    try {
        const context = {
            url:req.url,
            title:'技术直男星辰'
        }
        const html = await renderer.renderToString(context)
        res.send(html)
    } catch(err){
        res.status(500).send('服务器内部错误');
    }
});
app.linsten(port,()=>{
    console.log(`渲染服务器在${port}端口成功运行`)
})

刚才在文件中引入../public/index.temp.html需要创建一个宿主的模版文件 在public文件夹中创建index.temp.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <title>Vue ssr</title>
    </head>
    <body>
        <--vue-ssr-outlet-->  服务端渲染的口
    </body>
</html>

接下来所有项目就构建成功了。
总结一下:

  1. SSR需要node服务中间层,进行一个数据请求,接着模版编译输出html文件。
  2. 需要两个入口文件entry-server entry-client,对不同环境做出不同操作。
  3. 当用户请求node服务时,返回的是新实例化好的Vue和Router,而不是普通SPA只实例一次。
  4. 一些配置及宿主模版,编写规则是约定好的,需要像记for if map 一样记住即可。

我们聊下一个话题

SEO优化处理

由于我不是专业的SEO优化的工作人员,只能简单的分享到我在项目中遇到的一些经验:

  1. 在服务器根目录下创建robots.txt文件。
  2. 在首次渲染时,所有接口数据必须在源代码中展示。
  3. 优化路径问题。
  4. 所有跳转路径使用a标签跳转,不要用事件跳转(如router.push)。
  5. TDK中多些对页面的描述
  6. nofollow、canonical使用
  7. 301重定向

robots.txt文件

这个百度百科中有很详细的介绍robots。简单介绍下。当爬虫要爬网站时,会先从服务器根目录寻找robots.txt,robots文件中就是规定着允许哪些爬虫访问网站,允许爬虫访问哪些域名进行数据采集,当然也有不允许哪些爬虫访问和不予许爬虫访问哪些网站。可以屏蔽一些网站中比较大的文件,如:图片,音乐,视频等,节省服务器带宽;可以屏蔽站点的一些死链接。方便搜索引擎抓取网站内容;设置网站地图连接,方便引导蜘蛛爬取页面。
写法字段含义:

User-agent: 代表搜索引擎的种类名称 当选择全部时使用*即可,比如有Baiduspider BadBot Googlebot MSNBot YoudaoBot Sogou web spider

Disallow:禁止爬虫爬到的地址 Disallow:/禁止爬取所有地址; Disallow:/admin/ 禁止爬取admin目录下的目录;Disallow:/?/禁止访问网站中所有包含问号 (?) 的网址;Disallow: /live/*.html 禁止访问/cgi-bin/目录下的所有以".htm"为后缀的URL;Disallow: /.jpg$ 禁止抓取网页所有的.jpg格式的图片。

Allow:允许爬取资源 Allow: .htm$ 仅允许访问以".htm"为后缀的UR;LAllow: /cgi-bin/ 这里定义是允许爬寻cgi-bin目录下面的目录;Allow: /tmp 这里定义是允许爬寻tmp的整个目录

Sitemap: 网站地图 告诉爬虫这个页面是网站地图
大家好奇的话可以看看百度的robots 再说说一些使用误区:
一:网站上所有内容都需要爬虫爬取,就没必要添加roubots文件了,反正不存在此文件,默认能让爬虫爬取所有页面。
当爬虫爬到一个不存在的url后,服务器会记录一条404,与正常用户访问时一致的,所以避免大量记录日志,是有必要添加一个爬虫的爬取规则。
二:在robots.txt文件中设置所有的文件都可以被搜索蜘蛛抓取,这样可以增加网站的收录率。 网站中的js,css即使被爬虫收录,也不会增加网站收录率,还会量费服务器性能。

在首次渲染时,所有接口数据必须在源代码中展示

前文也有提到,由于spa返回给页面是一个网页结构,内容是等框架脚本执行完毕才能出现,SSR技术其实就是为了解决内容输出的。有SSR后,接口返回内容就会体现在源代码中。这样做是为了爬虫能直接爬取到网页内容,收录更多关键词,从而提高网站排名。

优化路径问题

1.不要在路径中携带参数,比如query传参。 有关路径问题对SEO的影响也是很大的,路径一般分为三种
1).动态路径:我们常用的query携参的,以 ?开始用 & = 去分割传递不用参数,当爬虫遇见这种情况会递归的往下读取,当超过三个的时候。抓取会导致数据丢失。而且携参不宜过长。
2).静态路径:一般指层次结构相对清晰,有清晰的目录结构,切不包含参数,这是爬虫最愿意最省资源的抓去方式,当然动态路径和静态路径爬取是一样的,但既然做seo就要符合seo的规则。
3).伪静态路径:类似于路由的params传参,是一种使用技术将动态路径转换为静态路径的形式,但本质上是静态路径,如xx.com/index/112.html
另外,网站仅允许设置一种类型的路径,即所有动态路径或静态路径。不允许同时使用两个路径。如果存在第二个连接,则必须将其阻止,也可以使用robots.txt来阻止它。 2.路径名称都改为小写
3.不能使用汉字作为域名
爬虫根本识别不了这种路径形式,基本上不会被抓取到,可以使用拼音或英文。
4.路径名称和级别尽可能的短,如果有以前收录的链接,现在不用了,使用301重定向到新的域名,这样会将这个网站的权重转移到另一个。

所有跳转路径使用a标签跳转,不要用事件跳转(如router.push

当爬虫爬取网站时,会顺着a标签继续向下访问,如果写click事件,利用路由跳转,爬虫是无法读取到脚本,进行有关网站更多的收录。

TDK中多些对页面的描述

T网站首页标题是最重要的,直接影响整个网站排名
<title>技术直男星辰</title>
它是对我们整个网站的核心关键词或服务领域的浓缩和提炼。首页标题T的设置时,要尽量使用分词技术,百度会对标题进行组合和分解。以英文- _ 来分割。首页标题总字数不要多于30个中文字符,不然标题有可能会显示不完整。其中

  1. 首页title写法:一般是‘网站名称-主关键词或含有关键词的描述’,一般吧主关键词放在最前面,因为搜索引擎给予标题最前面的词比后面的高。
  2. 栏目页title写法:一般有两种,‘栏目名称-网站名称’,‘栏目名称栏目关键词-网站名称’,比如 企业招聘 栏目,最好就用企业招聘,不要用生僻或无法识别的名称,比如 企业来人,企业看看等虽然个性但对优化并不友好。
  3. 列表页title写法:‘列表页名称-栏目名称-网站名称’。
  4. 文章详情页title写法:一半有三种‘文章标题-网站名称‘、‘内容标题-栏目名称’、’内容标题-栏目名称-网站名称‘,最后一个最为规范,能给用户很好的提示。

D描述,是网站关键词汇集标签
<meta name="Description" content="技术直男星辰,SSRNuxt.js框架避坑记">
字数一般控制在150字内,太长就无法正常显示完成了。百度移动搜索建站优化白皮书中关于网站优化的内容提到:百度未承诺严格按照title和description的内容展示标题和摘要,尤其是摘要,会根据用户检索的关键词,自动匹配展示合适的摘要内容,让用户了解网页的主要内容,影响用户的行为决策。(本段内容来源于百度搜索资源平台)
K关键词的设置\

  1. 首页description写法:一般是将首页的标题,关键词和一些特殊栏目的内容融合到里面,写成简单介绍。
  2. 栏目页description写法:一般是将栏目标题、关键字,分类列表名称融合写成介绍。
  3. 列表页description写法:一般是将列表标题、关键字融合写成介绍。
  4. 文章详情页description写法:一般是将文章标题、文章内容融合写成介绍。 <meta name="Keywords" content="掘金,技术直男星辰">
    主要作用是告诉搜索引擎本页内容是围绕哪些词展开的。因此keywords的每个词都要能在内容中找到相应匹配,才有利于排名。keywords一般不超过3个,每个关键词不宜过长,而且词语间要用英文“,”隔开。但各大搜索引擎对关键词的堆砌打击很大,目前百度等搜索引擎已经不对关键词进行查看收录了,但主要为了使用第三方工具,查看和统计关键词的排名情况,比如我们用站长工具查询,就有必要添加上。
  5. 首页keyword写法:一般是‘网站名称,主要栏目名,主要关键词’。
  6. 栏目页keyword写法:一般是‘栏目名称,栏目关键字,栏目分类列表’。
  7. 列表页keyword写法:‘栏目的主关键字’。
  8. 文章详情页keyword写法:提取一些文章关键词或重复比较多的词。

nofollow、canonical使用

nofollow:是html的属性值,他会存在于a标签和meta标签中,告诉爬虫不要追踪此网页上的链接或不要追踪此链接,比如一些重要度低的或者一些不用做排名的,使用nofollow,可以使爬虫对重要的链接提高权重,便于集中网站权重,减少权重的分散

<!-- 网站所有的都不要追踪 -->
<meta name="robots" content="nofollow" />
<!-- 该链接不要追踪 -->
<a href="www.XXX.com" rel="nofollow"></a>

canonical:是html中规范网址的属性值,比如多个URL同时可以访问一个网页,那么我们用canonical告诉爬虫这个页面的首选网址是什么,这个指定的URL是最有价值最规范的页面

<link href="canonical" href="http://www.XXX.com/newWebsite">

301重定向

301是浏览器发送http请求时,服务器响应的一个状态码,指的是页面永久性移走。也叫重定向。一般新老网站的替换,如因需要把 .php 改成 .html。在这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户还会得到一个404页面错误信息,访问流量白白丧失。使用301重定向不仅能使页面实现自动跳转,告诉用户你已经换了新的网址了。同时也告诉搜索引擎,这个才是真正的网址,搜索引擎只对重定向后的新网址进行索引,同时又会把旧地址权重如数转移到新地址下,从而不会让网站的排名因为网址变更而受到影响。
同时,当一个网站注册了多个域名,切有重复的内容时,需要通过301让用户跳到其中一个主域名,从然避免搜索引擎的惩罚。 当然301是服务器语言,ApacheIIS实现的,纯前端技术是实现不了的,但Nuxt用的是express作为中间层,在Nuxt执行环节的最开始,也就是在服务器端时,也可以实现301跳转。


如果此文章对您有帮助或启发,那便是我的荣幸