單頁(yè)應(yīng)用SPA做SEO的一種清奇的方案

網(wǎng)上有好幾種單頁(yè)應(yīng)用轉(zhuǎn)seo的方案英上,有服務(wù)端渲染ssr涝滴、有預(yù)渲染prerender耕蝉、google抓AJAX横堡、靜態(tài)化耕腾。卷扮。盗舰。這些方案都各有優(yōu)劣庭瑰,開(kāi)發(fā)者可以根據(jù)不同的業(yè)務(wù)場(chǎng)景和環(huán)境決定用哪一種方案枪狂。本文將介紹另一種思路比較清奇的SEO方案危喉,這個(gè)方案也是有優(yōu)有劣,就看讀者覺(jué)得適不適合了州疾。

項(xiàng)目分析

我的項(xiàng)目是用react+ts+dva技術(shù)棧搭建的單頁(yè)應(yīng)用辜限,目前在線上已經(jīng)有幾十個(gè)頁(yè)面,若干個(gè)sdk和插件在里面孝治。

  • 考慮想用服務(wù)端渲染來(lái)做seo列粪,但是我的項(xiàng)目已經(jīng)開(kāi)發(fā)了這么多,打包配置谈飒、代碼分割岂座、語(yǔ)法兼容、摒棄瀏覽器對(duì)象杭措,服務(wù)端思想费什,這么多的點(diǎn)需要考慮,還不如換個(gè)框架重新開(kāi)發(fā)呢手素,所以改造成本太大??鸳址,服務(wù)端渲染不適合我這種情況。
  • 預(yù)渲染雖然是開(kāi)發(fā)成本最低的泉懦,但畢竟是生成一張一張的靜態(tài)html稿黍,而我的seo需求是能夠讓蜘蛛抓取到我的社區(qū)論壇下的每一篇帖子,這樣子下來(lái)一篇帖子就是一份html,再加上分頁(yè)崩哩,那得多大的量級(jí)來(lái)存儲(chǔ)啊??巡球,而且網(wǎng)站更新就更麻煩了言沐,這個(gè)方案也不太適合。
  • google.....Emmmm.........................下一個(gè)
  • 靜態(tài)化也是跟預(yù)渲染差不多酣栈。险胰。。

隆重介紹

以前寫(xiě)過(guò)一種單頁(yè)應(yīng)用seo的方案矿筝,就是自己先在本地用爬蟲(chóng)做預(yù)渲染起便,生成同樣目錄結(jié)構(gòu)的靜態(tài)化的html,前端項(xiàng)目服務(wù)器判斷請(qǐng)求的UA是搜索引擎蜘蛛的話就會(huì)轉(zhuǎn)發(fā)到我事先靜態(tài)化過(guò)的html頁(yè)面

當(dāng)時(shí)的項(xiàng)目只是一個(gè)簡(jiǎn)單的只有幾個(gè)頁(yè)面的企業(yè)官網(wǎng)窖维,預(yù)渲染沒(méi)啥問(wèn)題榆综。

跟著這個(gè)思路,只要判斷搜索引擎蜘蛛讓蜘蛛看到另一個(gè)有數(shù)據(jù)的頁(yè)面不就行了铸史。

至于頁(yè)面長(zhǎng)什么樣奖年,蜘蛛??才不會(huì)管呢,就像是你找廣告商投放廣告沛贪,廣告商不會(huì)要求你要怎樣的主題什么色調(diào),只要你按照他的尺寸和要求來(lái)做震贵,然后給錢(qián)給貨就完事了??利赋。

所以可以針對(duì)SEO做另一套網(wǎng)站,沒(méi)有樣式猩系,只有符合seo規(guī)范的html標(biāo)簽和對(duì)應(yīng)的數(shù)據(jù)媚送,不需要在原有項(xiàng)目上改造,開(kāi)發(fā)成本也不會(huì)很高寇甸,體積小加載速度更快塘偎。

