Backbone, Node.Js и SEO

  1. проблема
  2. решение
  3. Настройка приложения
  4. PreRender
  5. производительность
  6. Практический кейс

Платформы javascript типа Backbone.js или Angular.js находятся в полном разгаре и позволяют быстро и чисто создавать приложения, называемые одностраничными приложениями. Этот тип приложений в основном улучшает опыт ваших пользователей.

Когда пользователь хочет получить доступ к приложению, он отправляет запрос на веб-сервер, который отвечает обычной страницей HTML. Эта страница ссылается на файлы javascript Backbone, underscore.js, jquery и javascript, содержащие логику приложения на основе Backbone.

пример:

<! DOCTYPE html> <html lang = "en" xmlns = "http://www.w3.org/1999/xhtml"> <head> <charset meta = "utf-8" /> <meta name = "фрагмент "content ="! "> <title> </ title> </ head> <body> </ body> </ html> <script src =" http://underscorejs.org/underscore.js "> </ script > <script src = "http://code.jquery.com/jquery-2.1.3.js"> </ script> <script src = "http://backbonejs.org/backbone.js"> </ script > <script> // Javascript, соответствующий приложению Backbone </ script>

Код javascript, соответствующий приложению, затем выполняется браузером. Страница больше не перезагружается, все взаимодействия с сервером осуществляются через вызовы AJAX. URL-адрес браузера можно изменить с помощью якорей "#" или через PushState доступно с HTML5.

проблема

Хотя Google указывает в своем лучшие практики Поскольку сканеры анализируют файлы CSS и javascript, настоятельно рекомендуется размещать содержимое веб-сайта в тегах <body> страницы при его загрузке с веб-сервера.

пример:

<! DOCTYPE html> <html lang = "en" xmlns = "http://www.w3.org/1999/xhtml"> <head> <! - ... -> </ head> <body> Контент сайта, проиндексированный Google </ body> </ html>

Как видно из первого примера, теги <body> пусты. Контент будет поступать динамически во время создания Backbone.

Поэтому мы можем сделать вывод, что использование каркаса типа Backbone или Angular не подходит для индексации Google.

решение

К счастью, Google внедрил механизм индексации приложений на основе вызовов AJAX. Вся операция обобщена на страница разработчика Google ,

В итоге, 2 случая происходят:

