「単一ページ」JS WebサイトとSEO


128

今日、強力な「単一ページ」JavaScript Webサイトを作成するためのクールなツールがたくさんあります。私の意見では、これはサーバーにAPIとして機能させ(それ以上は何もさせない)、クライアントにHTML生成のすべてのものを処理させることによって正しく行われます。この「パターン」の問題は、検索エンジンのサポートがないことです。私は2つの解決策を考えることができます:

  1. ユーザーがWebサイトにアクセスしたら、サーバーがクライアントのナビゲーションとまったく同じようにページをレンダリングできるようにします。したがって、http://example.com/my_path直接移動すると、サーバーは、/my_pathpushState を使用して移動した場合と同じようにレンダリングします。
  2. サーバーに、検索エンジンボット専用の特別なWebサイトを提供させます。通常のユーザーがhttp://example.com/my_pathサーバーにアクセスする場合は、JavaScriptを多用したバージョンのWebサイトを提供する必要があります。しかし、Googleボットがアクセスした場合、サーバーは、Googleにインデックス登録させたいコンテンツを含む最小限のHTMLを提供する必要があります。

最初のソリューションについては、ここでさらに説明します。私はこれをしているウェブサイトで働いていました、そしてそれはとても良い経験ではありません。それはDRYではなく、私の場合、クライアントとサーバーに2つの異なるテンプレートエンジンを使用する必要がありました。

私はいくつかの古いフラッシュWebサイトの2番目の解決策を見たと思います。私はこのアプローチが最初のアプローチよりもはるかに好きで、サーバーに適切なツールがあれば、非常に簡単に実行できます。

だから私が本当に思っているのは次のとおりです:

  • あなたはもっと良い解決策を考えることができますか?
  • 2番目のソリューションの欠点は何ですか?通常のユーザーと同じようにGoogleボットにまったく同じコンテンツを提供していないことがGoogleで判明した場合、検索結果で罰せられるのでしょうか?

回答:


44

#2は開発者にとって「より簡単」かもしれませんが、検索エンジンのクロールのみを提供します。そして、はい、Googleが提供しているさまざまなコンテンツを見つけた場合、罰せられる可能性があります(私はその専門家ではありませんが、それが起こっていると聞いています)。

SEOとアクセシビリティ(障害者だけでなく、モバイルデバイス、タッチスクリーンデバイス、およびその他の非標準のコンピューティング/インターネット対応プラットフォームを介したアクセシビリティ)は、どちらも同様の基本的な哲学を持っています。これらすべての異なるブラウザでアクセス、表示、読み取り、処理、またはその他の方法で使用されます)。スクリーンリーダー、検索エンジンクローラー、またはJavaScriptを有効にしたユーザーはすべて、問題なくサイトのコア機能を使用/インデックス作成/理解できるはずです。

pushState私の経験では、この負担は増えません。これは、かつての考えで「時間があれば」というものをWeb開発の最前線にもたらすだけです。

オプション#1で説明することは通常、最善の方法です。ただし、他のアクセシビリティやSEOの問題と同様pushStateに、JavaScriptを多用するアプリでこれを行うには、事前の計画が必要です。そうしないと、大きな負担になります。それは最初からページとアプリケーションアーキテクチャに組み込まれる必要があります-後付けは苦痛であり、必要以上に重複を引き起こします。

私はpushState最近、いくつかの異なるアプリケーションのためにSEOを使用していて、良いアプローチだと思いました。基本的にはアイテム#1に従いますが、html /テンプレートが重複していないことを示しています。

ほとんどの情報は、次の2つのブログ投稿にあります。

http://lostechies.com/derickbailey/2011/09/06/test-driving-backbone-views-with-jquery-templates-the-jasmine-gem-and-jasmine-jquery/

そして

http://lostechies.com/derickbailey/2011/06/22/rendering-a-rails-partial-as-a-jquery-template/

その要点は、ERBまたはHAMLテンプレート(Ruby on Rails、Sinatraなどを実行)をサーバー側のレンダリングに使用し、Backboneが使用できるクライアント側のテンプレートを作成することと、Jasmine JavaScript仕様を作成することです。これにより、サーバー側とクライアント側の間でのマークアップの重複がなくなります。

そこから、サーバーによってレンダリングされたHTMLでJavaScriptを機能させるために、いくつかの追加手順を実行する必要があります。提供されたセマンティックマークアップを取得し、JavaScriptで拡張します。

たとえば、で画像ギャラリーアプリケーションを構築していpushStateます。/images/1サーバーからリクエストすると、画像ギャラリー全体がサーバーにレンダリングされ、HTML、CSS、JavaScriptのすべてがブラウザーに送信されます。JavaScriptを無効にしている場合は、完全に正常に動作します。実行するすべてのアクションはサーバーに異なるURLを要求し、サーバーはブラウザーのすべてのマークアップをレンダリングします。ただし、JavaScriptを有効にしている場合、JavaScriptはサーバーによって生成されたいくつかの変数とともに既にレンダリングされたHTMLを取得し、そこから引き継ぎます。