缺點(diǎn)也有,就是需要另外維護(hù)一套網(wǎng)站拿霉,主網(wǎng)站界面變化不會(huì)影響吟秩,如果展示數(shù)據(jù)有變化就需要同步修改seo版的網(wǎng)站。

代碼實(shí)現(xiàn)

先建個(gè)單獨(dú)的seo文件夾绽淘,不需要?jiǎng)拥皆许?xiàng)目涵防,下面是代碼結(jié)構(gòu):


image

代碼實(shí)現(xiàn)非常之簡(jiǎn)單,只要寫(xiě)一個(gè)中間件攔截請(qǐng)求沪铭,鑒別蜘蛛壮池,返回對(duì)應(yīng)路徑的seo頁(yè)面即可。

我的前端服務(wù)器是用express杀怠,可以寫(xiě)個(gè)express的中間件, 新建server.js:

// seo/server.js
const routes = require('./routes')
const layout_render = require('./src/layout');

module.exports = (req, res, next) => {
  // 各大搜索引擎蜘蛛U(xiǎn)A
  const spiderUA = /Baiduspider|bingbot|Googlebot|360spider|Sogou|Yahoo! Slurp/
  var isSpider = spiderUA.test(req.get('user-agent'))
  // 獲取路由表的路徑
  var seoPath = Object.keys(routes)
  if (isSpider) {
    for (let i=0,route; route = seoPath[i]; i++) {
      if (new RegExp(route).test(req.path)) {
        routes[route](req).then((result) => {
          // 返回對(duì)應(yīng)的模板結(jié)果給蜘蛛
          res.set({'Content-Type': 'text/html','charset': 'utf-8mb4'}).status(200).send(layout_render(result))
        })
        break;
      }
    }
  } else {
    // 未匹配到蜘蛛則繼續(xù)后面的中間件
    return next()
  }
}

然后在前端的啟動(dòng)服務(wù)器里加入這個(gè)中間件椰憋,記得要放在其他中間件之前

// 前端啟動(dòng)服務(wù)器的server文件
var express = require('express')
var app = express()
// seo
app.use(require('seo/server'));
......

app.listen(xxxx)

接下來(lái)就是寫(xiě)模板和對(duì)應(yīng)的解析了, 新建一個(gè)home文件夾,文件夾下再建一個(gè)index.ejs和index.js

<!-- seo/src/home/index.ejs -->
<div>
  <h1>官網(wǎng)首頁(yè)</h1>
  <p>友情鏈接:</p>
  <p><a  target="_blank">百度</a></p>
  <p><a  target="_blank">谷歌</a></p>
</div>

index.js用于解析對(duì)應(yīng)的ejs模板

// seo/src/home/index.js
const ejs = require('ejs')
const fs = require('fs')
const path = require('path')
const template = fs.readFileSync(path.resolve(__dirname, './index.ejs'), 'utf8');

// 這里為什么會(huì)有個(gè)async關(guān)鍵字赔退,往后面看就可以知道橙依。
module.exports = async (req) => {
  const result = ejs.render(template)
  return result
}

我們還可以建多個(gè)layout模板來(lái)管理head、title和導(dǎo)航欄這些公有的元素

<!-- seo/layout.ejs -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="content-type" content="text/html;charset=utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name=”renderer” content=”webkit”>
  <meta content="網(wǎng)站關(guān)鍵字"" name="keywords"/>
  <meta content="網(wǎng)站描述" name="description"/>
  <title>網(wǎng)站標(biāo)題</title>
</head>
<body>
  <div id="root">
    <ul>
      <li><a href="/">首頁(yè)</a></li>
      <li><a href="/community">社區(qū)</a></li>
    </ul>
    <%- children -%>
  </div>
</body>
</html>

解析layout.ejs,套入內(nèi)容的layout_render:

