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

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

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

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

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

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

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

8 ответов

Хотя №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, 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. Это позволяет мне повторно визуализировать представление позже, если мне нужно.

Нажатие кнопки "Say My Name" с включенным 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 поддержка.

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

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

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

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

Таким образом, похоже, что основная проблема заключается в том, чтобы быть сухим

  • Если вы используете pushState, пусть ваш сервер отправляет один и тот же точный код для всех URL-адресов (которые не содержат расширение файла для обслуживания изображений и т. Д.) "/ Mydir/myfile", "/myotherdir/myotherfile" или root "/" - все запросы получают одинаковый точный код. Вы должны иметь какой-то движок перезаписи URL. Вы также можете использовать чуть-чуть html, а остальное может прийти из вашего CDN (используйте require.js для управления зависимостями - см. /questions/5163787/nastrojka-lyubogo-cdn-dlya-dostavki-tolko-odnogo-fajla-nezavisimo-ot-togo-kakoj-url-byil-zaproshen/5163802#5163802).
  • (проверьте правильность ссылки, преобразовав ссылку в вашу схему URL-адресов, и проверив наличие содержимого, запросив статический или динамический источник. Если он недействителен, отправьте ответ 404.)
  • Когда запрос не от бота Google, вы просто обрабатываете нормально.
  • Если запрос поступил от бота Google, вы используете phantom.js - браузер безглавого веб-набора ("Безглавый браузер - это просто полнофункциональный веб-браузер без визуального интерфейса.") Для рендеринга html и javascript на сервере и отправки гугл бот в результате HTML. Когда бот анализирует html-файл, он может попасть на другие ссылки "pushState" / somepage на сервере. <a href="/someotherpage">mylink</a>, сервер переписывает URL в файл вашего приложения, загружает его в phantom.js и полученный HTML-код отправляется в бот, и так далее...
  • Для вашего html я предполагаю, что вы используете обычные ссылки с каким-то угоном (например, с помощью backbone.js /questions/3572194/backbonejs-i-pushstate/3572215#3572215)
  • Чтобы избежать путаницы с любыми ссылками, разделите ваш API-код, который обслуживает JSON, на отдельный поддомен, например, api.mysite.com.
  • Чтобы повысить производительность, вы можете заранее обрабатывать страницы вашего сайта для поисковых систем в нерабочее время, создавая статические версии страниц, используя тот же механизм с phantom.js, и, следовательно, обслуживать статические страницы для ботов Google. Предварительная обработка может быть выполнена с помощью простого приложения, которое может анализировать <a> теги. В этом случае обработка 404 проще, так как вы можете просто проверить наличие статического файла с именем, содержащим URL-путь.
  • Если вы используете #! Синтаксис хэш-взрыва для ссылок вашего сайта применяется аналогичный сценарий, за исключением того, что ядро ​​сервера переписать URL будет искать _escaped_fragment_ в URL и будет форматировать URL для вашей схемы URL.
  • На github есть несколько интеграций node.js с phantom.js, и вы можете использовать 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

Если вы используете Rails, попробуйте пуаро. Это жемчужина, которая упрощает повторное использование шаблонов усов или рулей на стороне клиента и сервера.

Создайте файл в ваших представлениях как _some_thingy.html.mustache,

Рендеринг на стороне сервера:

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

Поместите шаблон в вашу голову для использования на стороне клиента:

<%= template_include_tag 'some_thingy' %>

Rendre на стороне клиента:

html = poirot.someThingy(my_model)

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

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

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

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

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

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

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

Допустим, ваши маршруты настроены так:

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

Убедитесь, что каждый серверный контроллер отображает тот же шаблон, который требуется для работы вашей клиентской инфраструктуры (html/css/javascript/etc). Если ни один из контроллеров не совпадает в запросе (в этом примере у нас есть только 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 включен.

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

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

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

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

Это может вам помочь: https://github.com/sharjeel619/SPA-SEO

Логика

  • Браузер запрашивает ваше одностраничное приложение с сервера, которое будет загружено из одного файла index.html.
  • Вы программируете некоторый код промежуточного сервера, который перехватывает клиентский запрос и определяет, пришел ли запрос от браузера или какого-то социального робота.
  • Если запрос пришел от какого-то робота-краулера, выполните вызов API на свой внутренний сервер, соберите необходимые данные, заполните эти данные в метатегах html и верните эти теги в строковом формате обратно клиенту.
  • Если запрос пришел не от какого-либо робота-краулера, просто верните файл index.html из папки build или dist вашего одностраничного приложения.
Другие вопросы по тегам