Разработка SEO оптимизированных высокопроизводительных фронтенд приложений на базе 1С-Битрикс.

Оптимизация Битрикса по рекомендациям Google Page Speed

Артем Житник

Артем Житник

Photo by Diggity Marketing on Unsplash

Более качественные сайты показываются выше в поисковой выдаче. Такие инструменты как Google PageSpeed и Lighthouse в Chrome позволяют выявить проблемы, замедляющие страницы, вызывающие неудобства в их использовании пользователями. Давайте пробежимся по типичным рекомендациям и опишем способы их решения в Битрикс проекте.

Serve images in next-gen formats

Предлагается вместо jpeg и png использовать более продвинутые форматы графики, например webp, что обеспечит при прочих равных лучшую компрессию, соответственно меньший размер файлов. В php должен быть установлен плагин для работы с webp. Стандартным образом в Битриксе не получится получать webp картинки, придется городить костыли. Можно например, сделать обертку над стандартным методом получения предварительных просмотров CFile::ResizeImageGet():

namespace MyNamespace;

use Bitrix\Main\Application;
use Bitrix\Main\Config\Option;
use CFile;

if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();

class PreviewHelper {

    /**
     * Подобие стандартного уменьшателя картинок CFile::ResizeImageGet.
     * Помимо jpg создает еще webp копию.
     *
     * @param int|string      $file ID в таблице файлов или путь к файлу
     * @param array           $arSize
     * @param int             $resizeType
     * @param bool            $bInitSizes
     * @param array|bool      $arFilters
     * @param bool            $bImmediate
     * @param int|string|bool $quality
     * @return mixed
     * @see \CFile::ResizeImageGet
     *
     */
    public static function resizeImageWebp(
        $file,
        array $arSize,
        $resizeType = BX_RESIZE_IMAGE_PROPORTIONAL,
        $bInitSizes = false,
        $arFilters = false,
        $bImmediate = false,
        $quality = false
    ) {
        if (is_numeric($file) || is_array($file)) {
            $result = CFile::ResizeImageGet($file, $arSize, $resizeType, $bInitSizes, $arFilters, $bImmediate, $quality);
        } else {
            $uploadDirName = Option::get("main", "upload_dir", "upload");
            $fileName = basename($file);
            $subdir = "iblock/" . substr($fileName, 0, 3);
            $cacheDir = "{$arSize["width"]}_{$arSize["height"]}_$resizeType" . (is_array($arFilters) ? md5(serialize($arFilters)) : "");
            $cacheImageFile = Application::getDocumentRoot() . "/$uploadDirName/resize_cache/$subdir/$cacheDir/$fileName";

            // Перезаписываем не каждый раз, а только если исходный jpg изменился
            if (!file_exists($cacheImageFile) || filemtime($cacheImageFile) < filemtime($file)) {
                CFile::ResizeImageFile(
                    $file,
                    $cacheImageFile,
                    $arSize,
                    $resizeType,
                    [],
                    $quality,
                    $arFilters
                );
            }

            $result = [
                "src" => str_replace(Application::getDocumentRoot(), "", $cacheImageFile),
            ];
        }

        if ($result === false) {
            return $result;
        }

        if ($quality === false) {
            $quality = 95;
        }

        $previewMainPath = Application::getDocumentRoot() . $result["src"];
        $result["src_webp"] = self::replaceExtension($result["src"], "webp");

        self::copyPictureToWebp($previewMainPath, $quality);

        return $result;
    }

    /**
     * Меняет расширение у файла на новое
     *
     * @param string $path
     * @param string $newExtension
     * @return string
     */
    public static function replaceExtension(string $path, string $newExtension): string {
        $info = pathinfo($path);

        return "{$info["dirname"]}/{$info["filename"]}.$newExtension";
    }

    /**
     * Создается копия исходной картинки в формате webp в том же самом каталоге
     *
     * @param string     $originalPath
     * @param int|string $quality
     * @return string Возвращает полный путь к файлу webp
     */
    public static function copyPictureToWebp(string $originalPath, $quality = 95): string {
        $webpPath = self::replaceExtension($originalPath, "webp");

        // Перезаписываем webp не каждый раз, а только если исходный jpg изменился
        if (file_exists($webpPath) && filemtime($webpPath) >= filemtime($originalPath)) {
            return $webpPath;
        }

        switch (exif_imagetype($originalPath)) {
            case IMAGETYPE_PNG:
                $previewMain = imagecreatefrompng($originalPath);
                break;

            case IMAGETYPE_JPEG:
            default:
                $previewMain = imagecreatefromjpeg($originalPath);
        }

        imagewebp($previewMain, $webpPath, $quality);

        return $webpPath;
    }
}

