SEOに強いフレームワーク開発

2017年7月17日

Javascript テクノロジー プログラミング

Googleが提唱しているSEOの強いサイトの特徴は、「速い」と「レスポンシブ」だ。 これは、明らかにスマートフォンユーザーの利便性を追求して、モバイル端末で見易いページを優先するというGoogleの会社の姿勢でもある事がよくわかる。 確かに、何かを検索する場面でスマートフォンを使う事が多い事は明確だが、当たり前だがPC表示を疎かにしていいわけではない。 マルチデバイス、マルチブラウザ、そのユーザーの環境に合わせたWEBサイトを作らなくてはいけないというのが、コンテンツメーカーに課せられた使命であると言っても過言ではないでしょう。

あまり知られていないSEOに強いサイトの特徴

とあるメディア・コンテンツの製作を担当している友人から、面白い話を聞きました。
1ページを複数ページに分割して、PV数をかさ増しするよりも、ページコンテンツを遷移する時に、history移動させずに、ajaxでソースコードを読んできて、ページが切り替わらないようなサイトを構築できると、Googleのクローラーは、ページ滞在時間が多いページは優良コンテンツとみなし、SEO順位が上昇する。
非常に興味深い話でその根拠を聞いてみた所、とあるサイトをスクラッチで作った時に、AJAX読み込み方式にすることで、メジャーなHOTワードだったにも関わらず、そのキーワードで検索順位が1位になることができたとのこと。 もちろん他の要因もあるかと思ったのだが、ページは極めてシンプルなため、ページ滞在時間でSEOが向上するのがよく理解できた。 新聞記事ページなどで、ページ分割しているコンテンツサイトをよく見かけるが、ページを分けてPVを向上させるよりも、滞在時間によりSEOを向上させて、結果的にPVも増えるという構造が望ましいという事。 もちろん、Googleが検索アルゴリズムを変更したら元も子もないのだが、今の時点で有効であれば、これを考慮したフレームワークを作っておけば、以後自分の構築するWEBサイトはSEOに強いサイトが作ることが可能になるという理屈だ。

プログラム構築の事前知識

そこで、今回は、ページの読み込みをAJAXを使ってWEBサイトのソースを取ってきて、その中の特定エレメントを差し替えるというシンプルな構成にしてみた。 ただし、このフレームワークで使う上での注意点は以下のとおりである。
・サイト全体でメニューやコンテンツ部分が同じ構成である事。 ・ID="contents"の内部ソースコードを入れ替える方式 ※このID値を持っていなければ通常遷移になる。 ・次ページに切り替わった後、JSを起動するようなページは動作しない ※JS実行をロジックに入れていないため
上記の条件があるため、一般的なブログシステムレベルを想定している。 少なくともwordpressよりは早くページ遷移できるのではないかと考えている。 そして、このプログラムは、以前とある会社で研究開発したプログラムを少し改良して作っている。 WebPageAccerelator このプログラムは、ページ読み込みをいかに高速化できるかを研究しているので、そのページ読み込み部分のロジックを拝借した。

ソースコード