// seo/layout.js
const ejs = require('ejs')
const fs = require('fs')
const path = require('path')
const template = fs.readFileSync(path.resolve(__dirname, './layout.ejs'), 'utf8');

const layout_render = (children) => {
  return ejs.render(template, {children: children})
}
module.exports = layout_render

路由表用簡(jiǎn)單的鍵值對(duì)就可以了票编,鍵名用字符串形式的正則來(lái)表示路徑的匹配規(guī)則:

// seo/routes.js
const home_route = require('./src/home/index')

module.exports = {
  '^(/?)$': home_route,
}

那么數(shù)據(jù)如何做請(qǐng)求并展示到對(duì)應(yīng)的模板內(nèi)呢褪储?數(shù)據(jù)請(qǐng)求是異步的,怎樣等到請(qǐng)求完成再渲染模板呢慧域?

我們可以用async/await來(lái)實(shí)現(xiàn)鲤竹,現(xiàn)在來(lái)做一個(gè)社區(qū)的帖子列表頁(yè)面,需要先請(qǐng)求社區(qū)下帖子列表數(shù)據(jù)再把數(shù)據(jù)渲染到模板昔榴,新建一個(gè)community文件夾辛藻,同樣再建一個(gè)index.ejs作為帖子列表頁(yè)面模板:

<!-- seo/src/community/index.ejs -->
<div>
  <h1>帖子列表</h1>
  <ul>
    <% forum_list.map((item) => { %>
    <li><a href="/community/<%= item.id%>" target="_blank"><%= item.title-%></a></li>
    <% })%>
  </ul>
</div>

相關(guān)的接口請(qǐng)求及數(shù)據(jù)操作寫(xiě)在同級(jí)的index.js:

// seo/src/community/index.js
const ejs = require('ejs')
const fs = require('fs')
const path = require('path')
const template = fs.readFileSync(path.resolve(__dirname, './index.ejs'), 'utf8');
const axios = require('axios');

module.exports = async (req) => {
  const res = await axios.get('http://xxx.xx/api/community/list')
  const result = ejs.render(template, {forum_list: res.data.list})
  return result
}

再加上對(duì)應(yīng)的路由配置:

// seo/routes.js
const home_route = require('./src/home/index')
const community_route = require('./src/community/index')

module.exports = {
  '^(/?)$': home_route,
  '^/community$': community_route,
}

這樣就實(shí)現(xiàn)了先取接口數(shù)據(jù)再做渲染,保證了蜘蛛訪問(wèn)能給到完整的數(shù)據(jù)和html結(jié)構(gòu)互订。

繼續(xù)實(shí)現(xiàn)一個(gè)帖子詳情的頁(yè)面:

<!-- seo/src/community_detail/index.ejs -->
const community_route = require('./src/community/index')
<div>
  <h1><%= forum_data.title%></h1>
  <p><%= forum_data.content%></p>
  <p>作者:<%= forum_data.user.nickname%></p>
</div>
// seo/src/community_detail/index.js
const ejs = require('ejs')
const fs = require('fs')
const path = require('path')
const template = fs.readFileSync(path.resolve(__dirname, './index.ejs'), 'utf8');
const axios = require('axios');

module.exports = async (req) => {
  // 獲取路徑里的id   /community/:id
  const forum_id = req.path.split('/')[2]
  const res = await axios.get(`http://xxx.xx/api/community/${forum_id}/details?offset=1&limit=10`)
  const result = ejs.render(template, {forum_data: res.data})
  return result
}

同樣加上對(duì)應(yīng)的路由配置:

// seo/routes.js
const home_route = require('./src/home/index')
const community_route = require('./src/community/index')
const community_detail_route = require('./src/community_detail/index')

module.exports = {
  '^(/?)$': home_route,
  '^/community$': community_route,
  '^/community/\\d+$': community_detail_route,
}

