Советы по оптимизации сайта штатными средствами MODX
Всегда вызывайте сниппеты кешируемыми
Многие разработчики привыкли писать так:
[[!getResource? &parents=`5` &tpl=`tpl.news` ]]
Это в корне неверно. Убирайте восклицательные знаки везде. Исключение составляют те сниппеты, которые обрабатывают какую-то информацию, которая поступает от пользователя, т. е.:
- FormIt (пользователь заполняет форму и сниппет должен данные обработать);
- Login, Register (точно так же — обработка пользовательских данных, например, логин и пароль);
- getPages (ссылки на разные страницы формируются с GET-параметрами, типа /?page=3, соответственно сниппет должен обработать этот GET-параметр);
- Ну и другие сниппеты, например, на моем сайте — это сниппеты Loginza для авторизации и TicketsComments, с помощью которого можно оставлять комментарии к статье.
Отключите плагин phpThumbofCacheManager
Если вы используете сниппет phpThumbOf для уменьшения и обрезки изображений, отключите плагин phpThumbofCacheManager (он идет в комлекте пакета phpThumbOf).
Этот плагин удаляет все сгенерированные ранее картинки во время очистки кеша (а это происходит постоянно — при сохранении любых документов, например). Если он включен, то ваш аккаунт хостинга могут заблокировать за высокую нагрузку, помимо того, что страницы будут открываться очень долго — ведь картинки будут постоянно генерироваться заново.
Следите за временем генерации страницы
Добывьте в футер такой плейсхолдер: [^t^] — он будет показывать вам общее время генерации страницы. Если страница генерируется больше 1 секунды (или если обновить страницу, а время генерации больше 0,5 секунды), это плохо. Если советы выше не помогли, то читаем советы ниже про кеширование.
Совет. Вы можете заключить этот плейсхолдер в комментарий так:<!-- Render time: [^t^] -->И тогда на страницах сайта этой служебной информации не будет, но вы всегда сможете посмотреть ее в исходном коде страницы.
Продвинутая оптимизация. Управляем кешированием целых блоков.
Очень часто страницы сайта отличаются только содержанием — а остальные блоки на всех страницах одинаковые (например, боковые панели, на которых выводятся последние новости, какие-то баннеры и пр., футер, в котором дублируется меню, выводятся копирайты и счетчики или шапка, в которой у нас логотип, телефон и меню).
Так давайте скажем MODX, что эти блоки не нужно постоянно генерировать заново — пусть они сгенерируются один раз и на всех страницах просто выводятся.
Для этого создадим новый сниппет chunk:
$cache_key = "chunk_".$name;
$output = $modx->cacheManager->get($cache_key);
if (empty($output)) {
$output = $modx->getChunk($name, $scriptProperties);
$modx->cacheManager->set($cache_key,$output);
}
return $output;После этого возьмем наш шаблон и заменим в нем вызовы чанков нашим сниппетом:<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
[[$head]] [[$head]]
</head> </head>
<body> <body>
<header class="page"> <header class="page">
[[$header]] <!-- → --> [[chunk? &name=`header`]]
</header> </header>
<section class="page"> <section class="page">
[[$asideRight]] <!-- → --> [[chunk? &name=`asideRight`]]
[[$asideLeft]] <!-- → --> [[chunk? &name=`asideLeft`]]
<article> <article>
<h1>[[*longtitle]]</h1> <h1>[[*longtitle]]</h1>
<div id="content"> <div id="content">
[[*content]] [[*content]]
</div> </div>
<div class="clear"></div> <div class="clear"></div>
</article> </article>
</section> </section>
<footer class="page"> <footer class="page">
[[$footer]] <!-- → --> [[chunk? &name=`footer`]]
</footer> </footer>
</body> </body>
</html> </html>Чанк [[$head]] я не стал заменять, потому что у меня там жестко прописаны пути и устанавливаются TITLE, KEYWORDS, DESCRIPTION, а они на всех страницах разные.
Совет. В вызове Wayfinder добавьте &hereClass=``:[[Wayfinder? &startId=`0` &hereClass=``]]так как если первой будет открыта внутренняя страница, она получит класс active и будет подсвечиваться на всех остальных страницах.
Когда мы это сделали, можно открыть сайт и, тем самым, сгенерировать наш первый пользовательский кеш. Чтобы посмотреть, что из себя представляет кеш, загляните в папку /core/cache/default/ — там вы найдете по одному файлу для каждого чанка. Откройте эти файлы и посмотрите, что записалось в кеш )))
Исправляем меню
После наших действий сломалось меню на сайте — раньше на каждой странице в меню проставлялся класс «active» у тех пунктов, которые в данный момент активны. Сейчас же меню для всех страниц одинаково и класс «active» не проставляется нигде.
Исправим это с помощью JavaScript. Находим, где у нас вызывается Wayfinder и после него добавляем код, проставляющий нужные классы у текущих пунктов меню. Например, так:
<nav>
[[Wayfinder? &startId=`0` &hereClass=``]]
</nav>
<script type="text/javascript">
var tagLimit = 'nav'; // нужен, чтобы ограничить подъём по дереву DOM
var className = 'active'; // какой клас будем проставлять
var setActiveMenuItem = function ($li) {
var $liParent = $li.addClass(className).parent('ul').closest('li');
if ($li.closest(tagLimit).length && $li.length) {
setActiveMenuItem($liParent);
}
}
setActiveMenuItem($(tagLimit +' [href="[[~[[*id]]]]"]').closest('li'));
</script>Этот скрипт пройдет до ближайшего nav вверх и проставит всем нужным элементам списка класс «active» (то есть подходит и для многоуровневых меню).Итог
Применяя эти советы вы с легкостью будете делать сайты, которые не будут создавать нагрузку на сервер и будут открываться очень быстро. Например, сайт balirehab.ru/ открывается в среднем за 0.05 — 0.15 секунды. И это нормально для MODX))) G+
Объектная
Спасибо большое за статью!
Идея со сниппетом «chunk» очень интересная.
А можно провернуть такое со сниппетом, не с чанком?
Я вообще не использую чанки, у меня 3 статичных меню: верхнее, нижнее и боковое.
Это самописные сниппеты, вызываются [[Top_menu]], вот так. Меню мультиязычное.
То есть, можно ли как-то сказать MODX, чтоб он один раз их загрузил и чтоб потом лишние файлы не инклудились?
Менюхи ведь статичные, только вот мультиязычность сделана с помощью Babel.
Спасибо.
С праздником!
$lang = $modx->context->get('key'); $cache_key = "Top_menu_".$lang; $output = $modx->cacheManager->get($cache_key); if (empty($output)) { // Здесь код вашего сниппета. Само меню помещаем в переменную $output // Например, $output = $modx->runSnippet('Wayfinder', array('startId' => 0)); $modx->cacheManager->set($cache_key,$output); } return $output;И вас, кстати, с праздником)Попробую — отпишусь.
Дописав в них код, который вы дали?
Или вызывать как-то по-особому, вроде этого [[chunk? &name=`footer`]]
Распишите, пожалуйста, поподробнее и попроще, если несложно.
Спасибо.
Не могу понять, лучше стало, или нет.
Время загрузки не изменилось.
Извиняюсь, что не по теме.
Еще не вижу кэша чанков в /core/cache/default/ — просто нет такой папки, создал вручную, но тоже ничего в ней не появилось.
Куда копать?
На сайте есть еще вызов Wayfinder, в связи с этим вопрос: нормально ли, что после каждого вызова Wayfinder'a будет прописан скрипт? Просто они (меню) в разных местах или можно в скрипте контейнеры через запятую прописать?
Есть также и вызов getProducts, если его закешировать, не получится так, что при открытии разных категорий посетитель увидит один и тот же контент? Насколько я понял сниппет именно для статичных блоков, или я не прав.
Сразу скажу в программировании я не силен, поэтому приношу извинения за возможно глупые вопросы.
Ну вот, еще и иерархию нарушил…
Плагин phpThumbOfCacheManager отключили?
Классы в Wayfinder'e обновляются только после очистки кэша. WF вызывается на странице 5 раз (меню состоит из 5 кусков). А если зайти в карточку товара, то класс вообще не проставляется. Что-то я косячу…
&hereClass=`` — прописан
Первичная загрузка страницы ~ 5сек.
1. Можно-ли как-нибудь закешировать вызов getPage который вызывает getResources?
2. Как можно оптимизировать время генерации страницы, если у меня портал, с большим количеством выводов сниппетов: getResources (6 кешируемых вызовов) + вывод блога с помощью getPage + Мультиязычность + Опрос + simpleSearch + likeDislike + modxTalks и всё это на одной странице. В данный момент из базы загрузка около 3 секунд, а из кеща около 1 секунды.
3. Можно-ли кешировать Опросы, Поиск, Комментарии, Babel?
Спасибо.
Одно НО! — На JavaScript не работает!!! Точнее, работает только первую загрузку, после очистки кеша. Потом подсвечивается все время одна страница. &hereClass=`` есть. Первый раз классы выставляет JavaScript. Почему потом он не вносит изменения?
<script type="text/javascript"> var tagLimit = 'nav'; // нужен, чтобы ограничить подъём по дереву DOM var className = 'active'; // какой клас будем проставлять var setActiveMenuItem = function ($li) { var $liParent = $li.addClass(className).parent('ul').closest('li'); if ($li.closest(tagLimit).length && $li.length) { setActiveMenuItem($liParent); } } setActiveMenuItem($(tagLimit +' [href="[[~[[*id]]]]"]').closest('li')); </script>А где надо отключит phpThumbOfCacheManager в Modx 2.3.3
а как сделать если вот так:
<div class="col-xs-12 col-sm-8 col-md-9 column"> [[pdoCrumbs? &exclude=`2,113` &showAtHome=`0` &showHome=`1` &outputSeparator=`` &tpl=`@INLINE <li><b><a href="[[+link]]">[[+menutitle]]</a></b></li>` &tplCurrent=`@INLINE <li class="active">[[+menutitle]]</li>` &tplWrapper=`@INLINE <ul class="breadcrumb">[[+output]]</ul>` ]] </div><nav class="col-xs-12 col-sm-8 col-md-9 column"> [[pdoCrumbs? &exclude=`2,113` &showAtHome=`0` &showHome=`1` &outputSeparator=`` &tpl=`@INLINE <li><b><a href="[[+link]]">[[+menutitle]]</a></b></li>` &tplCurrent=`@INLINE <li class="active">[[+menutitle]]</li>` &tplWrapper=`@INLINE <ul class="breadcrumb">[[+output]]</ul>` ]] </nav>Причем в cache чанк он складывает, но не отображает его.
Помогите, с системой MODX пока только знакомлюсь.
Спасибо.
Ниже приведен код сниппета:
<?php /** @var array $scriptProperties */ // Convert parameters from Wayfinder if exists if (isset($startId)) { $scriptProperties['parents'] = $startId; } if (!empty($includeDocs)) { $tmp = array_map('trim', explode(',', $includeDocs)); foreach ($tmp as $v) { if (!empty($scriptProperties['resources'])) { $scriptProperties['resources'] .= ',' . $v; } else { $scriptProperties['resources'] = $v; } } } if (!empty($excludeDocs)) { $tmp = array_map('trim', explode(',', $excludeDocs)); foreach ($tmp as $v) { if (!empty($scriptProperties['resources'])) { $scriptProperties['resources'] .= ',-' . $v; } else { $scriptProperties['resources'] = '-' . $v; } } } if (!empty($previewUnpublished) && $modx->hasPermission('view_unpublished')) { $scriptProperties['showUnpublished'] = 1; } $scriptProperties['depth'] = empty($level) ? 100 : abs($level) - 1; if (!empty($contexts)) { $scriptProperties['context'] = $contexts; } if (empty($scriptProperties['context'])) { $scriptProperties['context'] = $modx->resource->context_key; } // Save original parents specified by user $specified_parents = array_map('trim', explode(',', $scriptProperties['parents'])); if ($scriptProperties['parents'] === '') { $scriptProperties['parents'] = $modx->resource->id; } elseif ($scriptProperties['parents'] === 0 || $scriptProperties['parents'] === '0') { if ($scriptProperties['depth'] !== '' && $scriptProperties['depth'] !== 100) { $contexts = array_map('trim', explode(',', $scriptProperties['context'])); $parents = array(); if (!empty($scriptProperties['showDeleted'])) { $pdoFetch = $modx->getService('pdoFetch'); foreach ($contexts as $ctx) { $parents = array_merge($parents, $pdoFetch->getChildIds('modResource', 0, $scriptProperties['depth'], array('context' => $ctx))); } } else { foreach ($contexts as $ctx) { $parents = array_merge($parents, $modx->getChildIds(0, $scriptProperties['depth'], array('context' => $ctx))); } } $scriptProperties['parents'] = !empty($parents) ? implode(',', $parents) : '+0'; $scriptProperties['depth'] = 0; } $scriptProperties['includeParents'] = 1; $scriptProperties['displayStart'] = 0; } else { $parents = array_map('trim', explode(',', $scriptProperties['parents'])); $parents_in = $parents_out = array(); foreach ($parents as $v) { if (!is_numeric($v)) { continue; } if ($v[0] == '-') { $parents_out[] = abs($v); } else { $parents_in[] = abs($v); } } if (empty($parents_in)) { $scriptProperties['includeParents'] = 1; $scriptProperties['displayStart'] = 0; } } if (!empty($displayStart)) { $scriptProperties['includeParents'] = 1; } if (!empty($ph)) { $toPlaceholder = $ph; } if (!empty($sortOrder)) { $scriptProperties['sortdir'] = $sortOrder; } if (!empty($sortBy)) { $scriptProperties['sortby'] = $sortBy; } if (!empty($permissions)) { $scriptProperties['checkPermissions'] = $permissions; } if (!empty($cacheResults)) { $scriptProperties['cache'] = $cacheResults; } if (!empty($ignoreHidden)) { $scriptProperties['showHidden'] = $ignoreHidden; } $wfTemplates = array( 'outerTpl' => 'tplOuter', 'rowTpl' => 'tpl', 'parentRowTpl' => 'tplParentRow', 'parentRowHereTpl' => 'tplParentRowHere', 'hereTpl' => 'tplHere', 'innerTpl' => 'tplInner', 'innerRowTpl' => 'tplInnerRow', 'innerHereTpl' => 'tplInnerHere', 'activeParentRowTpl' => 'tplParentRowActive', 'categoryFoldersTpl' => 'tplCategoryFolder', 'startItemTpl' => 'tplStart', ); foreach ($wfTemplates as $k => $v) { if (isset(${$k})) { $scriptProperties[$v] = ${$k}; } } //--- /** @var pdoMenu $pdoMenu */ $fqn = $modx->getOption('pdoMenu.class', null, 'pdotools.pdomenu', true); $path = $modx->getOption('pdomenu_class_path', null, MODX_CORE_PATH . 'components/pdotools/model/', true); if ($pdoClass = $modx->loadClass($fqn, $path, false, true)) { $pdoMenu = new $pdoClass($modx, $scriptProperties); } else { return false; } $pdoMenu->pdoTools->addTime('pdoTools loaded'); $cache = !empty($cache) || (!$modx->user->id && !empty($cacheAnonymous)); if (empty($scriptProperties['cache_key'])) { $scriptProperties['cache_key'] = 'pdomenu/' . sha1(serialize($scriptProperties)); } $output = ''; $tree = array(); if ($cache) { $tree = $pdoMenu->pdoTools->getCache($scriptProperties); } if (empty($tree)) { $data = $pdoMenu->pdoTools->run(); $data = $pdoMenu->pdoTools->buildTree($data, 'id', 'parent', $specified_parents); $tree = array(); foreach ($data as $k => $v) { if (empty($v['id'])) { if (!in_array($k, $specified_parents) && !$pdoMenu->checkResource($k)) { continue; } else { $tree = array_merge($tree, $v['children']); } } else { $tree[$k] = $v; } } if ($cache) { $pdoMenu->pdoTools->setCache($tree, $scriptProperties); } } if (!empty($tree)) { $output = $pdoMenu->templateTree($tree); } if ($modx->user->hasSessionContext('mgr') && !empty($showLog)) { $output .= '<pre class="pdoMenuLog">' . print_r($pdoMenu->pdoTools->getTime(), 1) . '</pre>'; } if (!empty($toPlaceholder)) { $modx->setPlaceholder($toPlaceholder, $output); } else { return $output; }<nav> [[pdoMenu? &startId=`0` &hereClass=``]] </nav> <script type="text/javascript"> var tagLimit = 'nav'; // нужен, чтобы ограничить подъём по дереву DOM var className = 'active'; // какой клас будем проставлять var setActiveMenuItem = function ($li) { var $liParent = $li.addClass(className).parent('ul').closest('li'); if ($li.closest(tagLimit).length && $li.length) { setActiveMenuItem($liParent); } } setActiveMenuItem($(tagLimit +' [href="[[~[[*id]]]]"]').closest('li')); </script>А если сайт полностью на fenom (ну или почти полностью:) ), то как-то изменится синтаксис оптимизации?
{'chunk' | snippet : ['name' => 'header']}Зачем использовать какой-то лишний сниппет?
И писать постоянно его вызов. Когда проще и быстрее написать вызов чанка. Причем всё чаще начинаю встречать бездумное применение этого совета в поддерживаемых сайтах.
Прироста скорости и уменьшения нагрузки — ну не смеши. На 0.0001 сек. быстрее?)) И прям пррисходит великая нагрузка при генирации кэша чанка?
Лучше посоветуй перейти с устаревшего wayfinder на pdoMenu.
Я это всё проверял. Исправьте
Письмо уходит, но очень долго. 30 сек.висит страница. Намучилась уже. как ускорить процесс?
в строке &hooks=`spam,email` надо убрать spam.
т.к. в formit идет обращение к api.stopforumspam.org/, а он недоступен и вылетает по таймауту.