Все началось с того, что мы запустили фронтенд нашего фонда на ангуляре. И, конечно, же у нас сразу появились проблемы с индексированием сайта в поисковиках, в том числе и в Google, несмотря, на их заявления о том, что googlebot теперь классно сканирует JS сайты.

В итоге, я пошел гуглить и попал на статью  Jesse Lawson — DIY AngularJS SEO with PhantomJS (the easy way!). В принципе, в основном, я следовал этой статье. И все бы хорошо, пока, PhantomJS не споткнулся об странный эксепшен ангуляра. Итак, что я сделал:

1. Установил PhantomJS

npm install phantomjs

2. Добавил специальный мета тег в index.html, чтобы дать знать поисковикам о том, что есть html версия этой страницы

<meta name="fragment" content="!"/>

3. Вытянул из репозитория steeve/angular-seo два файла

  • angular-seo.js
  • angular-seo-server.js

Так как у нас бекенд на Symfony, я положил эти два файла в папку web. Первый файл это модуль seo для AngularJS, а второй файл — скрипт запуска сервера для PhantomJS. В принципе, можно обойтись и без angular-seo модуля, но если у вас более сложная логика подгрузки возможно модуль вам пригодиться.

4. В итоге я зашел в ту папку, где у меня лежит angular-seo-server.js и запустил это хозяйство  так:

../../../node_modules/phantomjs/lib/phantom/bin/phantomjs --debug=yes --disk-cache=no angular-seo-server.js 9090 http://localhost:3000

где http://localhost:3000 это локальный сайт на котором запускается фронтенд. PhantomJS я установил прямо в корень проекта и он оказался в папке node_modules.

5. Далее, изменил конфиг nginx, чтобы запросы с параметром ?_escaped_fragment_= проксировались в фантом


location / {
    if ($args ~ escaped_fragment) {
        proxy_pass http://127.0.0.1:9090; 
    }        
}

6. Конечно, мы захотели иметь html5 красивые урлы, без хештега #. И поэтому включили html5 режим:

$locationProvider.html5Mode(true);

7. Попробовал протестировать что у нас получилось и открыл в браузере исходный код главной страницы:

view-source:http://localhost:3000/?_escaped_fragment_=

Все сработало как надо!

Но при открытии урлов типа

  • http://localhost:3000/about?_escaped_fragment_=
  • http://localhost:3000/contacts?_escaped_fragment_=

Рендеринг не работал!

Дебаг PhantomJS показывал такую ошибку:

Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"fn: function () {return $location.hash();}","newVal":"!%252525252F%2525252F%25252F%252F%2F/","oldVal":"!%2525252F%25252F%252F%2F/"}],[{"msg":"fn: function () {return $location.hash();}","newVal":"!%25252525252F%252525252F%2525252F%25252F%252F%2F/","oldVal":"!%252525252F%2525252F%25252F%252F%2F/"}],[{"msg":"fn: function () {return $location.hash();}","newVal":"!%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F/","oldVal":"!%25252525252F%252525252F%2525252F%25252F%252F%2F/"}],[{"msg":"fn: function () {return $location.hash();}","newVal":"!%252525252525252F%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F/","oldVal":"!%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F/"}],[{"msg":"fn: function () {return $location.hash();}","newVal":"!%25252525252525252F%252525252525252F%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F/","oldVal":"!%252525252525252F%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F/"}]]

http://errors.angularjs.org/1.5.8/$rootScope/infdig?p0=10&p1=%5B%5B%7B%22msg%22%3A%22fn%3A%20function%20()%20%7Breturn%20%24location.hash()%3B%7D%22%2C%22newVal%22%3A%22!%25252525252F%252525252F%2525252F%25252F%252F%2F%22%2C%22oldVal%22%3A%22!%252525252F%2525252F%25252F%252F%2F%22%7D%5D%2C%5B%7B%22msg%22%3A%22fn%3A%20function%20()%20%7Breturn%20%24location.hash()%3B%7D%22%2C%22newVal%22%3A%22!%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F%22%2C%22oldVal%22%3A%22!%25252525252F%252525252F%2525252F%25252F%252F%2F%22%7D%5D%2C%5B%7B%22msg%22%3A%22fn%3A%20function%20()%20%7Breturn%20%24location.hash()%3B%7D%22%2C%22newVal%22%3A%22!%252525252525252F%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F%22%2C%22oldVal%22%3A%22!%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F%22%7D%5D%2C%5B%7B%22msg%22%3A%22fn%3A%20function%20()%20%7Breturn%20%24location.hash()%3B%7D%22%2C%22newVal%22%3A%22!%25252525252525252F%252525252525252F%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F%22%2C%22oldVal%22%3A%22!%252525252525252F%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F%22%7D%5D%2C%5B%7B%22msg%22%3A%22fn%3A%20function%20()%20%7Breturn%20%24location.hash()%3B%7D%22%2C%22newVal%22%3A%22!%2525252525252525252F%25252525252525252F%252525252525252F%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F%22%2C%22oldVal%22%3A%22!%25252525252525252F%252525252525252F%2525252525252F%25252525252F%252525252F%2525252F%25252F%252F%2F%22%7D%5D%5D

После очередного прочтения статьи яндекса про индексирование аякс сайтов, стало понятно, что у нас в ссылках не используется #! и возможно поэтому PhantomsJS не может получить HTML снимок страницы.

Прочитав эту ветку в google groups, я решил выключить режим html5 и добавить префикс «!» в роутинг:

$locationProvider.html5Mode(false);
$locationProvider.hashPrefix('!');

И действительно, это помогло! Теперь при запросах

  • http://localhost:3000/?_escaped_fragment_=/about
  • http://localhost:3000/?_escaped_fragment_=/contacts

Я начал получать долгожданные HTML снимки!

Но теперь стал другой вопрос.

Как сделать так чтобы PhantomJS работал с включенным HTML5?

То что PhantomJS теперь работает как надо — это хорошо, но ведь мы, по прежнему, сильно хотим иметь красивые URLs, без всяких «#», «!».

В итоге решение оказалось очень простым. Даже не потребовалось костылей со стороны ангуляра. В файле angular-seo-server.js Стив ориентировался на то что фантом будет работать с урлами, содержащими «#», «!». Это можно увидеть в 48 строке:


var url = urlPrefix
      + request.url.slice(1, request.url.indexOf('?'))
      + '#!' + decodeURIComponent(route);

В итоге я просто удалил в файле angular-seo-server.js из 48-й строки код ‘#!’

var url = urlPrefix
    + request.url.slice(1, request.url.indexOf('?')) + decodeURIComponent(route);

Теперь можно включать

$locationProvider.html5Mode(true);

И PhantomJS будет работать как нам надо!

Как сделать так, чтобы facebook получал HTML версию страницу при расшаривании?

Чтобы facebook и другие соцсети получали HTML  версию страницу, надо сделать так чтобы Nginx понимал, что страницу запросил бот соцсети и перенаправлял бы такой запрос в фантом.

Наш админ помог мне, изменив конфиг таким образом:


location / {
        error_page 555 = @prerender;
        recursive_error_pages on;

        if ($args ~ escaped_fragment) {
            return 555;
        }
        if ($http_user_agent ~* "facebookexternalhit|twitterbot|vkShare|OdklBot") {
            return 555;
        }

        try_files /index.html =404;
        error_page 405 = $uri;
    }

    location @prerender {
        proxy_pass http://127.0.0.1:9090;
    }

Если вам помогла статья, вы можете поделитесь ее в соцсетях)