«Одностраничные» JS-сайты и SEO

128

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

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

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

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

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

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

Ответы:

44

Хотя №2 может быть «проще» для вас как разработчика, он обеспечивает только сканирование в поисковых системах. И да, если Google обнаружит, что вы обслуживаете другой контент, вас могут наказать (я не эксперт в этом, но слышал об этом).

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

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

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

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

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

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. Это исключает дублирование разметки между серверной и клиентской стороной.

Оттуда вам нужно предпринять несколько дополнительных шагов, чтобы ваш JavaScript работал с HTML, отображаемым сервером - истинное прогрессивное улучшение; взяв полученную семантическую разметку и улучшив ее с помощью JavaScript.

Например, я создаю приложение галереи изображений с помощью pushState. Если вы запросите /images/1с сервера, он отобразит всю галерею изображений на сервере и отправит все HTML, CSS и JavaScript в ваш браузер. Если у вас отключен JavaScript, он будет работать нормально. Каждое ваше действие будет запрашивать у сервера другой URL-адрес, и сервер будет отображать всю разметку для вашего браузера. Однако, если у вас включен 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. Это позволяет мне повторно визуализировать представление позже, если мне нужно.

Нажатие кнопки «Назови мое имя» при включенном 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>

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

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

Дерик Бейли
источник
Я понимаю вашу точку зрения, но что интересно, так это то, как выполняется рендеринг после того, как «ваш JavaScript берет верх». В более сложном примере вам, возможно, придется использовать некомпилированный шаблон на клиенте, просматривая массив пользователей для построения списка. Представление обновляется каждый раз, когда модель пользователя изменяется. Как бы вы это сделали, не дублируя шаблоны (и не прося сервер визуализировать представление для клиента)?
user544941
2 сообщения в блоге, на которые я ссылаюсь, должны вместе показать вам, как иметь шаблоны, которые можно использовать на клиенте и сервере - дублирование не требуется. серверу потребуется отобразить всю страницу, если вы хотите, чтобы она была доступной и оптимизированной для SEO. Я обновил свой ответ, включив в него более сложный пример присоединения к списку пользователей, который был отрисован сервером
Дерик Бейли
22

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

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

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

Ariel
источник
На самом деле, это не то, что я ищу. Это несколько вариантов первого решения, и, как я уже упоминал, я не очень доволен этим подходом.
user544941
2
Вы не прочитали весь мой ответ. Вы также используете специальный бэкэнд, который отображает для вас javascript - вы не пишете ничего дважды.
Ариэль
Да, я это читал. Но если бы я вас правильно понял, это была бы чертовски сложная программа, поскольку ей пришлось бы моделировать каждое действие, которое запускает pushState. В качестве альтернативы я мог бы напрямую передать ему действия, но тогда мы уже не такие СУХИЕ.
user544941
2
Думаю, это в основном браузер без передней панели. Но, да, вы должны сделать программу полностью управляемой из фрагментов привязки. Вам также необходимо убедиться, что все ссылки содержат правильный фрагмент вместе с 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 - браузер без заголовка ( «Безголовый браузер - это просто полнофункциональный веб-браузер без визуального интерфейса» ), чтобы отображать HTML и javascript на сервере и отправлять google bot полученный html. Когда бот анализирует html, он может попасть в другие ваши ссылки pushState / somepage на сервере <a href="https://stackoverflow.com/someotherpage">mylink</a>, сервер перезаписывает URL-адрес вашего файла приложения, загружает его в phantom.js, и полученный html отправляется боту, и так далее. ..
  • Для вашего html я предполагаю, что вы используете обычные ссылки с каким-то захватом (например, с использованием backbone.js https://stackoverflow.com/a/9331734/1595913 )
  • Чтобы избежать путаницы с любыми ссылками, выделите код api, который обслуживает json, в отдельный поддомен, например api.mysite.com
  • Для повышения производительности вы можете заранее обрабатывать страницы своего сайта для поисковых систем в нерабочее время, создавая статические версии страниц, используя тот же механизм, что и phantom.js, и, следовательно, обслуживать статические страницы для роботов Google. Предварительную обработку можно выполнить с помощью простого приложения, которое может анализировать <a>теги. В этом случае обработка 404 упрощается, так как вы можете просто проверить наличие статического файла с именем, содержащим путь URL.
  • Если вы используете #! С синтаксисом hash bang для ссылок вашего сайта применяется аналогичный сценарий, за исключением того, что серверный механизм перезаписи URL будет искать _escaped_fragment_ в URL-адресе и форматировать URL-адрес в соответствии с вашей схемой URL-адресов.
  • Есть несколько интеграций node.js с phantom.js на github, и вы можете использовать node.js в качестве веб-сервера для создания вывода html.

Вот пара примеров использования phantom.js для поисковой оптимизации:

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

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

Leonidaz
источник
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

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

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

Clive
источник
И кто-нибудь доказал, что вы неправы? Комментарий был опубликован уже
давно
1

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

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

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

Вот мой взгляд на решение проблемы. Хотя это не подтверждено как работающее, оно может дать некоторые идеи или идеи для других разработчиков.

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

Допустим, у вас настроены следующие маршруты:

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

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

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

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

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

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

Майкл ван Ройен
источник
1
Что ж, если вы сначала отображаете контент, а чуть позже удалите его, то, скорее всего, конечный пользователь может заметить, что контент мигает / мерцает в его браузере :) Особенно если это медленный браузер, огромный размер HTML-контента, который вы пытаетесь отобразить / удалить, и некоторые задержка перед загрузкой и выполнением вашего JS-кода. Что ты думаешь?
Evereq
1

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

Phrearch
источник
0

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

Алеш Котник
источник