原文鏈接https://juejin.cn/post/6844903996793028622
vue項目seo問題簡單解決,並生成sitemap
1.場景
隨着這幾年前端的大變革,對於大型項目,過多的Dom綁定后期的維護已成噩夢。單頁面應用由然而生,如今市場上vue,react,angular已成為三駕馬車,也是前端人員需要必備的技能了。今天我們主要講單頁面seo的問題,其他的東西就不深入了。
2.單頁面為什么不利於seo?
1.搜索引擎爬蟲的原理就是抓取你的url,然后獲取你的html源代碼並解析。單頁面應用最終渲染頁面都是通過js動態生成的,爬蟲獲取到的html只是單頁面的模型頁面不是最終渲染的頁面,所以用js來渲染數據對seo並不友好。
2.seo的本質就是一個服務器向另一個服務器發起請求,解析請求內容。通常情況下搜索引擎在追求速度的原因下,並不會去執行請求到的js。這時單頁面的問題就來了,html文件在服務端並沒有渲染數據,實際渲染數據實在客戶端完成的,所以搜索引擎請求到的html只是一個結構,這樣就很不利於頁面內容被搜索引擎搜索到。所以服務端渲染就是為了解決單頁面應用在發送到瀏覽器之前頁面就有內容了。
3.單頁面seo的解決方案
1.頁面預渲染
2.服務端渲染
服務端渲染已經很成熟了,也有現成的類庫,今天我們主要講的就是頁面預渲染。
使用場景:針對於只需改善少數頁面,無需與服務端交互實時動態編譯html文件的業務場景,比如:門戶網站等
使用插件:prerender-spa-plugin,prerender-spa-plugin是webpack的插件,所以用webpack打包的單頁面(react,vue,angular等)都能使用,今天的實踐是vue的,prerender-spa-plugin原理就是使用瀏覽器內核預先加載頁面渲染數據得到完整頁面后生成完整的html文件
4.預渲染實踐
4.1 安裝prerender-spa-plugin
yarn add prerender-spa-plugin -D
or
npm install prerender-spa-plugin -D
4.2 將vue router mode屬性改為history
export default new Router({ mode: 'history', routes: [ { path: '/', component: Index }, { path: '/about', component: About } ] })
mode必須為history,如果不改為history,在hash模式下,prerender-spa-plugin打包出來的靜態html只有首頁的html是完整的,其他頁面還是用的首頁骨架,然后動態生成生成html替換,就達不到seo的效果。
4.3 build/webpack.prod.conf.js (vue-cli2生成的結構下都有) build/webpack.prod.conf.js
const path = require('path') const PreRenderSPAPlugin = require('prerender-spa-plugin'); const Renderer = PreRenderSPAPlugin.PuppeteerRenderer; const routes = [ '/', '/about']; const resolve = dir => path.join(__dirname, '..', dir); const webpackConfig = merge(baseWebpackConfig, { plugins: [ new PreRenderSPAPlugin({ staticDir: resolve('dist'), routes, renderer: new Renderer({ inject: {}, // 在 main.js 中 document.dispatchEvent(new Event('render-event')),兩者的事件名稱要對應上。 //render-event的作用就是在render-event事件執行后執行preRender renderAfterDocumentEvent: 'render-event', //puppeteer參數,標簽意思:完全信任在Chrome中打開的內容 args: ['--no-sandbox', '--disable-setuid-sandbox'] }) }) ] })
本地打包沒有問題,linux服務器打包可能會出現ERROR: Fail to lauunch chrome!
原因一:linux docker 容器不能啟動chrome,因為docker共享內存不夠的原因
其他問題詳細答案:github.com/chrisvfritz…
4.4 src/main.js (vue-cli2生成的結構下都有)
new Vue({ el: '#app', router, components: { App }, template: '<App/>', mounted() { document.dispatchEvent(new Event('render-event')) } });
配置完后,npm run build就可以生成routes中路由對應的html了。是不是很簡單? 打包后結構:
│ index.html
├─about
│ index.html
└─static
├─css
├─fonts
├─img
└─js
復制代碼
5.自動生成打包后所有頁面sitemap
5.1 Sitemap簡介
百度百科:Sitemap 可方便網站管理員通知搜索引擎他們網站上有哪些可供抓取的網頁。最簡單的 Sitemap 形式,就是XML 文件,在其中列出網站中的網址以及關於每個網址的其他元數據(上次更新的時間、更改的頻率以及相對於網站上其他網址的重要程度為何等),以便搜索引擎可以更加智能地抓取網站。
粗俗易懂:百度爬蟲非常喜歡Sitemap,Sitemap協議使你能夠告知搜索引擎網站中可供抓取的網址,sitemap的生成就是讓搜索引擎更好的去訪問網站從而給網站的收錄產生一個好的作用。
5.2 sitemap.js
簡介:sitemap.js是高級站點地圖生成庫,可輕松創建網站地圖XML文件。
具體用法可以去github上看,這里不詳細深入,就是快速生成sitemap的一個工具(sitemap.js)
安裝:
yarn add sitemap -D
or
npm install sitemap -D
**5.3 結合prerender-spa-plugin postProcess方法自動生成網站sitemap **
postProcess:prerender-spa-plugin允許您在生成的html寫入文件前做個性化的處理,每個生成html的路由都會走進來一次。官方解釋
postProcess函數參數:content
- content.originalRoute: 重定向之前,原始路由
- content.route:重定向后的預渲染路由,也是文件輸出的位置,相對於staticDir,這個屬性是可以修改的
- content.html: 輸出的html內容
生成思路:每次生成html之前獲取當前html內容中的外鏈接,將其push進全局地址容器中,以便生成最終的sitemap。
5.4 sitemap需采集內容
需采集鏈接:
1.單頁面路由鏈接
2.網站html內容中的外鏈
** 5.5 實踐生成sitemap,修改build/webpack.prod.conf.js**
//需生成html的路由 const fs = require('fs'); const path = require('path') const sm = require('sitemap'); const PreRenderSPAPlugin = require('prerender-spa-plugin'); const Renderer = PreRenderSPAPlugin.PuppeteerRenderer; const routes = [ '/', '/about']; //sitemap url容器 const siteMapUrls = []; const resolve = dir => path.join(__dirname, '..', dir); const webpackConfig = merge(baseWebpackConfig, { plugins: [ new PreRenderSPAPlugin({ staticDir: resolve('dist'), routes, postProcess (context) { //content 參數 const {originalRoute, route, html} = context; //全局獲取href內容正則 const reg = /(?<=<a\s*.*href=")[^"]*(?=")/g; //過濾不包含http或https開頭的url const urlList = html.match(reg).filter(url => url.startsWith('http')); //將路由添加到全局sitemap容器 siteMapUrls.push(originalRoute); //將html中的外鏈添加到全局sitemap容器 if (urlList.length) { urlList.forEach(url => siteMapUrls.push(url)); } //當當前路由為最后一個生成路由時 if (route === routes[routes.length - 1]) { //去除重復的鏈接 let currentSiteMapUrls = Array.from(new Set(siteMapUrls)); //過濾掉鏈接中的錨點后內容 currentSiteMapUrls = currentSiteMapUrls.map(url => { const isMao = url.indexOf('#') > -1; //生成sitemap所需數據,具體參數參詳sitemap.js官網 return {url: isMao ? url.split('#')[0] : url, changefreq: 'weekly', priority: 0.5, lastmod: new Date().toLocaleDateString()} }); //生成siteMap文件 const siteMap = sm.createSitemap({ //路由前綴地址,全地址自動不會添加hostname(https://www.baidu.com不會添加hostname) hostname: 'https://www.test.com', cacheTime: 600000, //600 sec (10 min) cache purge period urls: currentSiteMapUrls }); //將sitemap文件添加搭配打包文件夾dist中 fs.writeFileSync(resolve('dist/sitemap.xml'), siteMap.toString()); } //返回當前contet對象 return context }, renderer: new Renderer({ inject: {}, //在 main.js 中 document.dispatchEvent(new Event('render-event')),兩者的事件名稱要對應上。 //render-event的作用就是在render-event事件執行后執行preRender renderAfterDocumentEvent: 'render-event', //puppeteer參數,標簽意思:完全信任在Chrome中打開的內容 args: ['--no-sandbox', '--disable-setuid-sandbox'] }) }), ] })
生成的sitemap.xml
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"> <url> <loc>https://www.test.com</loc> <priority>0.5</priority> <lastmod>2019-11-14</lastmod> <changefreq>weekly</changefreq> </url> <url> <loc>https://www.test.com/about</loc> <priority>0.5</priority> <lastmod>2019-11-14</lastmod> <changefreq>weekly</changefreq> </url> <url> <loc>https://www.baidu.com</loc> <priority>0.5</priority> <lastmod>2019-11-14</lastmod> <changefreq>weekly</changefreq> </url> </urlset>
總結:
prerender-spa-plugin 預加載適合於門戶型靜態網站,大型數據交互網站有seo需求的還是使用服務器渲染更好,這也只是一個快速解決seo的一個方案,還有生成sitemap,取頁面中的外鏈上述代碼只是簡單的取,需要更加完善的外鏈規則的自己可以定制化,這個方案用於解決門戶型小型網站使用單頁面構建不利於seo的痛點的簡單解決。
