"Одностраничные" веб-сайты 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, чтобы свести к минимуму трафик.