Reduce unused JavaScript/CSS

Часто, разработчики Битрикс проектов, собирают фронт как конструктор Lego, из отдельных кубиков-библиотек, которые несут в себе много избыточного кода. Мы можем настроить сборку js через какой-нибудь бандлер, типа webpack. Webpack подтягивает только используемые функции, классы и т.д. Это отдельная большая тема, обязательно к ней вернусь в будущих публикациях.

Если по простому, некоторые библиотеки позволяют скачать кастомные сборки. Тот же Bootstrap, позволяет получить бандл онлайн. Галочками отмечаете какие компоненты вас интересуют и нажимаете в самом низу страницы "Compile and Download". Но это, конечно, полумеры, лучше самому все собирать.

Еще один прикол, Битрикс в публичной части может отдавать css, который нужен только для админки (или админ панели). Для обычного посетителя это стили никогда не понадобятся, их можно выпилить таким костылем:

//Optimization.php

namespace MyNamespase;

if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();

/**
 * Оптимизация css/js
 * @link https://gdecider.github.io/articles-bx-site-optimisation-for-google-pagespeed-insights.html
 */
class Optimization {

    /**
     * Убираем из страницы стили ядра битрикса для обычных пользователей
     * @param $content
     */
    public static function deleteKernelCss(&$content) {
        global $USER, $APPLICATION;
        if (UserHelper::userIsPersonal($USER)) {
            return;
        }
        if (strpos($APPLICATION->GetCurDir(), "/bitrix/") !== false) {
            return;
        }

        $arPatternsToRemove = [
            '/<link.+?href=".+?kernel_main\/kernel_main(_v\d+)?\.css\?\d+"[^>]+>/',
            '/<link.+?href=".+?bitrix\/js\/main\/core\/css\/core[^"]+"[^>]+>/',
            '/<link.+?href=".+?bitrix\/templates\/[\w\d_-]+\/styles.css[^"]+"[^>]+>/',
            '/<link.+?href=".+?bitrix\/templates\/[\w\d_-]+\/template_styles.css[^"]+"[^>]+>/',
        ]; 
        $content = preg_replace($arPatternsToRemove, "", $content); 
    } 
}
//init.php

use Bitrix\Main\EventManager;

EventManager::getInstance()->addEventHandler(
    "main",
    "OnEndBufferContent", [
        MyNamespace\Optimization::class,
        "deleteKernelCss",
    ]
);

Efficiently encode images

Тут речь и идет о том, что картинки могут быть облегчены за счет большей компрессии, либо за счет уменьшения избыточного размера (ширины и высоты). Страница может быть сверстана как адаптивная, подходящая под разные размеры экранов. В этом случае, одна и та же картинка подстраиваясь под экран может в одном случае иметь эффективный размер, а в других - нет. Тогда нужно подготовить несколько вариантов картинок и описать их в атрибуте srcset тега img. Scrset позволяет задавать условия показа каждой картинки, например в зависимости от размера экрана.

Reduce initial server response time

Долгий ответ сервера может быть связан с большим количеством запросов к базе данных, выполняемых для построения страницы. Могут присутствовать медленные запросы. Необходимо проверить работу кеша компонентов, выяснить какие части приложения осуществляют эти запросы.

С этим поможет справится панель администрирования Битрикса, меню "Отладка/Суммарная статистика". Для каждого компонента будет выведено время выполнения и количество выполненных запросов к базе. Стоит задуматься, почему тот или иной компонент выполняет запросы, не кешируя данные, которые должны быть кешированы. На это может быть несколько причин, например постоянно меняются входные параметры компонента. Либо, запросы выполняются в некешируемой части, в component_epilog.php.

Также возможны варианты выполнения запросов на уровне модулей на каждой странице. Определить их можно по плашке с общими показателями по странице внизу страницы слева. Примером может служить модуль AB тестирование, который работает на каждой странице. Если нам такой функционал прямо сейчас не нужен, можно удалить этот модуль. При желании его всегда можно вернуть (установочные файлы останутся).

Для самых медленных запросов определить источник, причину такой работы. Возможно какие-то таблицы сильно разрослись. На одном из проектов в котором я участвовал, таблица значений свойств инфоблоков содержала 6,5 млн записей. Когда эта таблица, даже с учетом наличия индексов, участвовала в ряде запросов, это мягко говоря не летало. Значит нужно ее сократить - переместить значения свойств каждого инфоблока в отдельные таблицы (это стандартный функционал Битрикса).

При нормальном кешировании, можно добиться выполнения всего нескольких легких запросов - до 10. До нуля не получится снизить.

Eliminate render-blocking resources

Ресурсы которые блокируют рендеринг контента страницы должны быть минимизированы. Например, не критические блоки <script></script> должны быть с атрибутом async.

