遇到的问题:
近来在写个人博客的时候遇到了大家可能都会遇到的问题
vue 单页面在 SEO 时显得很无力, 尤其是百度不会抓取动态脚本
Vue-Router 配合前后端分离无法让 meta 标签在蜘蛛抓取时动态填充
Vue 单页面又是大势所趋, 写起来也不止是一个爽, 当然也可以选择多页面
但即使是多页面在面对文章和文档时候也不可能说给每篇文章生成个 Vue 页面
SSR 当然能解决这个问题, 但是仔细想想 SSR 不就跟以前的. PHP 页面一样了么
都是预先拉取所有数据然后填充返回给浏览器, 需要多消耗服务器资源, 而且配置繁琐
当然预渲染也不能解决这个问题
那么问题来了, 我只是想让百度抓取下我的动态文章, 但是配置个繁琐的 SSR 并不是最好选择
我的解决办法:
既然只是想让百度抓取下我的动态文章, 其实就是让蜘蛛抓取我的静态页面时候, html 的 meta 标签是已经填充好的
那么就很简单了, 我们只需要实现一个极其简单的阉割版的 SSR 不就好了么
PS: 我百度了很久没有找到相关的文章, 不知道是不是我百度的姿势不对
具体思路:
因为我的服务器后端语言是 PHP,service 是 nginx, 所以我这里展示的所有后端代码都是基于 PHP, 大家当然可以选择 Node.JS 或者其他的语言去实现, 这里提供个简单的思路
上面说到我们要实现个阉割版 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 窝 http://cdn.linkvall.cn/
每篇文章刷新的时候相应的 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/;
- }
- }
这样就可以把各自的资源路径区分开
写在最后
本来是打算用 Node.JS 实现的, 写一个 NPM 包, 跑一个 node 进程, 然后 nginx 把请求反向代理到 node 端口, 这样看上去更优雅
不过转念一想我的后端是 PHP, 直接代理到这个 PHP 文件可以省去写 NPM 包的时间, 就是构建好的 HTML 还要每次都更改下有一点点费劲, 而且不优雅
这里更多的是提供一个思路, 后面有时间了或许我会写一个 NPM 包优雅的实现它
来源: https://www.jb51.net/article/150417.htm