"Одностраничные" сайты 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, что и обычный пользователь, то я буду наказан в результатах поиска?
8 ответов
Хотя №2 может быть "проще" для вас как разработчика, он обеспечивает только сканирование поисковыми системами. И да, если Google обнаружит, что вы обслуживаете другой контент, вас могут наказать (я не эксперт в этом, но я слышал об этом).
И SEO, и доступность (не только для людей с ограниченными возможностями, но и с помощью мобильных устройств, устройств с сенсорным экраном и других нестандартных вычислительных платформ / платформ с доступом в Интернет) имеют схожую основную философию: семантически богатая разметка, которая "доступна" (т. Е. Может доступ, просмотр, чтение, обработка или иное использование) для всех этих различных браузеров. Программа чтения с экрана, сканер поисковой системы или пользователь с включенным JavaScript должны иметь возможность без проблем использовать / индексировать / понимать основные функции вашего сайта.
pushState
по моему опыту, не добавляет к этому бремени. Это только выводит то, что раньше было второстепенным и "если у нас есть время", на передний план веб-разработки.
То, что вы описываете в варианте № 1, обычно лучше всего, но, как и другие проблемы с доступностью и SEO, делать это с pushState
в приложении с большим количеством JavaScript требует предварительного планирования, иначе это станет значительным бременем. Он должен быть встроен в архитектуру страницы и приложения с самого начала - модернизация болезненна и приведет к большему дублированию, чем необходимо.
Я работал с pushState
и SEO для пары разных приложений, и я нашел то, что считаю хорошим подходом. Он в основном соответствует вашему пункту №1, но учитывает отсутствие дублирования html / шаблонов.
Большую часть информации можно найти в этих двух сообщениях блога:
а также
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 вашего одностраничного приложения.