Оптимизация сайта на MODX
Мне дали задание оптимизировать сайт https://mebmart.by для https://developers.google.com/speed/pagespeed/insights/. Я с задачей оптимизации сталкиваюсь первый раз. К сожалению, инструкций по оптимизации сайта на MODX не нашел. Поэтому хочу сделать такую инструкцию. Надеюсь, в комментариях, более опытные разработчика подскажут что и как еще можно сделать.
PageSpeed Insights мне написал:
Для Сократите HTML нашел плагин minifyHTML на событие OnWebPagePrerender:
Поставил компонент debugParser. Захожу на адрес mebmart.by/?debug=1 и вижу следующею раскладку:

Total parse time 0.4579089 s
Total queries 129
Total queries time 0.0209119 s
(Надо было запускать mebmart.by/?debug=1&cache=1 чтобы считало с кешом. Спасибо комментаторам.)
202мс занимает только вызов pdoPage. Как оказалось, на главной выводятся картинки товаров с функцией БЫСТРЫЙ ПРОСМОТР. А для того чтобы эта функция работала в чанке tpl.msProducts.row выводится сразу модальное окно с просмотром товара:
Для того чтобы преодолеть эту проблему решил подгружать модальное окно БЫСТРЫЙ ПРОСМОТР через ajax. Так как функция думаю востребованная написал сразу компонент AjaxModal. После этого скорость pdoPage упала до 88мс.

Total parse time 0.3473721 s
Total queries 92
Total queries time 0.0194972 s
PageSpeed Insights показывает
Почему-то для pdoPage установка параметров &cache=`1` &cacheTime=`3600` &context=`web` никак не влияют на скорость ответа. Возможно из-за &ajaxMode=`button`. При вызове pdoPage кешируемым скорость тоже не падает и к тому же пропадает кнопка показать еще.
Следующим этапом, думаю, перевести по крайней мере главную страницу на феном. Все его хвалят за скорость вот и посмотрим насколько он быстрее.
Более опытные пожалуйста скажите как еще можно оптимизировать сайт. И, кстати, сколько стоит оптимизация сайта? Мне сейчас предложили маленькую сумму, а чтоб ее увеличить нужно аргументы :).
UPD 07.08.18
pdoPage отрабатывает за 88мс и явно не кешируется. Полез разбираться и обнаружил 2 ошибки с кешем в нем.
Сделал вывод имен файлов кеша. Запрашиваемое имя и записываемое. Обнаружил:
Добавил
UPD 13.08.18
Взялся за оптимизацию изображений на главной. Сперва пробую tinyCompressor. Написал сниппет-модификатор [[+image_absolute:phpthumbon=`w=476&h=318&zc=1`:tinyCacheImage]], чтобы обрабатывать изображения во время первой загрузки страницы. Поправил ошибки в tinyCompressor https://modx.pro/components/14066#comment-100636. Тестирую и тут выясняется, что картинки, сжатые TinyPNG, не проходят в PageSpeed Insights. Он пишет нужно сжать еще.
В итоге, пробую другой компонент OptiPic. Сперва не заработал, но выяснилось, что я просто не весь ключ АПИ скопировал. В чанке my.galItemThumb, который для Gallery, сделал

