服务端预渲染:前后端分离项目SEO优化方案

前后端分离项目天生对SEO不友好,目前的方案主要有以下几种:

  • 服务端渲染:需要选定支持SSR的前端框架 Next.jsNuxt.js
  • 预渲染:针对搜索引擎的爬取做服务端的模拟渲染
  • 静态站点生成
  • 提供多版本

针对已有的前后端分离,使用服务端渲染方案改造工程较大,主要使用的是预渲染的模式。
下面针对遇到的实际Vue项目做一下预渲染方案

第一步:实现页面切换的 TDK 更新

使用的工具可以是 Vue UnheadVue-meta

# Vue unhead 示例
import { useHead } from '@unhead/vue'

useHead({
  title: 'My awesome site'
})

第二步:安装 Puppeteer 及相关依赖

找个目录安装 Puppeteer 组件

选定一个目录作为Puppeteer服务的项目文件夹,安装依赖组件

cd xxxx
npm install puppeteer --save
npm install express
npm install html-minifier

安装服务器依赖

根据系统环境安装对应的依赖包

# Linux
yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86
# Debian
sudo apt-get update
sudo apt-get install -y \
    ca-certificates \
    fonts-freefont-ttf \
    libasound2 \
    libatk-bridge2.0-0 \
    libatk1.0-0 \
    libc6 \
    libcairo2 \
    libcups2 \
    libdbus-1-3 \
    libexpat1 \
    libfontconfig1 \
    libgbm1 \
    libgcc1 \
    libglib2.0-0 \
    libgtk-3-0 \
    libnspr4 \
    libnss3 \
    libpango-1.0-0 \
    libpangocairo-1.0-0 \
    libstdc++6 \
    libx11-6 \
    libx11-xcb1 \
    libxcb1 \
    libxcomposite1 \
    libxcursor1 \
    libxdamage1 \
    libxext6 \
    libxfixes3 \
    libxi6 \
    libxrandr2 \
    libxrender1 \
    libxss1 \
    libxtst6 \
    lsb-release \
    wget \
    xdg-utils

开发预渲染代码

项目主要包含三个个js文件,其中 puppeteer-pool.js是一个简单的工具,随机返回一个无头浏览器;spider.js是模拟爬虫访问无头浏览器获取网页渲染结果的,其中会过滤掉一些对结果不产生影响的图片及媒体文件请求;server.js是启动服务的,将目标

# puppeteer-pool.js
const puppeteer = require('./node_modules/puppeteer');
const MAX_WSE = 2; // 浏览器数目 
let WSE_LIST = [];
//负载均衡
(async () => {
	for (var i = 0; i < MAX_WSE; i++) {
		const browser = await puppeteer.launch({
            //无头模式
			headless: true,
			args: [
				'--disable-gpu',
				'--disable-dev-shm-usage',
				'--disable-setuid-sandbox',
				'--no-first-run',
				'--no-sandbox',
				'--no-zygote',
				'--single-process'
			],
			// 一般不需要配置这条,除非启动一直报错找不到谷歌浏览器
			//executablePath:'chrome.exe在你本机上的路径,例如C:/Program Files/Google/chrome.exe'
		});
		let browserWSEndpoint = await browser.wsEndpoint();
		WSE_LIST.push(browserWSEndpoint);
	}
})();

module.exports = WSE_LIST
# spider.js
const puppeteer = require('./node_modules/puppeteer');
const WSE_LIST = require('./puppeteer-pool.js');
const spider = async (url) => {
	let tmp = Math.floor(Math.random() * WSE_LIST.length);
	//随机获取浏览器
	let browserWSEndpoint = WSE_LIST[tmp];
	//连接
	const browser = await puppeteer.connect({
		browserWSEndpoint
	});
	
	//打开一个标签页
	const page = await browser.newPage();
	
	// Intercept network requests.
	await page.setRequestInterception(true);

    page.on('request', req => {
      // 忽略图片媒体文件
      // (images, media).
      const whitelist = ['document', 'script', 'xhr', 'fetch', 'stylesheet'];
      if (!whitelist.includes(req.resourceType())) {
          return req.abort();
      }
      
      req.continue();
    });
	
	//打开网页
	await page.goto(url, {
		timeout: 20000, //连接超时时间,单位ms
		waitUntil: 'networkidle0' //网络空闲说明已加载完毕
	});
	
	// 获取结果
	let html = await page.evaluate(() => {
		return document.getElementsByTagName('html')[0].outerHTML;
	});
	await page.close();
	return html;
}

module.exports = spider;
# server.js
const express = require('./node_modules/express');
var app = express();
var spider = require("./spider.js");
var minify = require('html-minifier').minify;
app.get('*', async (req, res) => {
	let url = "http://xxxx.com" + req.originalUrl; // 完善域名地址
	console.log('请求的完整URL:' + url);
	let content = await spider(url).catch((error) => {
		console.log(error);
		res.send('获取html内容失败');
		return;
	});
	// 通过minify库压缩代码
    content=minify(content,{removeComments: true,collapseWhitespace: true,minifyJS:true, minifyCSS:true});
	res.send(content);
});
app.listen(3000, () => {
	console.log('服务已启动!');
});

第三步:启动 Puppeteer

启动服务,端口默认是3000,出现问题根据报错信息调整

nohup node server.js &

第四步:配置 Nginx 做爬虫的预渲染转发

location / {
    if ($http_user_agent ~* "Baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|bingbot|Sosospider|Sogou Pic Spider|Googlebot|360Spider") {
      proxy_pass  http://127.0.0.1:3000;
    }
    
  	try_files $uri $uri/ /index.html =404;
}

通过postman等工具,通过User-Agent模拟爬虫校验结果是否成功

User-Agent: User-Agent:Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)