web-dev-qa-db-ja.com

「シングルページ」JS WebサイトおよびSEO

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

  1. ユーザーがWebサイトに入ると、サーバーは、ナビゲーション時にクライアントが行うとおりにページをレンダリングします。したがって、http://example.com/my_pathに直接アクセスすると、サーバーはクライアントと同じものをレンダリングします/my_path through pushState。
  2. サーバーが検索エンジンボット専用の特別なWebサイトを提供するようにします。通常のユーザーがhttp://example.com/my_pathサーバーは、WebサイトのJavaScriptヘビーバージョンを彼に提供する必要があります。ただし、Googleボットがアクセスした場合、サーバーは、Googleにインデックスを作成するコンテンツを含む最小限のHTMLを提供する必要があります。

最初の解決策についてさらに詳しく説明します here 。私はこれを行うウェブサイトに取り組んできましたが、それはあまりいい経験ではありません。 DRYではありません。私の場合、クライアントとサーバーに2つの異なるテンプレートエンジンを使用する必要がありました。

古き良きFlashウェブサイトの2つ目のソリューションを見たと思います。私はこのアプローチが最初のアプローチよりもはるかに好きであり、サーバー上の適切なツールを使用すると、非常に簡単に行うことができます。

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

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

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

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

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

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

私は最近、いくつかの異なるアプリケーションで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テンプレート(Rails、SinatraなどでRubyを実行)を使用し、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としてビューインスタンスに提供されます。私はnotレンダーを呼び出したり、最初のビューがロードされたときにビューにelを生成させます。ビューが起動して実行され、ページがすべてJavaScriptになった後に使用できるrenderメソッドがあります。これにより、必要に応じて後でビューを再レンダリングできます。

JavaScriptを有効にして[名前を言う]ボタンをクリックすると、警告ボックスが表示されます。 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サポートのために物事を順調に進める素晴らしい方法です。

お役に立てば幸いです。

44
Derick Bailey

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

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

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

22
Ariel

だから、主な懸念は乾燥しているようです

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

17
Leonidaz

Railsを使用している場合は、 poirot を試してください。 mustache または handlebars テンプレートのクライアント側とサーバー側を再利用するのが簡単になる逸品です。

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

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

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

クライアント側で使用するテンプレートを頭に入れてください。

<%= template_include_tag 'some_thingy' %>

Rendreクライアント側:

html = poirot.someThingy(my_model)
4
Tim Scott

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

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

3
Clive

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

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

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

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

「プッシュ状態」機能をサポートするJSフレームワークを使用しており、バックエンドフレームワークがRuby on Railsであると仮定します。シンプルなブログサイトがあり、検索エンジンにすべてのインデックスを付けたい場合記事indexおよびshowページ。

次のようにルートを設定したとします:

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

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

私が理解していることから、ブラウザに表示されないコンテンツをレンダリングすることは悪い考えです。したがって、Googleがインデックスを作成すると、ユーザーはGoogleを介して特定のページにアクセスし、コンテンツが存在しないため、ペナルティが科せられる可能性があります。頭に浮かぶのは、divノードでコンテンツをレンダリングすることですdisplay: noneのCSS。

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

<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が無効なデバイスを使用している場合、raw/staticコンテンツが表示されます。そのため、コンテンツは物理的にであり、JavaScriptが無効なデバイスを使用しているすべてのユーザーに表示されます。

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

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

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

1

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

1
Phrearch

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

0
Aleš Kotnik