- Если вы используете якоря в URL для навигации в приложении Backbone (пример URL: www.example.com/index.html # key = value ). В этом случае просто преобразуйте «#» в «#!» (Пример преобразованного URL: www.example.com/index.html #! ключ = значение ).

- Если вы используете HTML5 pushState (пример URL www.example.com/ Пользователь / 1 ), просто добавьте следующий метатег в <head> вашей страницы:

<meta name = "фрагмент" content = "!">

Так что, если Google встречает «#!» Или метатег выше, он будет вызывать вашу страницу с дополнительным параметром в URL:

- Пример "#!":

www.example.com/index.html#key=value становится

www.example.com/ajax.html? _escaped_fragment_ = ключ = значение

- Пример PushState:

www.example.com/ user / 1 становится

www.example.com/user/1 ? _escaped_fragment_ =

Параметр "_escaped_fragment" добавляется при вызове сервера. Этот параметр позволяет запускать другое поведение на стороне сервера для отображения статической страницы с читаемым Google контентом.

Настройка приложения

На стороне сервера мы будем использовать Node.Js для рендеринга необработанной HTML-страницы, содержащей javascript для пользователей и статической HTML-страницы для Google.

Проект Node.Js основан на библиотеке express.js быстро настроить веб-сервер. Проект можно скачать здесь или генерироваться через йомен ,

В нашем примере приложение Backbone является минималистичным и представляет одежду, созданную из непонятного воображаемого имени питомца (термин без конкуренции в Google).

Доступна демонстрация заполненной заявки. здесь ,

Архитектура проекта следующая:

Наше приложение Backbone находится в файле /public/groingrek/index.html. Она делает несколько звонков на сервер AJAX, чтобы получить информацию о нашем продукте:

<script> $ (document) .on ('click', 'a [href ^ = "/"]', function (event) {event.preventDefault (); Backbone.history.navigate (this .href);}) ; var eventAggregator = _.extend ({}, Backbone.Events); var Cara = Backbone.Model.extend ({}); Caras = Backbone.Collection.extend ({модель: Cara, URL: "http://aymeric.cloudapp.net/groingrek/characteristics"}); var CarasView = Backbone.View.extend ({tagName: "ul", render: function () {console.log (this); console.log (this .collection); _ (this .collection.models) .each (function) (cara) {//console.log(person); this. $ el.append (новый CaraView ({model: cara}). render (). el);}, this); вернуть this;}}); var CaraView = Backbone.View.extend ({tagName: "li", render: function () {this. $ el.html ("Name:" + this .model.get ("name") + "<br /> Значение: "+ this .model.get (" value ") +" <br /> Описание: "+ this .model.get (" description ")); вернуть this;}}); var MainModel = Backbone.Model.extend ({url: "http://aymeric.cloudapp.net/groingrek/description"}); MainView var = Backbone.View.extend ({tagName: "div", модель: MainModel, render: function () {this. $ El.html ("Name:" + this .model.get ("name") + " <br /> Описание: "+ this .model.get (" description ") +" br /> "+ 'twitter: <a href ="' + this .model.get ("twitter") + '"title = "Groingrex Twitter"> groingrek Twitter </a> <br /> '+' информация: <a href="./details" title="Groingrex features"> функции groingrek </a> '); верните это;}} ); var CarasRouter = Backbone.Router.extend ({route: {"details": function () {caras = new caras (); caras.fetch ({success: function () {$ ('body') .html (new) CarasView ({collection: caras}). Render (). El);}});}, "index": function () {var main = new MainModel (); main.fetch ({success: function () {$ ('body') .html (новый MainView ({model: main}). render (). el);}});}, "* path": function () {router.navigate ("index", {trigger : true});}}}); var router = new CarasRouter (); Backbone.history.start ({pushState: true, "root": "/ groingrek"}); </ script>

На стороне сервера определены маршруты для ответа на вызовы AJAX и для выпуска файла index.html (/route/index.html):

var express = require ('express'); var router = express.Router (); router.get ("/ groingrek / features", function (req, res, next)) {res.json ([{name: "size", значение: 'XL', описание: "Размер гроингрека - XL, он позволяет чтобы тучные мужчины могли демонстрировать свой любимый гроингрек "}, {name:" color ", value:" blue ", описание:" Великолепный цвет гроунграка позволяет быть принятым за зеленый гроингрек за его блестящее качество "}, { name: "shape", value: "conical", description: "Коническая форма нашего продукта действительно дает иллюзию, которую мы принимаем за нашего любимого зверя, гроингрек"}]);}); router.get ("/ groingrek / description", function (req, res, next) {res.json ({"name": "groingrek the ужасный", "photo": "http://aymeric.cloudapp.net/ images / groingrek.png "," description ":" Гроингрек - это домашнее животное с беспрецедентной мощностью, оно может съесть сотни фунтов мяса за очень короткое время, а гроингрек очень дружелюбный Продукт, который мы предлагаем за скромную сумму в 5 евро, является идеальным решением для того, чтобы стать самим гроингреком и ободрить своих друзей в своем гроингрекуити. "," Link ":" http: // aymeric.cloudapp.net/groingrek/characteristics "," twitter ":" https://twitter.com/groingrek "});}); router.get ("/ groingrek *", function (req, res, next) {res.sendFile ("./public/groingrek/index.html", {root: "../"});}); module.exports = router;

Здесь приложение полностью функционально для пользователей, но почти невидимо для Google.

PreRender

Для обработки случая параметра _escaped_fragment_ =, отправленного Google, мы будем использовать PreRender ,

Prerender может использоваться как услуга (бесплатно до определенного количества страниц), но он также может быть установлен как автономный на вашем сервере. Основано на PhantomJS браузер без графического интерфейса на основе WebKit, который будет воспроизводить JavaScript нашей HTML-страницы.

Цель здесь - развернуть этот сервис и использовать его из нашего веб-приложения.

Чтобы установить prerender, перейдите на страницу Выделенный GitHub и скачать исходники.

После загрузки удалите исходные коды на своем сервере и выполните следующую команду в корневом каталоге:

установка npm

Затем запустите службу Prerender с помощью команды:

узел server.js

Ваша услуга Prerender теперь доступна по адресу: HTTP: // локальный: 3000 ,

Чтобы проверить, что служба работает правильно, попробуйте получить доступ к следующему URL:

HTTP: // локальный: 3000 / HTTP: //aymeric.cloudapp.net/groingrek/index

Мы видим, что HTML-код, возвращаемый службой, содержит теги <body> с содержимым моей страницы, которое обычно создается на стороне клиента с помощью Backbone.

Мы видим, что HTML-код, возвращаемый службой, содержит теги <body> с содержимым моей страницы, которое обычно создается на стороне клиента с помощью Backbone

Теперь, когда сервис работает, мы должны изменить наше веб-приложение, чтобы использовать этот сервис, когда Google запросит страницу.

Начнем с установки Пакет Node.Js предоставлено Prerender:

npm установить prerender-node -save

Затем откройте файл app.js и добавьте следующую строку:

app.use (require ('prerender-node') .set ('prerenderServiceUrl', 'http: // localhost: 3000'));

выше линии:

app.use (logger ('dev'));

После перезапуска веб-приложения URL-адреса, использующие _escaped_fragment_, будут использовать Prerender.

Команда:

скручиваемость http://aymeric.cloudapp.net/groingrek/index

всегда возвращает наше приложение с пустым телом и весь javascript, используемый для сборки приложения.

всегда возвращает наше приложение с пустым телом и весь javascript, используемый для сборки приложения

С другой стороны, запрос:

curl http://aymeric.cloudapp.net/groingrek/index?_escaped_fragment_=

возвращает статическую HTML-страницу без JavaScript и с содержимым приложения в тегах <body>.

возвращает статическую HTML-страницу без JavaScript и с содержимым приложения в тегах <body>

производительность

На запрос, который вызывает сервис Prerender, мы замечаем, что время ответа плохое. Они приходят из времени обработки, выполненного PhrantomJS, до возвращения страницы.

время:

- http://aymeric.cloudapp.net/groingrek/index: около 10 мс

- http://aymeric.cloudapp.net/groingrek/index?_escaped_fragment_=: около 1 с

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

Это управление кешем предоставляется в сервисе Prerender благодаря Redis (установка Redis здесь ). Когда Redis установлен на вашем компьютере, выполните следующую команду в корне ранее установленной службы prerender:

npm установить prerender-redis-cache -save

Затем отредактируйте файл server.js для той же службы, добавив следующую строку:

server.use (prerender.httpHeaders ());

выше

server.start ();

Затем перезапустите службу Prerender.

Если мы перезапустим запросы по URL-адресу, содержащему _escaped_fragment_, первый запрос всегда будет занимать около 1 с, однако, для следующего, время будет между 10 мс и 20 мс.

Примечание. Без специальной конфигурации prerender-redis-cache использует адрес локального хоста и порт по умолчанию, все без аутентификации для подключения к серверу Redis.

Практический кейс

К сожалению, инструменты, предлагаемые Google в инструментах Wemasters, не управляют _escaped_fragment_. Сайт выглядит как пустой.

Для тестирования я поставил несколько ссылок на http://aymeric.cloudapp.net/groingrek/index в моем блоге, твиттере и т. д. Через несколько дней 2 страницы сайта будут проиндексированы и доступны в Google.

Через несколько дней 2 страницы сайта будут проиндексированы и доступны в Google

Дополнительная информация

В статье представлено использование Prerender с Node.Js и Backbone.Js, но операция идентична с такими фреймворками, как ember.js или AngularJS ,

На стороне Prerender доступны другие модули или файлы конфигурации для Nginx , рубин , апаш И т.д.

Html?
Net/groingrek/index?
Net/groingrek/index?