Preconnect to required origins

Можно сообщать браузеру, что мы будем подключаться к внешним хостам. Используем атрибут preconnect, например:

<link rel="preconnect" href="https://mc.yandex.ru" crossorigin="anonymous">

Браузер таким образом более оптимально выполнит dns запросы и сократит накладные расходы на установку соединения.

Minify JavaScript/CSS

Тут понятно, js и css должны быть минифицированы. Минификация многих файлов из ядра уже сделана, именно они подтягиваются. В настройках главного модуля нужно включить галочки минификации js и css, для получения подсушенных версий также для файлов шаблона сайта. Также Битрикс позволяет объединять css и js в общие файлы для уменьшения количества запросов необходимых для получения всех стилей и скриптов.

Webpack несомненно более продвинутый инструмент для оптимизаций такого рода. Однако он требует дополнительных танцев. Битриксовый механизм работает из коробки.

Reduce the impact of third-party code

Типичная страница изобилует всякого рода помощниками: метриками, внешними виджетами, пикселами и т.д. Каждый из таких инструментов замедляет страницу. Нужно определиться может от чего-то можно избавиться? Либо не включать только на отдельных страницах.

Serve static assets with an efficient cache policy

Срок кеширования статики на клиенте рекомендуется делать большим. Если есть доступ к конфигам nginx, установим кеш в 1 год:

location ~ \.(jpg|JPG|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|svg|woff|woff2|ttf|eot|webp|html)$ {
    expires 1y;
}

Если доступа к nginx нет, попросите поддержку хостинга внести изменения в настройки.

Image elements do not have explicit width and height

Если размеры картинок явно не указаны, при загрузке страницы происходят сдвиги интерфейса по мере подгрузки картинок. Это может подбешивать пользователей, мешать им сосредоточиться на тексте, нажимать на ссылки и т.д. Нужно стремиться к тому, чтоб места где должны располагаться картинки были либо пустыми, либо содержали заглушки. В любом случае необходимо указывать размеры, либо пропорции картинок, если они адаптивные, чтоб избежать нежелательных эффектов загрузки.

Avoid enormous network payloads

Старайтесь избегать размещения больших файлов, тем более в верстке. Соединения могут быть медленные, да и дорогие. Ощущение от загрузки страницы с картинкой в 1МБ могут быть смазаны. Рекомендуется держать размер страницы не более 1,6МБ. Картинки должны быть оптимизированы, js/css минифицированы, статика кеширована.

Avoid an excessive DOM size

Большой DOM может тормозить на медленных устройствах, вроде телефонов. Идеально держать количество элементов DOM меньше 800 штук. Страницы бывают сложными, с большими списками, каждый элемент которого - иерархия вложенных элементов. Нужно сокращать эти списки, сокращать вложенность, убирать лишние элементы-обертки.

Выводы

Не стоит игнорировать рекомендации Google PageSpeed, они полезны не только для бездушных гигантов-поисковиков, но и для ваших любимых посетителей. Комплекс мероприятий по оптимизации необходим если вы не хотите терять покупателей из-за технических моментов, потенциальных тормозов на медленном железе или медленном канале связи.

В Битриксе еще есть так называемый композитный режим. Его суть заключается в разделении страницы на каркас (неизменную статическую часть которая загружается быстро) и контент загружаемый дополнительным ajax запросом. Работающий в штатном режиме композит дает ощущение быстрой загрузки страницы. Однако, в некоторых случаях страдает SEO, так как поисковый бот не всегда может дожидаться ajax запроса, а довольствоваться только пустым скелетом страницы. Да и на хорошем железе, работа в режиме композита и без него мало различимы.

А можно поступить радикально, и заменить голову Битрикса на Next.js приложение. Битрикс останется в виде админки. Многие из вышеописанных оптимизаций в Next.js идут из коробки: оптимизация картинок, ленивая их загрузка, конвертация в webp на лету, минификация js/css, неиспользуемые js/css скорее всего не попадут в билд, быстрый отклик сервера при рендеринге (SSR) на сервере (почти мгновенный при генерации на сервере - SSG). Собственно мотив создателей Next.js и был в облегчении описанной выше работы. Отличные показатели SEO, так как работает SSR/SSG. Можно делать сложные клиентские приложения, но порог вхождения выше. Для обслуживания помимо Битрикса нужен опыт работы с React, Next.js, TypeScript и т.д.

В каждом подходе оптимизации есть плюсы и минусы. Готов помочь с любым из них!

    SEO
    Битрикс
    Google Page Speed
    Bootstrap
    Webpack
    SSR

Связанные публикации:

©2022 ReactiveBx работает на «1С-Битрикс: Управление сайтом» и Next.js