/** * Title : LinkSourceGetter * Auther : yugeta@showcase-tv.com * Date : 2017.03.30 * Version : 1.0 / Body-change (2017.03.30) * Version : 1.1 / history-back , page-title (2017.07.13) */ ;(function(){ /** * start */ var $$ = function(){ // Debug-Message-- console.log("WPA-LinkSourceGetter : Start "); if(window.flgLinkSourceGetter == "set"){ console.log("WPA-LinkSourceGetter : set-over"); return; } //$$.dom.__construct(); this.setMutationObserver(); // page load-status check if(document.readyState === "complete"){ this.setPageLoaded(); } else{ this.setEvent(window , "DOMContentLoaded" , $$.prototype.setPageLoaded); } // URL History proc this.setEvent(window , "popstate" , $$.prototype.setHistoryBack); }; $$.prototype.urls = []; /** * loaded */ $$.prototype.setPageLoaded = function(){ var links = document.links; for(var i=0; i<links.length; i++){ $$.prototype.setLinkTags(links[i]); } // flg-set window.flgLinkSourceGetter = "set"; // URL-cache if($$.prototype.urls.length === 0 || $$.prototype.urls.indexOf(location.href) === -1){ $$.prototype.urls.push(location.href); } }; /** * DOM-event-proccess */ $$.prototype.setMutationObserver = function(){ var target = document.getElementsByTagName("html"); if(!target.length){return} var mo = new MutationObserver(function(mutationRecords){ for(var i=0; i<mutationRecords.length; i++){ for(var j=0; j<mutationRecords[i].addedNodes.length; j++){ // Image-Tag-Proc $$.prototype.setLinkTags(mutationRecords[i].addedNodes[j]); } } }); // Event-Set mo.observe(target[0] , {childList:true , subtree:true}); }; /** * set-link-tags */ $$.prototype.setLinkTags = function(elm){ // checc-elm if(!elm || elm.nodeType != 1){return} // check-flg if(elm.getAttribute("data-link-flg") == "set"){return} // check-tag if(elm.tagName != "A"){return} // check-href if(!elm.href){return} // domain-check var hrefs = elm.href.split("/"); if(hrefs.length < 2){return} if(hrefs[2] != location.host){return} // set-onclick elm.onclick = $$.prototype.procLinkClick; // set-flg elm.setAttribute("data-link-flg","set"); }; /** * proc-link-click */ $$.prototype.procLinkClick = function(){ var ajax = new $$ajax; ajax.set({ url:this.href, method:"GET", async:"true", onSuccess:$$.prototype.setSourceChange }); return false; }; /** * getLinkSource */ $$.prototype.setSourceChange = function(res){ // System console.log("WPA-LinkSourceGetter : [click] : " + this.url); // SetDOM var dom = document.createElement("html"); dom.innerHTML = res; // contentsチェック var elms = dom.getElementsByTagName("*"); var contents=null; for(var i=0; i<elms.length; i++){ if(elms[i].getAttribute("id") === "contents"){ contents = elms[i]; break; } } if(contents === null){ location.href = this.url; return; } // Address-URL change history.pushState(null,null,this.url); /** * headとbodyのソースを入れ替え定義(script対応版) */ var rootNode = document.getElementsByTagName("html")[0]; var domHTML = dom.getElementsByTagName("html")[0]; var domHead = dom.getElementsByTagName("head")[0]; var domBody = dom.getElementsByTagName("body")[0]; var docHead = document.getElementsByTagName("head")[0]; var docBody = document.getElementsByTagName("body")[0]; // // Body入れ替え // rootNode.replaceChild(domBody , docBody); // Contents入れ替え document.getElementById("contents").innerHTML = contents.innerHTML; // Title入れ替え document.title = dom.getElementsByTagName("title")[0].textContent; //スクロールtop window.scrollTo(0,0); // 読み込み後にリンク設定(ページ初期設定) $$.prototype.setPageLoaded(); }; /** * Ajax * $$.prototype.ajax.set({ * url:"", // "http://***" * method:"POST", // POST or GET * async:true, // true or false * data:{}, // Object * query:{}, // Object * querys:[] // Array * }); */ var $$ajax = function(){}; $$ajax.prototype.dataOption = { url:"", query:{}, // same-key Nothing querys:[], // same-key OK data:{}, // ETC-data event受渡用 async:"true", // [trye:非同期 false:同期] method:"POST", // [POST / GET] type:"application/x-www-form-urlencoded", // [text/javascript]... onSuccess:function(res){}, onError:function(res){} }; $$ajax.prototype.option = {}; $$ajax.prototype.createHttpRequest = function(){ //Win ie用 if(window.ActiveXObject){ //MSXML2以降用; try{return new ActiveXObject("Msxml2.XMLHTTP")} catch(e){ //旧MSXML用; try{return new ActiveXObject("Microsoft.XMLHTTP")} catch(e2){return null} } } //Win ie以外のXMLHttpRequestオブジェクト実装ブラウザ用; else if(window.XMLHttpRequest){return new XMLHttpRequest()} else{return null} }; // XMLHttpRequestオブジェクト生成 $$ajax.prototype.set = function(options){ if(!options){return} var ajax = new $$ajax; var httpoj = $$ajax.prototype.createHttpRequest(); if(!httpoj){return;} // open メソッド; var option = ajax.setOption(options); // 実行 httpoj.open( option.method , option.url , option.async ); // type httpoj.setRequestHeader('Content-Type', option.type); // onload-check httpoj.onreadystatechange = function(){ //readyState値は4で受信完了; if (this.readyState==4){ //コールバック option.onSuccess(this.responseText); } }; //query整形 var data = ajax.setQuery(option); //send メソッド if(data.length){ httpoj.send(data.join("&")); } else{ httpoj.send(); } }; $$ajax.prototype.setOption = function(options){ var option = {}; for(var i in this.dataOption){ if(typeof options[i] != "undefined"){ option[i] = options[i]; } else{ option[i] = this.dataOption[i]; } } return option; }; $$ajax.prototype.setQuery = function(option){ var data = []; if(typeof option.query != "undefined"){ for(var i in option.query){ data.push(i+"="+encodeURIComponent(option.query[i])); } } if(typeof option.querys != "undefined"){ for(var i=0;i<option.querys.length;i++){ if(typeof option.querys[i] == "Array"){ data.push(option.querys[i][0]+"="+encodeURIComponent(option.querys[i][1])); } else{ var sp = option.querys[i].split("="); data.push(sp[0]+"="+encodeURIComponent(sp[1])); } } } return data; }; /** * イベント処理(マルチブラウザ対応) * Event-Set * param @ target : Target-element * param @ mode : mode ["onload"->"load" , "onclick"->"click"] * param @ func : function **/ $$.prototype.setEvent = function(target, mode, func){ //other Browser if (target.addEventListener){target.addEventListener(mode, func, false)} else{target.attachEvent('on' + mode, function(){func.call(target , window.event)})} }; /** * History.back対応 */ $$.prototype.setHistoryBack = function(){ // 遷移したURLリストに存在するかの確認 if($$.prototype.urls.indexOf(location.href) !== -1){ $$.prototype.setHrefSource(location.href); } }; $$.prototype.setHrefSource = function(url){ if(!url){return} var ajax = new $$ajax; ajax.set({ url:url, method:"GET", async:"true", onSuccess:function(res){ // SetDOM var dom = document.createElement("html"); dom.innerHTML = res; // contentsチェック var elms = dom.getElementsByTagName("*"); var contents=null; for(var i=0; i<elms.length; i++){ if(elms[i].getAttribute("id") === "contents"){ contents = elms[i]; break; } } if(contents === null){ location.href = this.url; return; } /** * headとbodyのソースを入れ替え定義(script対応版) */ var rootNode = document.getElementsByTagName("html")[0]; var domHTML = dom.getElementsByTagName("html")[0]; var domHead = dom.getElementsByTagName("head")[0]; var domBody = dom.getElementsByTagName("body")[0]; var docHead = document.getElementsByTagName("head")[0]; var docBody = document.getElementsByTagName("body")[0]; // // Body入れ替え // rootNode.replaceChild(domBody , docBody); // Contents入れ替え document.getElementById("contents").innerHTML = contents.innerHTML; // 内部Script実行 // Title入れ替え document.title = dom.getElementsByTagName("title")[0].textContent; //スクロールtop window.scrollTo(0,0); // 読み込み後にリンク設定(ページ初期設定) $$.prototype.setPageLoaded(); // console.log("WPA-LinkSourceGetter : [history] : " + this.url); } }); }; new $$(); window.LinkSourceGetter = $$; })();

サンプルサイト

サンプルページのリンク サンプルページには上部に4つのリンクがあり、一番右のみ、contentsエレメントを含んでいない為、通常遷移になる。 ソレ以外の3つに関しては、contentsのみの差し替えになるし、ページ内の各種モジュールを読み込まないのでスピードも早い。

使い方

至って簡単で、上記axcel.jsをページのヘッダ部分にscriptタグ1行(ファイル読み込み)で追記するだけでいい。 id="contents"部分でなくて別のエレメントが希望の場合は、ソースコードの133行目、160m行目と312行目、336行目を書き換えてあげるとお望みの場所を切り替えることが可能になる。 まだまとまりきれていないコードなので、整理もできるが、とりあえず、効果を見るにはいいかもしれないので、これをwordpressに実装してみたり、自分でフレームワークを作ってみてもいいかもしれない。 今回は高速読み込みとページ遷移をコントロールできるモジュールのみを開発したのだが、これを実装できる便利で使いやすいフレームワークを開発中なので、完成したら、発表したいと思います。

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。