什么是seo
搜索引擎優(yōu)化(Search engine optimization,簡(jiǎn)稱SEO),指為了提升網(wǎng)頁(yè)在搜索引擎自然搜索結(jié)果中(非商業(yè)性推廣結(jié)果)的收錄數(shù)量以及排序位置而做的優(yōu)化行為,是為了從搜索引擎中獲得更多的免費(fèi)流量,以及更好的展現(xiàn)形象。
SEM(Search engine marketing,搜索引擎營(yíng)銷),則既包括了SEO,也包括了付費(fèi)的商業(yè)推廣優(yōu)化。
本文主要介紹的是前端如何在代碼上做SEO以及單頁(yè)項(xiàng)目如何實(shí)現(xiàn)SEO。
搜索引擎工作原理
要了解SEO,首先得了解搜索引擎的工作原理,其原理是比較復(fù)雜,流程簡(jiǎn)化如下:
爬蟲抓取網(wǎng)頁(yè)內(nèi)容
一般爬蟲抓取頁(yè)面內(nèi)容是先從一個(gè)頁(yè)面出發(fā),從中提取出其他頁(yè)面的鏈接,然后當(dāng)作下一個(gè)請(qǐng)求的對(duì)象,一直重復(fù)這個(gè)過(guò)程。所以要有良好的SEO,需要你在各大網(wǎng)站上擁有外鏈,這樣會(huì)提高你的網(wǎng)站被搜索引擎爬蟲的幾率。
分析網(wǎng)頁(yè)內(nèi)容
爬蟲拿到html之后,就會(huì)對(duì)其內(nèi)容進(jìn)行分析。一般需要進(jìn)行去雜、分詞、簡(jiǎn)歷索引數(shù)據(jù)庫(kù)。什么是索引數(shù)據(jù)庫(kù)呢?簡(jiǎn)單地說(shuō)就是記錄一個(gè)詞在哪些文檔中出現(xiàn)、出現(xiàn)次數(shù)、出現(xiàn)的位置等等。為什么要簡(jiǎn)歷索引數(shù)據(jù)庫(kù)呢?是為了快速查找。
搜索和排序
搜索會(huì)根據(jù)你輸入的關(guān)鍵詞,分別查詢其對(duì)應(yīng)的索引數(shù)據(jù)庫(kù),并對(duì)結(jié)果進(jìn)行處理和排序。
前端編碼的SEO
網(wǎng)站結(jié)構(gòu)
網(wǎng)站結(jié)構(gòu)要清晰。一般網(wǎng)站的結(jié)構(gòu)是樹(shù)形的,一般分為三個(gè)層次:首頁(yè) → 頻道頁(yè)(列表頁(yè)) → 文章頁(yè)(詳情頁(yè))。
網(wǎng)站的結(jié)構(gòu)要扁平。結(jié)構(gòu)層數(shù)越少越好,一般不要超過(guò)三層,搜索引擎一般到了第三層就不想繼續(xù)深入地爬取了。多數(shù)的網(wǎng)站,例如掘金、雪球等,他們的網(wǎng)站結(jié)構(gòu)是兩層,他們的首頁(yè)和頻道頁(yè)是同一個(gè)頁(yè)面。
導(dǎo)航
頁(yè)面應(yīng)該要有簡(jiǎn)明的導(dǎo)航。導(dǎo)航可以讓搜索引擎知道網(wǎng)站的結(jié)構(gòu),也可以讓搜索引擎知道當(dāng)前頁(yè)面在網(wǎng)站結(jié)構(gòu)所在的層次。 建議:
- 每一個(gè)頁(yè)面都包含導(dǎo)航。
- 對(duì)于內(nèi)容較多的網(wǎng)站可以采用面包屑導(dǎo)航。
- 鏈接使用文字鏈接,如果是圖片,則通過(guò)alt屬性告知搜索引擎鏈接的指向。
規(guī)范的URL
規(guī)范、簡(jiǎn)單、易理解的URL能讓搜索引擎更好地抓取內(nèi)容。建議:
- 同一個(gè)頁(yè)面,只對(duì)應(yīng)一個(gè)url 。多個(gè)url可以采用301進(jìn)行重定向。
- url可以反應(yīng)網(wǎng)頁(yè)內(nèi)容以及網(wǎng)站結(jié)構(gòu)信息。例如www.a.com/blog、www.a.com/blog/123、www.a.com/article。
- url盡量簡(jiǎn)短。
- 盡量減少動(dòng)態(tài)url中包含的變量參數(shù)。
提交Sitemap
Sitemap 可通知搜索引擎他們網(wǎng)站上有哪些可供抓取的網(wǎng)頁(yè),以便搜索引擎可以更加智能地抓取網(wǎng)站。
robot.txt
搜索引擎爬行網(wǎng)站第一個(gè)訪問(wèn)的文件就是robots.txt。在這個(gè)文件中聲明該網(wǎng)站中不想被蜘蛛訪問(wèn)的部分,這樣,該網(wǎng)站的部分或全部?jī)?nèi)容就可以不被搜索引擎訪問(wèn)和收錄了,或者可以通過(guò)robots.txt指定使搜索引擎只收錄指定的內(nèi)容。
合理的HTTP返回碼
不同的返回碼,搜索引擎的處理邏輯是不一樣的。
- 如果站點(diǎn)臨時(shí)關(guān)閉,當(dāng)網(wǎng)頁(yè)不能打開(kāi)時(shí),建議使用503狀態(tài)。503可以告知百度spider該頁(yè)面臨時(shí)不可訪問(wèn),請(qǐng)過(guò)段時(shí)間再重試。
- 如果百度spider對(duì)您的站點(diǎn)抓取壓力過(guò)大,請(qǐng)盡量不要使用404,同樣建議返回503。這樣百度spider會(huì)過(guò)段時(shí)間再來(lái)嘗試抓取這個(gè)鏈接,如果那個(gè)時(shí)間站點(diǎn)空閑,那它就會(huì)被成功抓取了。
- 有一些網(wǎng)站希望百度只收錄部分內(nèi)容,例如審核后的內(nèi)容,累積一段時(shí)間的新用戶頁(yè)等等。在這種情況,建議新發(fā)內(nèi)容暫時(shí)返回403,等審核或做好處理之后,再返回正常狀態(tài)的返回碼。
- 站點(diǎn)遷移,或域名更換時(shí),請(qǐng)使用301返回。
合適的title
title是告訴搜索引擎網(wǎng)頁(yè)的主要內(nèi)容。
- 每個(gè)網(wǎng)頁(yè)應(yīng)該有一個(gè)獨(dú)一無(wú)二的標(biāo)題,切忌所有的頁(yè)面都使用默認(rèn)標(biāo)題
- 標(biāo)題要主題明確和精練,包含這個(gè)網(wǎng)頁(yè)中最重要的內(nèi)容,且不羅列與網(wǎng)頁(yè)內(nèi)容不相關(guān)的信息
- 用戶瀏覽通常是從左到右的,重要的內(nèi)容應(yīng)該放到title的靠前的位置
百度建議描述:
- 首頁(yè):網(wǎng)站名稱 或者 網(wǎng)站名稱_服務(wù)介紹/產(chǎn)品介紹
- 頻道頁(yè):頻道名稱_網(wǎng)站名稱
- 文章頁(yè):文章標(biāo)題_頻道名稱_網(wǎng)站名稱
合適的description
description是對(duì)網(wǎng)頁(yè)內(nèi)容的精練概括。這個(gè)標(biāo)簽存在與否不影響網(wǎng)頁(yè)權(quán)值,只會(huì)用做搜索結(jié)果摘要的一個(gè)選擇目標(biāo)。 百度推薦做法:
- 為每個(gè)網(wǎng)頁(yè)創(chuàng)建不同的description,避免所有網(wǎng)頁(yè)都使用同樣的描述
- 網(wǎng)站首頁(yè)、頻道頁(yè)、產(chǎn)品參數(shù)頁(yè)等沒(méi)有摘要的網(wǎng)頁(yè)最適合使用description
- 準(zhǔn)確的描述網(wǎng)頁(yè),不要堆砌關(guān)鍵詞,長(zhǎng)度合理
HTML語(yǔ)義化
HTML語(yǔ)義化是用標(biāo)簽和屬性來(lái)描述內(nèi)容。所以HTML語(yǔ)義化是SEO的基石。一般建議:
- HTML結(jié)構(gòu)要清晰和簡(jiǎn)潔
- 跳轉(zhuǎn)使用<a>標(biāo)簽,不要使用js跳轉(zhuǎn)
- 圖片加alt說(shuō)明
- 文章用<article>標(biāo)簽承載
- ...
Vue單頁(yè)項(xiàng)目的SEO
目前,對(duì)于SEO支持比較好的項(xiàng)目方案是采用服務(wù)端渲染。所以如果項(xiàng)目有SEO需求,那么比較好的方案是服務(wù)端渲染。
如果你已經(jīng)采用了前后分離的單頁(yè)項(xiàng)目,而你的網(wǎng)站內(nèi)容不需要AJAX去獲取內(nèi)容和展示內(nèi)容,那么可以試試 prerender-spa-plugin 這個(gè)插件,這個(gè)插件是一個(gè)webpack插件,可以幫助你在打包過(guò)程中通過(guò)無(wú)頭瀏覽器去渲染你的頁(yè)面,并生成對(duì)應(yīng)的HTML。當(dāng)然這個(gè)方案適合你的路由是靜態(tài)的,并且路由數(shù)量非海量。
如果你的內(nèi)容是AJAX動(dòng)態(tài)獲取的,那么vue單頁(yè)項(xiàng)目可以試試 prerender ,這個(gè)是一個(gè)預(yù)渲染服務(wù),可以幫你通過(guò)無(wú)頭瀏覽器渲染頁(yè)面,并返回HTML。這個(gè)方案和prerender-spa-plugin很相似,都是通過(guò)無(wú)頭瀏覽器去渲染頁(yè)面,不同的是渲染的時(shí)機(jī),prerender-spa-plugin是在打包過(guò)程中渲染,注定了其只能渲染靜態(tài)路由,而prerender 是在請(qǐng)求時(shí)渲染,所以可以渲染動(dòng)態(tài)的路由。下面我重點(diǎn)介紹一下prerender方案。
prerender 的使用
1、安裝
$ npm install prerender 復(fù)制代碼
2、啟動(dòng)服務(wù) server.js
const prerender = require('prerender'); const server = prerender(); server.start(); 復(fù)制代碼
3、測(cè)試
http://localhost:3000/render?url=https://www.example.com/ 復(fù)制代碼
經(jīng)過(guò)上面三個(gè)步驟,你就已經(jīng)啟動(dòng)一個(gè)預(yù)渲染服務(wù),并且會(huì)返回"www.example.com/"的內(nèi)容,整個(gè)過(guò)程還是比較簡(jiǎn)單的。其github官網(wǎng)上面還介紹了它的許多中間件(Middleware),例如prerender-node (Express)、Nginx.conf等,那么這個(gè)和 prerender 是什么關(guān)系呢?是否直接使用中間件就可以呢?下面介紹prerender是如何工作的吧。
prerender方案的原理
首先服務(wù)端接收到一個(gè)頁(yè)面的請(qǐng)求,然后判斷這個(gè)請(qǐng)求是否來(lái)自搜索引擎的爬蟲,如果不是,則直接返回單頁(yè)項(xiàng)目的HTML,按照普通單頁(yè)項(xiàng)目的工作模式(客戶端渲染),如果是,則把請(qǐng)求轉(zhuǎn)發(fā)給prerender服務(wù),prerender服務(wù)會(huì)通過(guò)無(wú)頭瀏覽器進(jìn)行預(yù)渲染,渲染完成把內(nèi)容返回,這樣爬蟲就可以拿到有內(nèi)容的HTML了。prerender中間件就是用來(lái)判斷請(qǐng)求是否來(lái)自搜索引擎爬蟲和轉(zhuǎn)發(fā)請(qǐng)求的。
值得注意的是,prerender服務(wù)是不包含無(wú)頭瀏覽器的,所以需要自行安裝chrome瀏覽器。因此,整個(gè)方案運(yùn)行需要三部分:
- chrome瀏覽器
- prerender服務(wù)
- prerender中間件
那么prerender服務(wù)是怎么知道頁(yè)面渲染已經(jīng)完成的呢? Prerender服務(wù)通過(guò)計(jì)算未完成的請(qǐng)求數(shù)量,來(lái)確定頁(yè)面何時(shí)完成加載。一旦未完成的請(qǐng)求數(shù)達(dá)到零,服務(wù)會(huì)等待一段時(shí)間(默認(rèn)500ms),然后保存HTML。
prerender的最佳實(shí)踐
經(jīng)過(guò)實(shí)踐,請(qǐng)求一個(gè)經(jīng)過(guò)prerender渲染的頁(yè)面是時(shí)間,快的時(shí)候約2s,慢的時(shí)候會(huì)長(zhǎng)達(dá)8s。一般來(lái)說(shuō),請(qǐng)求時(shí)間在3s以內(nèi)是最好的。所以我從以下幾個(gè)方面入手,探索prerender的優(yōu)化方法。
減少資源請(qǐng)求的時(shí)間
影響prerender渲染時(shí)間的資源主要有js請(qǐng)求資源和api請(qǐng)求資源,api請(qǐng)求時(shí)間一般由后端決定,所以我考慮的是如何減少js資源請(qǐng)求時(shí)間。一般prerender服務(wù)渲染的資源請(qǐng)求地址是由頁(yè)面請(qǐng)求URL決定的,所以一般是線上的地址,如果我們把prerender服務(wù)部署在網(wǎng)站的服務(wù)器上,讓prerender服務(wù)請(qǐng)求資源走本地,那么就可以縮短資源的請(qǐng)求時(shí)間了。
如果你的線上服務(wù)是開(kāi)啟了CDN的話,那么資源走本地還有一個(gè)好處,就是可以節(jié)省CDN流量。
優(yōu)化prerender選項(xiàng)
prerender提供了一些自定義的選項(xiàng)
pageDoneCheckInterval:這個(gè)參數(shù)是prerender檢查頁(yè)面請(qǐng)求是否完成的定時(shí)器時(shí)間,默認(rèn)是500ms,即每500ms檢查未完成的請(qǐng)求數(shù)量是否為零,我將其修改為100ms,提高其檢查的頻率。
waitAfterLastRequest:這個(gè)參數(shù)是最后一個(gè)請(qǐng)求完成之后等待的時(shí)間,默認(rèn)是500ms,主要是請(qǐng)求完成之后,頁(yè)面更新渲染需要時(shí)間,立即返回的話,可能請(qǐng)求的數(shù)據(jù)來(lái)不及渲染,我將時(shí)間修改為200ms。
prerender插件
httpHeaders —— 返回合理的HTTP狀態(tài)碼
添加httpHeaders這個(gè)插件,可以更改返回的HTML的HTTP狀態(tài)碼,添加方式如下
var prerender = require('prerender'); var server = prerender() server.use(prerender.httpHeaders()); server.start(); 復(fù)制代碼
prerender通過(guò)識(shí)別在<head>中的<meta>標(biāo)簽來(lái)設(shè)置頁(yè)面返回的HTTP狀態(tài)碼。
<meta name="prerender-status-code" content="404"> 復(fù)制代碼
如果你需要設(shè)置301重定向,可以這樣做
<meta name="prerender-status-code" content="301"> <meta name="prerender-header" content="Location: http://www.xxx.com"> 復(fù)制代碼
blockResources —— 無(wú)需等待圖片資源
prerender是根據(jù)未完成的請(qǐng)求數(shù)來(lái)判斷是否渲染結(jié)束的。但是我們給搜索引擎返回的HTML只需要渲染通過(guò)js動(dòng)態(tài)增加的DOM,其實(shí)不需要渲染css或者渲染接口返回的圖片的,我們來(lái)看下prerender在渲染中是否會(huì)請(qǐng)求這些資源。 prerender可以開(kāi)啟是否打印請(qǐng)求,開(kāi)啟方式如下:
var server = prerender({ logRequests: true }); 復(fù)制代碼
開(kāi)啟之后就可以在控制臺(tái)看到請(qǐng)求了,請(qǐng)求里面是包含css和圖片資源的。
2019-07-17T04:34:03.180Z - 47 http://xxx.com/css/chunk-f4a02584.da8dca38.css 2019-07-17T04:34:03.180Z { source: 'network', level: 'error', text: 'Failed to load resource: net::ERR_INVALID_ARGUMENT', timestamp: 1563338043130.37, url: 'http://xxx.com/wefid/css/chunk-f4a02584.da8dca38.css', networkRequestId: '1000039068.65' } 2019-07-17T04:34:03.924Z + 3 http://xxx.com/img/erweima_wx.a84d82ef.jpg 2019-07-17T04:34:03.924Z + 4 http://xxx.com/img/erweima_wb.06971584.png 復(fù)制代碼
為什么prerender要等待這些資源呢?因?yàn)閜rerender服務(wù)還有一個(gè)強(qiáng)大的功能,那就是Prerender.com,其可以通過(guò)一個(gè)接口給你返回如下的東西:
- 網(wǎng)頁(yè)的HTML文件
- 網(wǎng)頁(yè)的屏幕截圖(視口或全屏)
- 網(wǎng)頁(yè)的PDF文件
- 網(wǎng)頁(yè)的HAR文件
- 執(zhí)行您自己的JAVAscript并返回json和HTML
很明顯,這些功能是需要加載你所需的CSS或圖片資源的,不然網(wǎng)頁(yè)顯示有問(wèn)題。這個(gè)時(shí)候,如果你只需要滿足SEO需求而不需要Prerender.com的功能的話,那么blockResources插件就可以派上用場(chǎng)了。插件添加方式如下:
var prerender = require('prerender'); var server = prerender() server.use(prerender.blockResources()); server.start(); 復(fù)制代碼
使用blockResources插件之后,圖片資源和字體資源會(huì)被abort(舍棄)。
自定義渲染結(jié)束時(shí)間
如果你想更細(xì)粒化地控制prerender的返回時(shí)機(jī),提前結(jié)束或者延后結(jié)束,那么可以使用這個(gè)標(biāo)志window.prerenderReady。
首先需要設(shè)置window.prerenderReady為false,prerender在檢測(cè)到window.prerenderReady為false之后,會(huì)等待你設(shè)置為true再返回結(jié)果。
<script> window.prerenderReady = false; </script> 復(fù)制代碼
當(dāng)你渲染完成之后,一般在接口請(qǐng)求完成并渲染完成之后
window.prerenderReady = true; 復(fù)制代碼
這樣你就可以更加自由地控制渲染結(jié)束的時(shí)機(jī)。
開(kāi)啟緩存
緩存這里有兩個(gè)方面,一方面是HTTP緩存(瀏覽器緩存),另一方面是渲染結(jié)果緩存。
首先HTTP緩存可以讓prerender服務(wù)不用頻繁地發(fā)起資源請(qǐng)求,節(jié)省傳輸時(shí)間。這個(gè)我就不展開(kāi)將,我想講的是渲染結(jié)果緩存。prerender中間件提供了兩種緩存方式, redis 或者 memcached ,以redis為例:
$ npm install redis 復(fù)制代碼 var redis = require("redis"), client = redis.createClient(); prerender.set('beforeRender', function(req, done) { client.get(req.url, done); }).set('afterRender', function(err, req, prerender_res) { client.set(req.url, prerender_res.body) }); 復(fù)制代碼
你可以通過(guò) beforeRender 和 afterRender 這兩個(gè)鉤子進(jìn)行細(xì)粒化地控制,對(duì)于內(nèi)容變化頻繁的不緩存或緩存時(shí)間短,對(duì)于內(nèi)容變化不頻繁的設(shè)置長(zhǎng)時(shí)間緩存。開(kāi)啟緩存不僅可以加速返回時(shí)間,還可以減輕服務(wù)器的壓力。
統(tǒng)計(jì)和監(jiān)控
統(tǒng)計(jì)和監(jiān)控可以放在中間件的 afterRender 中進(jìn)行。
prerender.set('afterRender', function(err, req, prerender_res) { if(err){ // 這里是錯(cuò)誤監(jiān)控代碼 // ... // return } let {headers: req_headers, originalUrl} = req let {headers: res_headers, body} = prerender_res // 這里是統(tǒng)計(jì)代碼,可以保存請(qǐng)求和返回的相關(guān)信息 }) 復(fù)制代碼
小結(jié)
通過(guò)以上的優(yōu)化方法(除了自定義渲染結(jié)束時(shí)間和開(kāi)啟緩存),我已經(jīng)將HTML的請(qǐng)求時(shí)間穩(wěn)定在2.5s左右。
總結(jié)
以上就是我想講的關(guān)于前端編碼SEO的全部?jī)?nèi)容,總而言之,就是
- 合適的HTML標(biāo)簽和屬性
- 合理的HTTP狀態(tài)碼
- Sitemap & robot.txt
- 合適的渲染方案
參考文章:
https://juejin.im/post/5d2d64f36fb9a07eba2c6f65