«Одностраничные» сайты JS и SEO - PullRequest
128 голосов
/ 26 сентября 2011

В настоящее время существует множество крутых инструментов для создания мощных «одностраничных» сайтов 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 ]

44 голосов
/ 26 сентября 2011

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

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

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

22 голосов
/ 26 сентября 2011

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

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

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

17 голосов
/ 12 декабря 2012

Итак, похоже, что основной проблемой является DRY

  • Если вы используете pushState, ваш сервер должен отправить один и тот же точный код для всех URL (которые не содержат расширение файла дляобслуживать изображения и т. д.) "/ mydir / myfile", "/ myotherdir / myotherfile" или root "/" - все запросы получают одинаковый точный код.Вы должны иметь какой-то движок перезаписи URL.Вы также можете использовать чуть-чуть html, а остальное может прийти из вашего CDN (используйте require.js для управления зависимостями - см. https://stackoverflow.com/a/13813102/1595913).
  • (проверьте правильность ссылки, преобразовав ссылку в вашу схему URL).и проверка на наличие контента путем запроса статического или динамического источника. если он недействителен, отправьте ответ 404.
  • Если запрос не от бота Google, вы просто обрабатываете нормально.
  • Если запрос поступил от бота Google, вы используете phantom.js - браузер безголового webkit ( "Обозреватель без головы - это просто полнофункциональный веб-браузер без визуального интерфейса." ) для рендеринга htmlи javascript на сервере и отправьте в Google бот полученный html. Когда бот анализирует html, он может попасть на ваши другие ссылки / somepage "pushState" на сервере <a href="/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-путь.
  • Если вы используете #!Синтаксис хеш-взрыва для ссылок вашего сайта применяется аналогичный сценарий, за исключением того, что сервер перезаписи 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 голосов
/ 23 мая 2012

Если вы используете 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 голосов
/ 26 сентября 2011

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

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

1 голос
/ 26 июля 2013

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

1 голос
/ 17 августа 2012

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

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

Пусть сервер предоставит специальный сайт только для поисковика ботов. Если обычный пользователь посещает 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 включен.

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

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

0 голосов
/ 12 мая 2012

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

...