這樣就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的seo版網(wǎng)站吱肌,不需要任何樣式,不需要js做彈框之類(lèi)的后續(xù)交互仰禽,只要蜘蛛訪問(wèn)網(wǎng)址的第一個(gè)請(qǐng)求有它要的數(shù)據(jù)即可氮墨,是不是非常的清奇??。吐葵。规揪。

總結(jié)來(lái)說(shuō)呢,就是如果你的項(xiàng)目處在線上運(yùn)營(yíng)階段并且開(kāi)發(fā)到了一定的集成度了温峭,迫于ssr的改造成本太大猛铅,又需要讓一些數(shù)據(jù)(比如每一篇文章帖子)能夠被收錄,就可以考慮一下我的這個(gè)方法??凤藏。

但是我不保證蜘蛛的防作弊機(jī)制奸忽,會(huì)不會(huì)過(guò)濾掉我這種跟瀏覽器正常訪問(wèn)主站差異較大的seo版小網(wǎng)站??。目前這個(gè)方案還在試驗(yàn)階段揖庄。

測(cè)試

測(cè)試也很簡(jiǎn)單栗菜,寫(xiě)個(gè)模擬蜘蛛請(qǐng)求即可,curl蹄梢、爬蟲(chóng)苛萎、postman都可以模擬蜘蛛的UA來(lái)測(cè)試〖旌牛或者改一下搜索引擎蜘蛛的的判斷條件就可以直接用瀏覽器訪問(wèn)的呢腌歉。


如果有朋友用了我這個(gè)方法并且真的有用能夠被搜索引擎收錄的話,請(qǐng)記得我??齐苛,要是能打賞就更好了哈哈??翘盖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市凹蜂,隨后出現(xiàn)的幾起案子馍驯,更是在濱河造成了極大的恐慌阁危,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汰瘫,死亡現(xiàn)場(chǎng)離奇詭異狂打,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)混弥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)趴乡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蝗拿,你說(shuō)我怎么就攤上這事晾捏。” “怎么了哀托?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵惦辛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我仓手,道長(zhǎng)胖齐,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任嗽冒,我火速辦了婚禮市怎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辛慰。我一直安慰自己,他們只是感情好干像,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布帅腌。 她就那樣靜靜地躺著,像睡著了一般麻汰。 火紅的嫁衣襯著肌膚如雪速客。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天五鲫,我揣著相機(jī)與錄音溺职,去河邊找鬼。 笑死位喂,一個(gè)胖子當(dāng)著我的面吹牛浪耘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播塑崖,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼七冲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了规婆?” 一聲冷哼從身側(cè)響起澜躺,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蝉稳,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后掘鄙,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體耘戚,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年操漠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了收津。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颅夺,死狀恐怖朋截,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吧黄,我是刑警寧澤部服,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站拗慨,受9級(jí)特大地震影響廓八,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赵抢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一剧蹂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烦却,春花似錦宠叼、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至摩渺,卻和暖如春简烤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摇幻。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工横侦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绰姻。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓枉侧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親狂芋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子棵逊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)银酗。 注意:講述HT...
    kismetajun閱讀 27,513評(píng)論 1 45
  • 問(wèn)答題47 /72 常見(jiàn)瀏覽器兼容性問(wèn)題與解決方案辆影? 參考答案 (1)瀏覽器兼容問(wèn)題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 13,759評(píng)論 1 92
  • iPhone拍攝徒像、密歇根湖畔
    遼河人在遠(yuǎn)方閱讀 84評(píng)論 0 4
  • 不用做太好 我喜歡就好
    阿葵森友閱讀 302評(píng)論 0 1
  • 目標(biāo):5月到7月在稅務(wù)籌劃,財(cái)務(wù)日常問(wèn)題處理蛙讥,中級(jí)會(huì)計(jì)職稱(chēng)考試準(zhǔn)備這三個(gè)方面提升自己的能力锯蛀。 動(dòng)機(jī):擁有一份五險(xiǎn)一...
    空空dj閱讀 170評(píng)論 0 0