Вывод надо было начинать с оптимизации картинок :). Они очень сильно влияют на оценку гугла. Все остальное уже в пределах 10 баллов.
Главная страница практически оптимизированна. Осталось оптимизировать категории и товары. И тут выяснялась засада :). Многие превью с разрешением 298х200 битые. Сейчас их перегенерирую.
В результате экспериментов на основе https://modx.pro/help/8244#comment-91916 сделал скрипт перегенерации превью
Затем главную картинку гружу через optipic
Total parse time 0.1609571 s. И откуда целая секунда берется не понятно :(.
Для того, чтобы все картинки сайта прогрузились и закешировались прогнал сайт через Screaming Frog SEO Spider.
В целом, все страницы в PageSpeed Insights набирают Good 80-94 балла. На оптимизацию потратил 30 с чем-то часов.
В следующий раз, думаю, оптимизирую гораздо быстрей. И, надеюсь, данная статья сократит работу над оптимизацией для Вас.
PageSpeed Insights мне написал:
ОптимизацияДля Сократите JavaScript, Сократите CSS использую MinifyX.
Low
11 / 100
Предложения по оптимизации
Оптимизируйте изображения
Используйте кеш браузера
Сократите время ответа сервера
Удалите код JavaScript и CSS, блокирующий отображение верхней части страницы
Сократите JavaScript
Сократите CSS
Сократите HTML
Включите сжатие
Для Сократите HTML нашел плагин minifyHTML на событие OnWebPagePrerender:
<?php
$output = $modx->resource->_output;
$output= preg_replace('|\s+|', ' ', $output);
$modx->resource->_output = $output;Следующий пункт за который взялся Сократите время ответа сервера. PageSpeed Insights пишет:По результатам тестирования время ответа вашего сервера составило 0,42 секунды.И рекомендованное время ответа сервера меньше 200 мс.
Поставил компонент debugParser. Захожу на адрес mebmart.by/?debug=1 и вижу следующею раскладку:

Total parse time 0.4579089 s
Total queries 129
Total queries time 0.0209119 s
(Надо было запускать mebmart.by/?debug=1&cache=1 чтобы считало с кешом. Спасибо комментаторам.)
202мс занимает только вызов pdoPage. Как оказалось, на главной выводятся картинки товаров с функцией БЫСТРЫЙ ПРОСМОТР. А для того чтобы эта функция работала в чанке tpl.msProducts.row выводится сразу модальное окно с просмотром товара:
<li>
<div class="block_img">
<a class="wrap_img" href="[[~[[+id]]]]">
[[+new:is=`1`:then=`<span class="title">Новинка</span>`]]
[[+popular:is=`1`:then=`<span class="title">Хит Продаж</span>`]]
[[+favorite:is=`1`:then=`<span class="title">Новогодняя цена</span>`]]
<img src="[[+thumb:default=`[[++assets_url]]components/minishop2/img/web/ms2_small.png`]]" width="2149" height="1440" alt="[[+pagetitle]]"/>
</a>
<a href="#product_popup[[+id]]" class="more fancybox">БЫСТРЫЙ ПРОСМОТР</a>
</div>
<h3><a href="[[~[[+id]]]]">[[+pagetitle]]</a></h3>
<div class="price">[[+price]] бел.руб. </div>
<!-- product_popup -->
<div class="product_popup" id="product_popup[[+id]]">
<!-- product_page -->
<div class="product_page">
<!-- wrap_product -->
<div class="wrap_product">
<div class="main_img zoom"><img src="[[+image:default=`[[++assets_url]]components/minishop2/img/web/ms2_small.png`]]" width="2149" height="1440" alt=""/></div>
<ul class="thumbs">
[[!pdoResources?
&class=`msProductFile`
&sortby=`id`
&sortdir=`asc`
&where=`{"product_id":"[[+id]]","type:=":"image","path:=":"[[+id]]/1500x1005/"}`
&tpl=`@INLINE <li><img src="[[+url]]"></li>`
]]
</ul>
</div>
<!-- /wrap_product -->
<!-- options -->
<div class="options">
<h2 class="title_tovar">[[+pagetitle]]</h2>
<div class="price">[[+price]] бел.руб <a href="[[~[[+id]]]]">Подробнее о товаре</a></div>
<form method="post" class="ms2_form">
<div class="count">
<span>Количество</span>
<select name="count">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select>
</div>
<button class="add_to_cart btn" type="submit" name="ms2_action" value="cart/add">ДОБАВИТЬ В КОРЗИНУ</button>
<input type="hidden" name="id" value="[[+id]]">
<input type="hidden" name="options" value="[]">
</form>
</div>
<!-- /options -->
</div>
<!-- /product_page -->
</div>
<!-- /product_popup -->
</li>Скорость pdoPage падает из-за вызова внутри чанка сниппета pdoResources. И к тому же по 5мс 18 раз тратится на вызов pdoResources. Для того чтобы преодолеть эту проблему решил подгружать модальное окно БЫСТРЫЙ ПРОСМОТР через ajax. Так как функция думаю востребованная написал сразу компонент AjaxModal. После этого скорость pdoPage упала до 88мс.

Total parse time 0.3473721 s
Total queries 92
Total queries time 0.0194972 s
PageSpeed Insights показывает
По результатам тестирования время ответа вашего сервера составило 0,34 секунды.Уменьшилось на 80мс, но все равно много :(. На этом мои успехи пока закончились.
Почему-то для pdoPage установка параметров &cache=`1` &cacheTime=`3600` &context=`web` никак не влияют на скорость ответа. Возможно из-за &ajaxMode=`button`. При вызове pdoPage кешируемым скорость тоже не падает и к тому же пропадает кнопка показать еще.
Следующим этапом, думаю, перевести по крайней мере главную страницу на феном. Все его хвалят за скорость вот и посмотрим насколько он быстрее.
Более опытные пожалуйста скажите как еще можно оптимизировать сайт. И, кстати, сколько стоит оптимизация сайта? Мне сейчас предложили маленькую сумму, а чтоб ее увеличить нужно аргументы :).
UPD 07.08.18
pdoPage отрабатывает за 88мс и явно не кешируется. Полез разбираться и обнаружил 2 ошибки с кешем в нем.
Сделал вывод имен файлов кеша. Запрашиваемое имя и записываемое. Обнаружил:
No cached data for key "resource/web/resources/89/4e61020450c4424d73d66aee8a9324bfe0a70960"cacheKey web/resources/89/4ccd1f1b0b32e54c8ce591eca2270b39c739fdfcЗапрашивается имя кеша одно, а записывается другое. Нашел, что имя кеша генерируется в файле core/components/pdotools/model/pdotools/pdotools.class.php функция protected function getCacheKey($options = array())return $key . '/' . sha1(serialize($options));где $options — это $scriptProperties сниппета. Для запрашиваемого и записываемого имени кеша $scriptProperties в функцию попадают разные. В сниппете pdoPage$data = $cache
? $pdoPage->pdoTools->getCache($scriptProperties)
: array();
//echo "<pre>".print_r($data,1)."</pre>";
if (empty($data)) {
$scriptProperties['setTotal'] = true;то есть после запроса кеша в $scriptProperties записывается setTotal и соответственно генерируемое имя файла кеша изменяется. Для обхода этой ошибки в функции getCacheKey перед генерацией имени файла кеша сделалunset($options['setTotal']);
return $key . '/' . sha1(serialize($options));Вторая ошибка: в $scriptProperties попадают параметры индивидуальные для каждого браузера[request] => Array
(
[browser] => standard
[modx_setup_language] => ru
[_ga] => GA1.2.1805674015.1532178758
[_ym_uid] => 1532178758631790336
[_ym_d] => 1532178758
[minishop2-category-grid-1] => {"start":0,"limit":"","action":"mgr/product/getlist","parent":"1"}
[minishop2-category-grid-90] => {"start":0,"limit":"","action":"mgr/product/getlist","parent":"90"}
[minishop2-category-grid-93] => {"start":0,"limit":"","action":"mgr/product/getlist","parent":"93"}
[PHPSESSID] => 1f01ac36**********************************
[_gid] => GA1.2.907764301.1533494910
[_ym_isad] => 1
)То есть для каждого браузера генерируется отдельный кеш. Смысл кеша от этого пропадает :). Добавил
unset($options['setTotal']);
unset($options['request']);
return $key . '/' . sha1(serialize($options));В итоге pdoPage из кеша грузиться 9мс и Total parse time 0.1053231 s. Под требования гугл проходит. Переводить на феном трудозатратно и большого смысла нет.UPD 13.08.18
Взялся за оптимизацию изображений на главной. Сперва пробую tinyCompressor. Написал сниппет-модификатор [[+image_absolute:phpthumbon=`w=476&h=318&zc=1`:tinyCacheImage]], чтобы обрабатывать изображения во время первой загрузки страницы. Поправил ошибки в tinyCompressor https://modx.pro/components/14066#comment-100636. Тестирую и тут выясняется, что картинки, сжатые TinyPNG, не проходят в PageSpeed Insights. Он пишет нужно сжать еще.
В итоге, пробую другой компонент OptiPic. Сперва не заработал, но выяснилось, что я просто не весь ключ АПИ скопировал. В чанке my.galItemThumb, который для Gallery, сделал
<div class="[[+cls]]">
<a href="[[+url]]" title="[[+name]]" [[+link_attributes]]>
<img class="[[+imgCls]]" src="[[+image_absolute:phpthumbon=`w=476&h=318&zc=1`:optipic]]" alt="[[+name]]" [[+image_attributes]] />
</a>
</div>И, теперь, PageSpeed Insights на эти файлы уже не ругается :). Так же оптимизировал картинки для pdoPage. Тут пришлось докупить мегабайты на optipic.io. Но 1гб за 225р — это по моему не дорого. Тестирую и вижу, что гугл оценивает уже 77 баллов из 100. От куда-то взялись не оптимизированные картинки с разрешением 1000х670. В вызове pdoPage делаю &includeThumbs=`298x200` и в чанке tpl.msProducts.row <img src="[[+298x200:default=`[[++assets_url]]components/minishop2/img/web/ms2_small.png`:optipic]]" alt="[[+pagetitle]]"/>Тестирую и уже 
Вывод надо было начинать с оптимизации картинок :). Они очень сильно влияют на оценку гугла. Все остальное уже в пределах 10 баллов.
Главная страница практически оптимизированна. Осталось оптимизировать категории и товары. И тут выяснялась засада :). Многие превью с разрешением 298х200 битые. Сейчас их перегенерирую.
В результате экспериментов на основе https://modx.pro/help/8244#comment-91916 сделал скрипт перегенерации превью
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
require_once dirname(__FILE__).'/config.core.php';
require_once MODX_CORE_PATH.'config/'.MODX_CONFIG_KEY.'.inc.php';
require_once MODX_CONNECTORS_PATH.'index.php';
$corePath = $modx->getOption('minishop2.core_path',null,$modx->getOption('core_path').'components/minishop2/');
require_once $corePath.'model/minishop2/minishop2.class.php';
$modx->miniShop2 = new miniShop2($modx);
$modx->lexicon->load('minishop2:default');
$c = $modx->newQuery('msProduct');
$c->where(array('class_key' => 'msProduct'));
$offset = $_GET['offset'];
if(!$offset) $offset=0;
$count = $modx->getCount('msProduct', $c);
$c->limit(5,$offset);
echo "$offset from $count
";
$offset += 5;
$products = $modx->getIterator('msProduct', $c);
$i = 0;
foreach ($products as $product) {
//if($i >10) break;
$i++;
// Получаем оригиналы их картинок
$files = $product->getMany('Files', array('parent' => 0));
foreach ($files as $file) {
//echo print_r($file->toArray(),1)."
";
// Затем получаем их преью
$children = $file->getMany('Children');
foreach ($children as $child) {
//echo print_r($child->toArray(),1)."
";
//удаляем файлы optipic
$fullpath = MODX_BASE_PATH . $child->url;
$path_arr = explode('/', $fullpath);
$file_name = array_pop($path_arr);
$optimized_file_name = 'op-' . $file_name;
$optimized_file = implode('/', $path_arr) . '/' . $optimized_file_name;
echo $optimized_file."
";
@unlink($optimized_file);
// Удаляем эти превью, вместе с файлами
$child->remove();
}
// И генерируем новые
$file->generateThumbnails();
// Если это первый файл в галерее - обновляем ссылку на превью товара
/** @var msProductData $data */
if ($file->get('rank') == 0 && $data = $product->getOne('Data')) {
$thumb = $file->getFirstThumbnail();
$data->set('thumb', $thumb['url']);
$data->save();
}
}
}
if($offset > $count) exit;
?>
<script>
location.href = "?offset=<?echo $offset;?>";
</script>На каталоге и категориях 89-92 балла. Осталось оптимизировать страницы товаров. Сейчас на них 42 балла. В первую очередь меняю превью галереи, которые грузились в полный размер 1000х670, меняю на 41х41. my.tpl.msGallery.row<li class="active"><img src="[[+41x41]]" data-src="[[+1000x670]]" alt="[[+name]]"/></li>И меняю ява-скрипт, чтобы при клике на превью в главную картинку прогружалось из data-src.Затем главную картинку гружу через optipic
<div class="main_img zoom"><img src="[[+image:default=`[[++assets_url]]components/minishop2/img/web/ms2_small.png`:optipic]]" width="2149" height="1440" alt=""/></div>В итоге, на странице товара 84 / 100 good. Только почему-то время ответа сервера 1,2 секунды :(. В debugParser Total parse time 0.1609571 s. И откуда целая секунда берется не понятно :(.
Для того, чтобы все картинки сайта прогрузились и закешировались прогнал сайт через Screaming Frog SEO Spider.
В целом, все страницы в PageSpeed Insights набирают Good 80-94 балла. На оптимизацию потратил 30 с чем-то часов.
В следующий раз, думаю, оптимизирую гораздо быстрей. И, надеюсь, данная статья сократит работу над оптимизацией для Вас.
Комментарии: 24
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
Ну и картинки. Скачайте, что вам гугл предлагает, посмотрите на качество.
Это очень плохо yadi.sk/i/rvBAyF2g3ZvLEq
Режьте нужные размеры при загрузке (как в ms2gallery или userfiles)
Прогоните изображения шаблона (типа таких mebmart.by/search.png) через tinypng.com/, например
Не работал с Gallery, но на каждую картинку слайдера идет запрос на коннектор — отсюда короткое время кеширования. может есть там возможность уже готовый статичный путь получать?
До этого еще просто не добрался. Завис на скорости ответа сайта.
Это вообще не понятно зачем такое. В модальном окне зум работает. Наверно разраб сайта под одну гребенку все сделал.
Тоже удивило. Здесь еще не копался. Только стандартные параметры сниппета смотрел. Наверно напишу свой сниппет для вывода галереи. А то стандартный сниппет работает аж 50мс. Что он там делает столько времени не понятно :(.
Пока больше интересует скорость работы. Кто-нибудь замерял насколько феном убыстряет?
pdoPage все равно 88-93мс занимает. Его надо еще сокращать. Правда стало Total parse time 0.1874032 s. Но у гугла время ответа сервера от 210мс до 420мс. Как еще сократить время pdoPage?
2) Вместо &includeThumb попробовать получать картинку непосредственно в чанке. И вообще, задаться вопросом, нужен ли этот дополнительный джоин (даже 2, помоему) в запросе.
На самом деле вариантов множество, нужно тестировать.
Достаточно просто msProducts.
Инклудить большие превьюшки тоже не нужно, раз в Ajax модалька, пусть там у товара image используется
Если изначально thumb небольшого размера — то можно перегенерировать.
как правило никто внутренние ссылки не проверяет через гугл)))
Можно поправить прямо в исходниках репозитория pdoTools на гитхабе.
Вот ссылка на файлик github.com/bezumkin/pdoTools/blob/master/core/components/pdotools/model/pdotools/pdotools.class.php#L1389
ну и создать пулл-регвест. Вроде по самому короткому пути так.
PS: кстати у меня открывается ваш сайт за ~400-500мсек.
А у меня время ответа сайта прыгает от 183 до 700 мс. В основном 180-195мс а иногда 300 или 700мс. debugParser ничего такого не показывает. У него от 100мс до 180мс. Как выяснить из-за чего прыгает?
В каких сниппетах он ещё используется?
Не знаю в каких. Наверно не в каких, но если вдруг где-то еще кеш используется, то очистка зависимости имени файла кеша от request не помешает.
И вообще включите на гитхабе issue и я бы вообще в этот код не лез.
Это хостинг. Можно развернуть локальное окружение и попробовать на своём ПС. Но в 90% это виноват хостинг. Если локально не получается, попробуйте на тестовом тарифе modhost.pro развернуть сайт, там процессор E3-1270 v6 последнего поколения, скачков во времени ответа быть вообще не должно.
А вот такой вопрос, как быть с картинками, которые проходят через phpThumbOn?
Я к тому, что я вставляю картинки так:
PageSpeed Insights для Mobile — пишет 100 баллов.
Для Desktop — пишет 84 и говорит, что мне нужно оптимизировать изображения. То есть они по факту получаются 300 на 300, а отображаются 282 на 282. Но в чем прикол? На мобильной версии, там еще меньше получается, то есть картинка-то 300х300, а уменьшается отображаемая картинка до 192х192.
Может кто подскажет, как это работает???
или img с srcset и sizes
responsiveimages.org/#implementation