遇到的问题:
近来在写个人博客的时候遇到了大家可能都会遇到的问题
Vue单页面在SEO时显得很无力,尤其是百度不会抓取动态脚本
Vue-Router配合前后端分离无法让meta标签在蜘蛛抓取时动态填充
Vue单页面又是大势所趋,写起来也不止是一个爽,当然也可以选择多页面
但即使是多页面在面对文章和文档时候也不可能说给每篇文章生成个Vue页面
SSR当然能解决这个问题,但是仔细想想SSR不就跟以前的.php页面一样了么
都是预先拉取所有数据然后填充返回给浏览器,需要多消耗服务器资源,而且配置繁琐
当然预渲染也不能解决这个问题
那么问题来了,我只是想让百度抓取下我的动态文章,但是配置个繁琐的SSR并不是最好选择
我的解决办法:
既然只是想让百度抓取下我的动态文章,其实就是让蜘蛛抓取我的静态页面时候,html的meta标签是已经填充好的
那么就很简单了,我们只需要实现一个极其简单的阉割版的SSR不就好了么
PS:我百度了很久没有找到相关的文章,不知道是不是我百度的姿势不对😅
具体思路:
因为我的服务器后端语言是php,service是nginx,所以我这里展示的所有后端代码都是基于php,大家当然可以选择nodejs或者其他的语言去实现,这里提供个简单的思路
上面说到我们要实现个阉割版SSR,其实就是在服务器有请求过来的时候在静态html的meta标签上填充好数据然后返回给请求端
这里的实现都是基于已经构建好的Vue单页面,所以请先构建好一个Vue单页面
先把构建好的dist下的index.html改造下
在顶部将变量拿取到,因为接口都是现成的,所以偷个懒直接调取接口
<?php
$valDescription = '前端入门,进阶总结记录,个人日志博客,分享web学习经验的小窝。';
$valKeywords = 'web窝,前端,vue,js,博客,JavaScript,css,入门,教程';
$valTitle = 'web窝';
/**
* 发送HTTP请求方法
* @param string $url 请求URL
* @param array $params 请求参数
* @param string $method 请求方法GET/POST
* @return array $data 响应数据
*/
function http_Req($url, $params, $method = 'GET', $header = array("Content-type: text/html; charset=utf-8"), $multi = false){
if($method == 'POST'){
$header = ["Content-type: application/x-www-form-urlencoded"];
}
$opts = array(
CURLOPT_TIMEOUT => 30,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => $header
);
/* 根据请求类型设置特定参数 */
switch(strtoupper($method)){
case 'GET':
$opts[CURLOPT_URL] = $url . '?' . http_build_query($params);
break;
case 'POST':
//判断是否传输文件
$params = $multi ? $params : http_build_query($params);
$opts[CURLOPT_URL] = $url;
$opts[CURLOPT_POST] = 1;
$opts[CURLOPT_POSTFIELDS] = $params;
break;
default:
throw new Exception('不支持的请求方式!');
}
/* 初始化并执行curl请求 */
$ch = curl_init();
curl_setopt_array($ch, $opts);
$data = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
if($error) throw new Exception('请求发生错误:' . $error);
return $data;
}
//分割当前请求的路径
$urlExp = explode('/',$_SERVER['REQUEST_URI']);
//如果当前的路径是文章内容
if(count($urlExp)>2 && $urlExp[1] == 'article'){
//请求当前文章的标题和描述
$ret = json_decode(http_Req('http://127.0.0.1/api/Blog/getsinglelist',['tuid'=>$urlExp[2]],'POST'),true);
$valKeywords = $ret['info'][0]['tt'].','.$valKeywords;
$valDescription = $ret['info'][0]['txt'].' - '.$valTitle.','.$valDescription;
$valTitle = $ret['info'][0]['tt'].' - '.$valTitle;
}
?>
将拿到的数据输出到meta标签上
<meta name="description" content="<?php echo $valDescription; ?>"/>
<meta name="keywords" content="<?php echo $valKeywords; ?>"/>
<title><?php echo $valTitle; ?></title>
然后把文件名改为index.php
写的很糙,反正就只有这一个功能,先扔到这
我的service是nginx,Vue-Router是history模式,所以当有html请求过来的时候我把所有原先转向index.html的请求都转向到了index.php
这样就实现了个阉割版的SSR
效果就像这样 web窝
每篇文章刷新的时候相应的meta标签都会提前填充好数据
配合nginx实现Vue-Router的history模式
当然只有history模式才有SEO的意义
只需要将nginx配置里原先转向index.html的地方改为index.php即可,Apache同理
location / {
index index.php;
alias /var/www/html/blog/;
try_files $uri $uri/ /index.php;
}
关于后端接口请求的问题
因为我自己就一台服务器,后端语言是php
php所用的框架也需要伪静态
所以我的解决方式是用nginx配置根据二级域名,去代理所访问的路径
类似静态资源访问的都是cdn的二级域名,接口的请求访问的都是api的二级域名
server {
listen 80;
listen 443 ssl;
server_name cdn.linkvall.cn;
root /var/blog;
}
server {
listen 80;
listen 443 ssl;
server_name api.linkvall.cn;
root /var/api;
}
当然你可以简单的用请求的路径去配置
server {
listen 80;
listen 443 ssl;
server_name cdn.linkvall.cn;
root /var;
location ~ ^/api/ {
alias /var/api;
}
location ~ ^/blog/ {
alias /var/blog/;
}
}
这样就可以把各自的资源路径区分开
写在最后
本来是打算用nodejs实现的,写一个npm包,跑一个node进程,然后nginx把请求反向代理到node端口,这样看上去更优雅
不过转念一想我的后端是php,直接代理到这个php文件可以省去写npm包的时间,就是构建好的html还要每次都更改下有一点点费劲,而且不优雅
这里更多的是提供一个思路,后面有时间了或许我会写一个npm包优雅的实现它