"Одностраничные" веб-сайты JS и SEO

В настоящее время существует множество интересных инструментов для создания мощных "одностраничных" сайтов JavaScript. На мой взгляд, это делается правильно, позволяя серверу действовать как API (и не более того) и позволяя клиенту обрабатывать все элементы генерации HTML. Проблема с этим "шаблоном" - отсутствие поддержки поисковой системы. Я могу представить два решения:

  • Когда пользователь входит на веб-сайт, пусть сервер отображает страницу точно так же, как клиент будет при навигации. Поэтому, если я перейду к http://example.com/my_path напрямую, сервер будет делать то же самое, что и клиент, если я перейду к /my_path через pushState.
  • Пусть сервер предоставляет специальный сайт только для ботов поисковой системы. Если обычный пользователь посещает http://example.com/my_path, сервер должен предоставить ему тяжелую версию сайта на JavaScript. Но если бот Google посещает, сервер должен предоставить ему минимальный HTML-код с содержимым, которое я хочу индексировать Google.

Первое решение обсуждается далее здесь. Я работаю над этим сайтом, и это не очень приятный опыт. Это не СУХОЙ, и в моем случае мне пришлось использовать два разных механизма шаблонов для клиента и сервера.

Я думаю, что я видел второе решение для некоторых хороших веб-сайтов Flash. Мне нравится этот подход намного больше, чем первый, и с правильным инструментом на сервере это можно сделать безболезненно.

Так что мне действительно интересно:

  • Можете ли вы придумать какое-нибудь лучшее решение?
  • Каковы недостатки второго решения? Если Google каким-то образом узнает, что я не обслуживаю точный контент для бота Google в качестве обычного пользователя, был ли я наказан в результатах поиска?

Ответ 1

В то время как # 2 может быть "проще" для вас как разработчика, он обеспечивает только сканирование поисковой системы. И да, если Google узнает о вашем обслуживании другого контента, вы можете быть оштрафованы (я не эксперт по этому поводу, но я слышал об этом).

И SEO, и доступность (а не только для людей с ограниченными возможностями, но доступность с помощью мобильных устройств, устройств с сенсорным экраном и других нестандартных вычислений/интернет-совместимых платформ) имеют аналогичную основную философию: семантически богатая разметка, которая является "доступной", (т.е. можно получить доступ, просмотреть, прочитать, обработать или использовать иным образом) для всех этих разных браузеров. Считыватель экрана, искатель поисковой системы или пользователь с включенным JavaScript должны иметь возможность использовать/индексировать/понимать функциональность ядра вашего сайта без проблем.

pushState не добавляет этого бремени, по моему опыту. Это только приносит то, что раньше было запоздалым, и "если у нас есть время" на переднем крае развития веб-сайтов.

То, что вы описываете в варианте №1, как правило, является лучшим способом - но, как и другие проблемы с доступом и SEO, делать это с помощью pushState в приложении с тяжелым JavaScript требуется предварительное планирование или оно станет значительным бремя. С самого начала его нужно испечь в архитектуре страницы и приложения. Дооснащение является болезненным и вызывает большее дублирование, чем это необходимо.

Недавно я работал с pushState и SEO для нескольких разных приложений, и я нашел то, что, по моему мнению, является хорошим подходом. Он в основном следует за вашим номером # 1, но учитывает не дублирование html/templates.

Большая часть информации содержится в этих двух блогах:

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, а также для моих спецификаций JavaScript Jasmine, Это сокращает дублирование разметки между стороной сервера и клиентской стороной.

Оттуда вам нужно предпринять несколько дополнительных шагов, чтобы ваш JavaScript работал с HTML, который отображается сервером - истинное прогрессивное улучшение; взяв семантическую разметку, которая была доставлена ​​и улучшена с помощью 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 для представления. Я не вызываю render или имею представление, генерирующее el для меня, когда загружается первое представление. У меня есть метод рендеринга, доступный после того, как представление запущено и запущено, а страница - это все JavaScript. Это позволяет мне повторно отобразить представление позже, если мне нужно.