次に例を示します。

<form id="foo">
  Name: <input id="name"><button id="say">Say My Name!</button>
</form>

サーバーがこれをレンダリングした後、JavaScriptがそれを取得します(この例ではBackbone.jsビューを使用)。

FooView = Backbone.View.extend({
  events: {
    "change #name": "setName",
    "click #say": "sayName"
  },

  setName: function(e){
    var name = $(e.currentTarget).val();
    this.model.set({name: name});
  },

  sayName: function(e){
    e.preventDefault();
    var name = this.model.get("name");
    alert("Hello " + name);
  },

  render: function(){
    // do some rendering here, for when this is just running JavaScript
  }
});

$(function(){
  var model = new MyModel();
  var view = new FooView({
    model: model,
    el: $("#foo")
  });
});

これは非常に単純な例ですが、全体として重要だと思います。

ページの読み込み後にビューをインスタンス化するとき、サーバーによってレンダリングされたフォームの既存のコンテンツを、ビューのとしてビューインスタンスにel提供します。私はいないレンダリング呼び出しまたはビューが生成したel最初のビューがロードされたときに、私のために。ビューが起動して実行され、ページがすべてJavaScriptになった後に使用できるrenderメソッドがあります。これにより、必要に応じて後でビューを再レンダリングできます。

JavaScriptを有効にした状態で[Say My Name]ボタンをクリックすると、警告ボックスが表示されます。JavaScriptがなければ、サーバーにポストバックし、サーバーはどこかでhtml要素に名前をレンダリングできます。

編集する

添付する必要があるリストがあるより複雑な例を考えてください(この下のコメントから)

<ul>タグにユーザーのリストがあるとします。このリストは、ブラウザがリクエストを行ったときにサーバーによってレンダリングされ、結果は次のようになります。

<ul id="user-list">
  <li data-id="1">Bob
  <li data-id="2">Mary
  <li data-id="3">Frank
  <li data-id="4">Jane
</ul>

次に、このリストをループして、バックボーンビューとモデルを各<li>アイテムにアタッチする必要があります。data-id属性を使用すると、各タグのモデルを簡単に見つけることができます。次に、このhtmlに接続するのに十分スマートなコレクションビューとアイテムビューが必要になります。

UserListView = Backbone.View.extend({
  attach: function(){
    this.el = $("#user-list");
    this.$("li").each(function(index){
      var userEl = $(this);
      var id = userEl.attr("data-id");
      var user = this.collection.get(id);
      new UserView({
        model: user,
        el: userEl
      });
    });
  }
});

UserView = Backbone.View.extend({
  initialize: function(){
    this.model.bind("change:name", this.updateName, this);
  },

  updateName: function(model, val){
    this.el.text(val);
  }
});

var userData = {...};
var userList = new UserCollection(userData);
var userListView = new UserListView({collection: userList});
userListView.attach();

この例では、UserListViewはすべての<li>タグをループして、それぞれに適切なモデルを持つビューオブジェクトをアタッチします。モデルの名前変更イベントのイベントハンドラーを設定し、変更が発生したときに要素の表示テキストを更新します。


サーバーがレンダリングしたHTMLを取得し、JavaScriptがそれを引き継いで実行するこの種のプロセスは、SEO、アクセシビリティ、pushStateサポートを実現するための優れた方法です。

お役に立てば幸いです。


要点はわかりますが、興味深いのは、「JavaScriptが引き継いだ」後にレンダリングがどのように行われるかです。より複雑な例では、クライアントでコンパイルされていないテンプレートを使用し、ユーザーの配列をループしてリストを作成する必要がある場合があります。ビューは、ユーザーのモデルが変更されるたびに再レンダリングされます。テンプレートを複製せずに(クライアントにビューをレンダリングするようサーバーに要求せずに)どうすればよいでしょうか?
user544941 '26 / 09/26

私がリンクした2つのブログ投稿は、クライアントとサーバーで使用できるテンプレートの作成方法をまとめて示しているはずです。重複する必要はありません。アクセス可能でSEO対応にする場合は、サーバーでページ全体をレンダリングする必要があります。サーバーによってレンダリングされたユーザーリストに添付するより複雑な例を含むように私の回答を更新しました
Derick Bailey

22

これが必要だと思います:http : //code.google.com/web/ajaxcrawling/

サーバーでjavascriptを実行してページを「レンダリング」し、それをgoogleに提供する特別なバックエンドをインストールすることもできます。

両方を組み合わせると、2回プログラミングすることなくソリューションを利用できます。(アプリがアンカーフラグメントを介して完全に制御可能である限り)。


