SEO для сайта на AngularJS своими руками
Одностраничные приложения на AngularJS всем хорошои, кроме того, что поисковые системы пока что не могут (или не хотят) нормально индексировать их страницы. Часть ботов не умеет яваскрипт полностью, другие - в очень ограниченном объёме.
В наличии имеется AJAX сайт за nginx на CentOS 6, хочется обойтись минимальными телодвижениями для обеспечения его индексации. Основная идея - исполнять приложение в каком-нибудь headless браузере и отдавать результат поисковику.
Подготовка к сканированию
В секцию head главной страницы вставляем <meta name=”fragment” content=”!” />
. Увидев такое, поисковики понимают,
что на этой странице есть содержимое, которое генерируется яваскриптом, но при этом доступное по запросу, сформированному
специальным образом. Подробности и полная спецификация есть у гугла и яндекса.
Вкратце, происходит вот что:
- Поисковик заходит на главную страницу сайта и видит мета-тэг 'fragment'
- Теперь все сссылки на этой странице, которые начинаются c
#!
будут заменениы на?_escaped_fragment=
. То есть, было#!/movies/1
становится?_escaped_fragment=/movies/1
. по спецификации еще положено, что значение параметра будет подвергнуто urlencode, но на практике роботы не всегда так делают - Поисковик запрашивает у сервера данные по ссылкам в новом формате и сервер должен отдавать такой же html, какой видит пользователь в браузере
Рендеринг HTML
Для получения хтмл будем использовать PhantomJS. Проще всего его установить через npm, у меня он уже был установлен,
если у вас нет, то yum install npm
или что-то этом роде, а потом
npm install phantomjs
PhantomJS управляется яваскриптом и к тому же имеет встроенный веб-сервер, чем мы и воспользуемся. Вот простейший скрипт для отрисовки страниц:
var server = require('webserver').create();
var port = 19003;
var getPage = function(url, callback) {
var page = require('webpage').create();
// запрашиваем страницу, ждём 200 мс, чтобы ангуляр успел завершить работу
// можно попросить сайт отмечать готовность самостоятельно, но это потребует изменения приложения
// а у нас все-таки простое решение
page.open(url, function() {
setTimeout(function() {
page.evaluate(function() {
// удаляем мета-тэг фрагмента, иначе контент не будет проиндексирован
// заодно удалим скрипты, потому что отдаём готовый html
$('meta[name=fragment], script').remove()
});
callback(page.content);
page.close();
}, 200);
});
};
// запускаем встроенный в phantomjs веб-сервер
server.listen(port, function(request, response) {
response.headers = {
'Content-Type': 'text/html'
};
var regexp = /_escaped_fragment_=(.*)$/;
var fragment = request.url.match(regexp);
var url = 'http://localhost:19002/#!' + decodeURIComponent(fragment[1]);
// получаем хтмл и отдаём роботу
getPage(url, function(content) {
response.statusCode = 200;
response.write(content);
response.close();
})
});
Запускаем скрипт
phantomjs renderbot.js
Проверяем результат работы
wget http://localhost:19003/?_escaped_fragment_=/movies/1
Можно и из браузера. Должен вернуться html полноценной страницы.
Отправлем решение на живой сервер
В nginx внесем небольшие изменения
if ($args ~* _escaped_fragment_) {
proxy_pass http://localhost:19003; #phantomjs
}
proxy_pass http://127.0.0.1:19002; #оригинальный сайт
В общем, алогритм такой:
- Nginx обрабатывает все запросы как обычно и передаёт их бэкэнду на порт 19002
- До тех пор, пока не встречает в урл
_escaped_fragment_
. очевидно, что это запрос от робота поисковика (ну или от кого-то очень любопытного), такой запрос nginx передаёт серверу phantomjs на порт 19003 - Наш скрипт для phantomjs вычленяет все содержимое урл от
_escaped_fragment_
до конца строки, раскодирует его и делает запрос уже с хэштэгом к бэкэнду, даёт неторое время на выполенения яваскрипта на странице и возвращает полученный хтмл обратно поисковому роботу
Что дальше?
В посте представлено решение в общих чертах, его уже можно применять в продакшене. Но вожможно, потребуется добавить кэширование отрисованных страниц и обработку ошибок (таймауты и тому подобное) в скрипте phantomjs. Можно сделать это самому, а можно воспользоваться различными сервисами, которые все сделают сами за некоторое вознаграждение, например, prerender.io сотоварищи. It's up to you.