При нажатии кнопки "Скажи мое имя" с включенным JavaScript будет выведено окно предупреждения. Без JavaScript он будет отправляться обратно на сервер, и сервер может отобразить имя в элементе html где-то.

Edit

Рассмотрим более сложный пример, в котором у вас есть список, который необходимо прикрепить (из комментариев ниже)

Скажите, что у вас есть список пользователей в теге <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>

Теперь вам нужно пройти через этот список и привязать вид и модель Backbone к каждому из элементов <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.

Надеюсь, что это поможет.

Ответ 2

Я думаю, вам нужно это: http://code.google.com/web/ajaxcrawling/

Вы также можете установить специальный бэкэнд, который "отображает" вашу страницу, запустив javascript на сервере, а затем отправит ее в Google.

Объедините обе вещи, и у вас есть решение, не программируя вещи дважды. (Пока ваше приложение полностью контролируется с помощью анкерных фрагментов.)

Ответ 3

Итак, кажется, что главная проблема заключается в том, что DRY

  • Если вы используете pushState, ваш сервер отправляет одинаковый точный код для всех URL-адресов (которые не содержат расширение файла для обслуживания изображений и т.д.) "/mydir/myfile", "/myotherdir/myotherfile" или root "/" - все запросы получают одинаковый точный код. У вас должен быть какой-то механизм перезаписи URL. Вы также можете использовать крошечный бит html, а остальные могут поступать из вашего CDN (с помощью require.js для управления зависимостями - см. fooobar.com/questions/46595/...).
  • (проверьте правильность ссылки, переведя ссылку на вашу схему URL-адресов и протестируйте ее против существования содержимого, запросив статический или динамический источник, если он недействителен, отправьте ответ 404.)
  • Когда запрос не из бота Google, вы просто обрабатываете его нормально.
  • Если запрос отправлен из бота Google, вы используете phantom.js - браузер безгласного webkit ( "Безглавой браузер - это просто полнофункциональный веб-браузер без визуального интерфейса".), чтобы отображать html и javascript на сервер и отправить google бот в результате html. Поскольку бот анализирует html, он может ударить ваши другие "pushState" ссылки /somepage на сервере <a href="/someotherpage">mylink</a>, сервер перезаписывает URL-адрес вашему файлу приложения, загружает его в phantom.js, и полученный html отправляется боту, и т.д.
  • Для вашего html я предполагаю, что вы используете обычные ссылки с каким-то захватом (например, с помощью backbone.js fooobar.com/questions/46613/...)
  • Чтобы избежать путаницы с любыми ссылками, отделите свой код api, который обслуживает json в отдельный субдомен, например. api.mysite.com
  • Чтобы повысить производительность, вы можете предварительно обрабатывать страницы своего сайта для поисковых систем заблаговременно в нерабочее время, создавая статические версии страниц, используя тот же механизм с phantom.js и, следовательно, обслуживайте статические страницы для ботов. Предварительная обработка может быть выполнена с помощью некоторого простого приложения, которое может анализировать теги <a>. В этом случае обработка 404 проще, поскольку вы можете просто проверить наличие статического файла с именем, содержащим URL-адрес.
  • Если вы используете #! синтаксис hash bang для вашего сайта ссылается на аналогичный сценарий, за исключением того, что механизм перезаписи url-сервера будет искать _escaped_fragment_ в URL-адресе и будет форматировать URL-адрес для вашей схемы URL-адресов.
  • Существует несколько интеграций node.js с phantom.js на github, и вы можете использовать node.js в качестве веб-сервера для вывода вывода html.

Вот несколько примеров, использующих phantom.js для seo:

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

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

Ответ 4

Если вы используете 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)

Ответ 5