実際、それは私が探しているものではありません。これらは最初のソリューションのいくつかの変形であり、前述したように、そのアプローチにはあまり満足していません。
user544941 2011

2
あなたは私の答え全体を読んでいませんでした。また、JavaScriptをレンダリングする特別なバックエンドを使用します。物事を2回書くことはありません。
アリエル

はい、読みました。しかし、私が正しく理解した場合、それはプログラムの1つの地獄です。なぜなら、pushStateをトリガーするすべてのアクションをシミュレートする必要があるからです。別の方法として、直接アクションを与えることもできますが、そうすれば、もうDRYにはなりません。
user544941 2011

2
基本的にはフロントのないブラウザだと思います。しかし、はい、プログラムをアンカーフラグメントから完全に制御可能にする必要があります。また、onClicksとともに、またはonClicksの代わりに、すべてのリンクに適切なフラグメントがあることを確認する必要があります。
アリエル

17

だから、主な関心事はドライです

  • pushStateを使用している場合は、サーバーがすべてのURL(画像を提供するためのファイル拡張子を含まないなど)に対して同じコードを送信するようにします。 "/ mydir / myfile"、 "/ myotherdir / myotherfile"またはroot "/ "-すべてのリクエストは、まったく同じコードを受け取ります。なんらかのURL書き換えエンジンが必要です。ほんの少しのhtmlを提供することもでき、残りはCDNから取得できます(require.jsを使用して依存関係を管理します-https://stackoverflow.com/a/13813102/1595913を参照してください)。
  • (リンクをURLスキームに変換してリンクの有効性をテストし、静的または動的ソースを照会してコンテンツの存在をテストします。有効でない場合は、404応答を送信します。)
  • リクエストがGoogleボットからのものでない場合は、通常どおり処理します。
  • リクエストがGoogleボットからのものである場合は、phantom.js-ヘッドレスWebキットブラウザー(「ヘッドレスブラウザーは、ビジュアルインターフェースのない、フル機能のWebブラウザーにすぎません。」)を使用して、サーバーでHTMLとJavaScriptをレンダリングし、 google bot結果のhtml。ボットがサーバー上の他の "pushState"リンク/ somepageにヒットする可能性のあるhtmlを解析する<a href="https://stackoverflow.com/someotherpage">mylink</a>ときに、サーバーはURLをアプリケーションファイルに書き換え、それをphantom.jsにロードし、結果のhtmlがボットに送信される、などです。 ..
  • あなたのhtmlについては、何らかのハイジャックで通常のリンクを使用していると想定しています(たとえば、backbone.jsで使用していますhttps://stackoverflow.com/a/9331734/1595913
  • リンクとの混乱を避けるために、jsonを提供するAPIコードを、api.mysite.comなどの別のサブドメインに分離します。
  • パフォーマンスを向上させるには、phantom.jsで同じメカニズムを使用して静的バージョンのページを作成することにより、時間外に検索エンジンのサイトページを前処理し、その結果、静的ページをGoogleボットに提供できます。<a>タグを解析できるいくつかのシンプルなアプリで前処理を行うことができます。この場合、URLパスを含む名前の静的ファイルの存在を簡単に確認できるため、404の処理はより簡単です。
  • #を使用する場合!サイトリンクのハッシュバン構文は、同様のシナリオが適用されますが、書き換えURLサーバーエンジンは、URL内の_escaped_fragment_を探し、URLスキームをURLスキーマにフォーマットします。
  • node.jsとgithub上のphantom.jsの統合がいくつかあり、node.jsをWebサーバーとして使用してHTML出力を生成できます。

次に、seoにphantom.jsを使用する例をいくつか示します。

http://backbonetutorials.com/seo-for-single-page-apps/

http://thedigitalself.com/blog/seo-and-javascript-with-phantomjs-server-side-rendering


4

Railsを使用している場合は、poirotを試してください。それは口ひげハンドルバーのテンプレートをクライアント側とサーバー側で再利用するのをとても簡単にする宝石です。

のようなビューでファイルを作成します_some_thingy.html.mustache

サーバー側のレンダリング:

<%= render :partial => 'some_thingy', object: my_model %>

テンプレートをクライアント側で使用できるようにします。

<%= template_include_tag 'some_thingy' %>

Rendreクライアント側:

html = poirot.someThingy(my_model)

3

少し異なる角度をとるために、2番目のソリューションは、アクセシビリティの点で正しいものです。JavaScriptを使用できないユーザー(スクリーンリーダーなど)に代替コンテンツを提供します。

これは自動的にSEOの利点を追加し、私の意見では、Googleは「いたずら」なテクニックとは見なしません。


そして、誰かがあなたが間違っていることを証明しましたか?コメントが投稿されてからしばらく経ちました
jkulak '23年

1

面白い。私は実行可能な解決策を探し求めていましたが、それはかなり問題があるようです。

私は実際にはあなたの2番目のアプローチにもっと傾いていました:

サーバーに、検索エンジンボット専用の特別なWebサイトを提供させます。通常のユーザーがhttp://example.com/my_pathにアクセスした場合、サーバーはJavaScriptを多用したバージョンのWebサイトをユーザーに提供する必要があります。しかし、Googleボットがアクセスした場合、サーバーは、Googleにインデックス登録させたいコンテンツを含む最小限のHTMLを提供する必要があります。

これが問題を解決するための私の見解です。動作することは確認されていませんが、他の開発者に洞察やアイデアを提供する可能性があります。

「プッシュステート」機能をサポートするJSフレームワークを使用していて、バックエンドフレームワークがRuby on Railsであるとします。シンプルなブログサイトがあり、検索エンジンですべての記事indexshowページをインデックスに登録したいとします。

あなたのルートが次のように設定されているとしましょう:

resources :articles
match "*path", "main#index"

すべてのサーバー側コントローラーが、クライアント側フレームワークの実行に必要な同じテンプレート(html / css / javascript / etc)をレンダリングするようにします。リクエストで一致するコントローラーがない場合(この例では、RESTfulな一連のアクションしかありませんArticlesController)、他のものと一致し、テンプレートをレンダリングして、クライアント側フレームワークにルーティングを処理させます。コントローラーを押すこととワイルドカードマッチャーを押すことの唯一の違いは、JavaScriptが無効なデバイスに要求されたURLに基​​づいてコンテンツをレンダリングする機能です。

私が理解していることから、ブラウザに表示されないコンテンツをレンダリングすることは悪い考えです。したがって、Googleがインデックスに登録すると、ユーザーはGoogleを経由して特定のページにアクセスし、コンテンツはありません。その場合、おそらくペナルティが課せられることになります。頭に浮かぶのは、CSSのdivノードでコンテンツをレンダリングすることですdisplay: none

ただし、これを単に行うかどうかは問題ではないと確信しています。

<div id="no-js">
  <h1><%= @article.title %></h1>
  <p><%= @article.description %></p>
  <p><%= @article.content %></p>
</div>

そしてJavaScriptを使用すると、JavaScriptが無効になっているデバイスがページを開いたときに実行されません。

$("#no-js").remove() # jQuery

このようにして、Googleと、JavaScriptが無効になっているデバイスを使用しているすべてのユーザーは、未加工/静的コンテンツを見ることができます。そのため、コンテンツ物理的に存在し、JavaScriptが無効になっているデバイスを使用しているすべてのユーザーに表示されます。

ただし、ユーザーが同じページにアクセスし実際 JavaScriptが有効になっている#no-js場合は、ノードが削除されるため、アプリケーションが乱雑になることはありません。次に、クライアント側のフレームワークがルーターを介してリクエストを処理し、JavaScriptが有効になっているときにユーザーに表示される内容を表示します。

これは有効でかなり簡単に使えるテクニックだと思います。それはあなたのウェブサイト/アプリケーションの複雑さに依存するかもしれませんが。

正しくない場合は修正してください。私は自分の考えを共有すると思いました。


1
まあ、最初にコンテンツを表示して少し後で削除した場合、おそらくエンドユーザーはブラウザでコンテンツが点滅/ちらつくことに気付くでしょう:)特に遅いブラウザの場合、巨大なサイズのHTMLコンテンツを表示/削除しようとすると、 JSコードが読み込まれて実行されるまでの遅延。あなたが思うこと?
Evereq 2013

1

サーバーサイドでNodeJSを使用し、クライアントサイドコードをブラウザ化し、各httpリクエスト(静的httpリソースを除く)のURIをサーバーサイドクライアント経由でルーティングして、最初の 'bootsnap'(状態のページのスナップショット)を提供します。jsdomのようなものを使用して、サーバー上のjquery dom-opsを処理します。bootsnapが戻った後、websocket接続をセットアップします。おそらく、クライアント側で何らかのラッパー接続を作成することにより、WebSocketクライアントとサーバー側クライアントを区別するのが最善です(サーバー側クライアントはサーバーと直接通信できます)。私はこのようなものに取り組んできました:https : //github.com/jvanveen/rnet/


0

使用Googleの閉鎖テンプレートは、ページをレンダリングします。javascriptまたはjavaにコンパイルされるため、クライアント側またはサーバー側のいずれかでページを簡単にレンダリングできます。すべてのクライアントとの最初の出会いで、htmlをレンダリングし、ヘッダーにリンクとしてJavaScriptを追加します。クローラーはhtmlのみを読み取りますが、ブラウザーはスクリプトを実行します。ブラウザーからの後続のすべてのリクエストは、APIに対して実行され、トラフィックを最小限に抑えることができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.