Чтобы выбрать немного другой угол, ваше второе решение будет правильным с точки зрения доступности... вы бы предоставляли альтернативный контент пользователям, которые не могут использовать javascript (те, и т.д.).

Это автоматически добавит преимущества SEO и, на мой взгляд, не будет рассматриваться Google как "непослушный".

Ответ 6

Интересно. Я искал возможности для жизнеспособных решений, но это кажется довольно проблематичным.

Я действительно больше склонялся к вашему второму подходу:

Пусть сервер предоставляет специальный сайт только для поисковой системы ботов. Если обычный пользователь посещает http://example.com/my_path сервер должен дать ему тяжелую версию сайта на JavaScript. Но если Google бот посещений, сервер должен дать ему минимальный HTML с содержимое, которое я хочу индексировать Google.

Здесь я беру на себя решение проблемы. Несмотря на то, что это не подтверждено, он может дать некоторые идеи или идеи другим разработчикам.

Предположим, вы используете JS-инфраструктуру, которая поддерживает функциональность "push state", а ваша базовая инфраструктура - Ruby on Rails. У вас есть простой блог-сайт, и вы хотите, чтобы поисковые системы индексировали все ваши статьи index и show.

Скажем, у вас есть ваши маршруты, настроенные следующим образом:

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

Убедитесь, что каждый серверный контроллер отображает тот же шаблон, который требуется для выполнения вашей клиентской стороны (html/css/javascript/etc). Если ни один из контроллеров не согласован в запросе (в этом примере у нас есть только набор действий RESTful для ArticlesController), тогда просто сопоставьте что-нибудь еще и просто отрисуйте шаблон и позвольте клиентской стороне обработать маршрутизацию. Единственное различие между ударом контроллера и ударом совпадения с подстановочными знаками - это возможность отображать контент на основе URL-адреса, который был запрошен для устройств с отключенным JavaScript.

Из того, что я понимаю, это плохая идея для отображения содержимого, которое не видно браузерам. Поэтому, когда Google индексирует его, люди проходят через Google, чтобы посетить данную страницу, и нет никакого контента, тогда вы, вероятно, будете наказаны. Что приходит в голову, так это то, что вы создаете контент в div node, который вы 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, они будут видеть исходный/статический контент. Таким образом, контент физически существует и отображается всем, у кого есть отключенные с помощью JavaScript устройства.

Но, когда пользователь посещает одну и ту же страницу и на самом деле включен JavaScript, #no-js node будет удален, чтобы он не загромождал ваше приложение. Затем ваша клиентская среда обработает запрос через маршрутизатор и покажет, что пользователь должен увидеть, когда включен JavaScript.

Я думаю, что это может быть действительно и довольно простой способ использования. Хотя это может зависеть от сложности вашего веб-сайта/приложения.

Хотя, пожалуйста, поправьте меня, если это не так. Просто подумал, что разделю мои мысли.

Ответ 7

Используйте NodeJS на сервере, прокрутите код вашего клиента и проложите каждый URL-адрес http-request (за исключением статических http-ресурсов) uri через серверный клиент, чтобы предоставить первый "bootsnap" (моментальный снимок страницы, на которой он отображается). Используйте что-то вроде jsdom для обработки jQuery dom-ops на сервере. После возврата bootsnap настройте соединение с websocket. Вероятно, лучше всего провести различие между клиентом websocket и клиентским сервером, создав какое-то соединение с оболочкой на клиентском деле (клиентский сервер может напрямую взаимодействовать с сервером). Я работал над чем-то вроде этого: https://github.com/jvanveen/rnet/

Ответ 8

Используйте Google Closure Template для визуализации страниц. Он компилируется в javascript или java, поэтому его легко отобразить на стороне клиента или сервера. При первом столкновении с каждым клиентом выведите html и добавьте javascript в качестве ссылки в заголовке. Crawler будет читать только html, но браузер выполнит ваш script. Все последующие запросы из браузера можно было бы сделать против api, чтобы свести к минимуму трафик.