Top.Mail.Ru

Как я ускорил свой блог до 97 баллов PageSpeed: полный код без SEO-плагинов

Обложка статьи об оптимизации WordPress без плагинов: разработчик за ноутбуком с отчётом PageSpeed 97, перечёркнутый плагин кэширования, код и кофе.

Сначала важное

Эта статья — для тех, кто НЕ использует SEO-плагины. Если у вас стоит Yoast, Rank Math, All in One SEO или подобное — код ниже может с ними конфликтовать. Плагины генерируют свои title, description, canonical, Open Graph, Schema.org. Мой код делает то же самое вручную. Если поставите мой код с SEO плагинами — получите дубли мета-тегов, и поисковики запутаются.

Всё, что описано ниже, я писал и тестировал на чистой теме Twenty Twenty-Five с дочерней темой. Без SEO-плагинов. Только код.

Если вы пока не готовы отказаться от плагинов — просто прочитайте, вникните в логику.

С чего всё началось

Я долго думал, что бесплатный плагин кэширования — это хорошо. WP Fastest Cache, миллион установок и  мне его посоветовал DeepSeek. Поставил, настроил, забыл.

А потом заметил: правило 410 Gone для удалённой страницы не срабатывает. Google видит 404 вместо 410. Полез в .htaccess — а там блок от WP Fastest Cache с флагом [L]. Плагин перехватывает запрос раньше моих правил и отдаёт кэшированную 404-страницу.

Копнул глубже. Отключил плагин, чтобы протестировать 410. И на автомате прогнал PageSpeed.

Было: 74. Стало: 97.

Я не поверил. Прогнал ещё раз — 97. Другую статью — 95. И тогда я увидел, что плагин, который должен ускорять сайт, его замедлил.

Скриншоты: с плагином и без

Вот сравнение для одной и той же страницы — статьи «Как работает поиск Яндекса и Google».

С WP Fastest Cache (мобильный тест): производительность 78. Скриптов много, LCP высокий.

Без WP Fastest Cache (та же страница): производительность 97, все метрики в зелёной зоне.

Данные pagespeed.web без плагина

И вторая пара — страница «От идеи до работающего плагина на Python».

С плагином: 74 балла.

Показатель статьи в pagespeed.webс плагином

Без плагина: 95 баллов.

Без плагина лучше для pagespeed.web

Обратите внимание на детали. Без WP Fastest Cache улучшились все показатели:

МетрикаС плагиномБез плагина
Производительность74–7895–97
Специальные возможности96100
Рекомендации9696
Поисковая оптимизация100100

Почему бесплатный плагин замедлял сайт

Расскажу по пунктам, что именно я нашёл и понял. Это важно понять, чтобы не наступить на те же грабли.

Причина 1. Лишний JavaScript. Бесплатные версии плагинов живут за счёт продаж PRO. Значит, они должны напоминать о PRO. Баннеры, upsell-скрипты, скрытые трекеры — часть попадает на фронтенд.

Причина 2. Конфликты с кодом. WP Fastest Cache лезет в .htaccess, кэширует всё подряд, включая 404-страницы. Мои правила 410 Gone не работали. Google видел кэшированный 404 вместо честного 410.

Причина 3. Кэш поверх оптимизаций. Я настроил sizes, добавил fetchpriority, предзагрузку шрифтов. А плагин кэшировал старые HTML-версии страниц, где этих оптимизаций не было. Я улучшал сайт — а посетители получали старую версию.

Причина 4. Кэш не нужен маленькому сайту. Блог из 20 статей с посещаемостью 100 человек в день. PHP отрабатывает за 50 мс, база за 10 мс. Плагин добавляет 200 мс на инициализацию и проверки. В итоге у Вас минус, а не плюс.

Что я сделал вместо плагина

Пошёл проверенным своим способом. Просто написал код, который я понимаю и контролирую. Разберу его для Вас по блокам.

Блок 1. Исправление атрибута sizes для изображений

Тема генерировала sizes="(max-width: 1152px) 100vw, 1152px", хотя контейнер контента всего 645px. Браузер послушно качал картинку шириной 1152px вместо 768px. Экономия на одной картинке — до 60% размера.

php
/**
 * ИСПРАВЛЕНИЕ: размер изображений в srcset / sizes.
 * Заставляет браузер выбирать картинки под ширину колонки контента (645px),
 * а не под ширину экрана. Экономит до 500 КБ на мобильных.
 */
function milyutin_fix_image_sizes_attribute( $sizes, $size, $image_src, $image_meta ) {
    if ( is_admin() ) {
        return $sizes;
    }
    return '(max-width: 645px) 100vw, 645px';
}
add_filter( 'wp_calculate_image_sizes', 'milyutin_fix_image_sizes_attribute', 10, 4 );

Браузер теперь выбирает картинку под ширину контейнера 645px. Вместо версии 1152px — берёт 768px. Разница в весе — в разы.

Блок 2. fetchpriority для первого изображения

Это сигнал браузеру: «вот эту картинку грузи первой, она важная для LCP».

php
/**
 * Добавляем fetchpriority="high" первому изображению в контенте для улучшения LCP.
 */
function milyutin_add_fetchpriority_to_first_image( $content ) {
    if ( ! is_singular() ) {
        return $content;
    }
    if ( preg_match( '/<img[^>]+>/i', $content, $matches ) ) {
        $first_img = $matches[0];
        $new_img = str_replace( 'loading="lazy"', '', $first_img );
        $new_img = str_replace( '<img', '<img fetchpriority="high"', $new_img );
        $content = str_replace( $first_img, $new_img, $content );
    }
    return $content;
}
add_filter( 'the_content', 'milyutin_add_fetchpriority_to_first_image', 5 );

Первая картинка в статье загружается приоритетно, без lazy. Браузер не ждёт прокрутки. LCP улучшается.

Блок 3. Предзагрузка локальных шрифтов

Шрифты — часто самое узкое место. Браузер скачивает CSS, находит в нём ссылки на шрифты, и только потом начинает их грузить. Предзагрузка ломает эту цепочку.

php
/**
 * Предзагрузка локальных шрифтов.
 */
function milyutin_preload_local_fonts() {
    $fonts = array(
        'https://milyutin-codes.ru/wp-content/themes/twentytwentyfive/assets/fonts/manrope/Manrope-VariableFont_wght.woff2',
        'https://milyutin-codes.ru/wp-content/themes/twentytwentyfive/assets/fonts/fira-code/FiraCode-VariableFont_wght.woff2',
    );
    foreach ( $fonts as $font_url ) {
        echo '<link rel="preload" href="' . esc_url( $font_url ) . '" as="font" type="font/woff2" crossorigin>' . "\n";
    }
}
add_action( 'wp_head', 'milyutin_preload_local_fonts', 1 );

Важно: замените URL шрифтов на свои. Откройте исходный код страницы, поищите woff2 — увидите пути. Либо посмотрите в папке темы /assets/fonts/.

Блок 4. Defer для некритичных скриптов

comment-reply и hoverintent нужны не сразу. Их можно отложить, чтобы не блокировали рендеринг.

php
/**
 * Добавляем defer к некритичным скриптам для снижения TBT.
 */
function milyutin_defer_non_critical_scripts( $tag, $handle ) {
    $defer_scripts = array(
        'comment-reply',
        'hoverintent-js',
    );
    if ( in_array( $handle, $defer_scripts, true ) ) {
        return str_replace( ' src=', ' defer src=', $tag );
    }
    return $tag;
}
add_filter( 'script_loader_tag', 'milyutin_defer_non_critical_scripts', 10, 2 );

Скрипты загружаются параллельно, но выполняются после парсинга HTML. TBT (Total Blocking Time) снижается.

Блок 5. Исключение архивов из кэша (если кэш остался)

Если вы всё-таки оставили какой-то кэширующий плагин — попросите его не кэшировать архивы. Они обновляются часто, а кэш только вредит.

php
/**
 * Исключаем архивы из кэша WP Fastest Cache.
 * Если плагин удалён — функция просто не сработает, ничего не сломает.
 */
function milyutin_exclude_archive_pages_from_cache() {
    if ( is_home() || is_front_page() || is_category() ) {
        if ( function_exists( 'wpfc_exclude_current_page' ) ) {
            wpfc_exclude_current_page();
        }
    }
}
add_action( 'wp', 'milyutin_exclude_archive_pages_from_cache' );

Если WP Fastest Cache установлен — архивы не кэшируются. Если удалён — функция просто не вызовется.

Блок 6. 410 Gone для удалённых страниц через .htaccess

Это не PHP, а конфиг сервера. Правило говорит поисковикам: «страница удалена навсегда, забудьте про неё». Google убирает такие страницы из индекса быстрее, чем 404.

Откройте .htaccess в корне сайта. Найдите блок # BEGIN WordPress. Перед ним вставьте:

apache
# ========== 410 GONE ДЛЯ УДАЛЁННЫХ СТРАНИЦ ==========
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^novosti-ii/?$ - [G,L]
</IfModule>
# ========== КОНЕЦ БЛОКА ==========

Важно: novosti-ii — это пример. Замените на слаг вашей удалённой страницы. Если страниц несколько — добавьте правила по аналогии:

apache
RewriteRule ^staraya-stranica/?$ - [G,L]
RewriteRule ^eshchyo-odna/?$ - [G,L]

G — возвращает 410 Gone. L — останавливает обработку, WordPress не перехватит запрос.

Подводный камень: если у вас стоит плагин кэширования — он может перехватить запрос раньше. У меня так и было. Именно это и стало первым признаком, что плагин мне не помогает.

Как я столкнулся с проблемой оптимизации картинок

Когда я разобрался с кэшем и увидел 97 баллов, осталась одна нерешённая задача — изображения. JPEG-файлы весили по 80–300 КБ. Google в отчёте PageSpeed прямым текстом писал: «Конвертируйте в WebP, сэкономите 500 КБ».

Я поставил Converter for Media. Плагин с громким описанием: «Ускорьте работу сайта с помощью конвертера WebP и AVIF!». Звучало идеально. Бесплатный, в каталоге WordPress, миллион установок.

А потом я открыл его настройки. И понял: бесплатная версия практически ничего не делает. Да, там есть кнопка «Конвертировать», но:

  • AVIF недоступен — только в PRO.

  • Массовая конвертация всей медиатеки — ограничена или отсутствует.

  • Автоконвертация при загрузке новых файлов — только в PRO.

  • Отдача через <picture> с правильным srcset — только в PRO.

То есть плагин как бы есть. Но пользы от него — почти ноль. Он просто висит в админке и напоминает: «купи PRO, купи PRO».

И тогда я решил: напишу свой плагин. Без PRO, без ограничений, без чужих трекеров. Только код, который делает ровно то, что нужно мне.

Так я сделал Milyutin Image Optimizer — плагин, который:

  • конвертирует JPG/PNG в WebP и AVIF через Imagick или GD;

  • делает массовую конвертацию всей медиатеки батчами по 5 файлов через AJAX;

  • автоматически конвертирует новые изображения при загрузке;

  • отдаёт изображения через <picture> с правильным порядком: AVIF → WebP → оригинал;

  • ведёт лог и статистику экономии;

  • не требует PRO, не показывает баннеры, не следит за вами.

Скачать плагин можно на моей странице «Мои плагины». Там же — инструкция по установке и настройке.

Но я не хочу, чтобы вы просто скачали и поставили мой плагин. Я хочу, чтобы вы поняли, как его сделать. Потому что чужой код сложно понять. А свой код, лучше и понятнее.

Ниже — промпт для DeepSeek, с которым вы сможете создать точно такой же плагин самостоятельно. Я даю его в том виде, в котором сам его написал. Скопируйте, вставьте в DeepSeek — и получите рабочий плагин.

Промпт для DeepSeek: создаём свой аналог Converter for Media

Скопируйте этот текст целиком и отправьте в DeepSeek. Не нужно ничего добавлять, не нужно объяснять — всё уже внутри.

text
Ты — опытный WordPress-разработчик на PHP 8+. Напиши мне полноценный плагин для WordPress, который я могу установить на свой сайт и который будет делать бесплатно то, что в плагине Converter for Media доступно только в PRO-версии.

## Что должен делать плагин

Плагин конвертирует изображения JPG и PNG в современные форматы WebP и AVIF. Он должен:

1. **Автоматически конвертировать новые изображения** при загрузке в медиатеку. Сработать после того, как WordPress создаст все размеры (хук `wp_generate_attachment_metadata`).

2. **Уметь массово конвертировать все старые изображения**, которые уже есть на сайте. Работать это должно батчами по 5–10 файлов через admin-ajax.php, чтобы не упасть по таймауту. С прогресс-баром в админке.

3. **Отдавать изображения на фронтенде через `<picture>`** с порядком источников AVIF → WebP → оригинал. Заменять `<img>` на `<picture>` через фильтры `wp_content_img_tag` и `wp_get_attachment_image`. При этом корректно обрабатывать `srcset` и `sizes` — если хотя бы один размер не сконвертирован, формат пропускается.

4. **Иметь страницу настроек в админке**: тумблер авто-конвертации, выбор форматов (WebP, AVIF или оба), ползунки качества для каждого формата, кнопка запуска массовой конвертации, отображение статистики (сколько сконвертировано, сколько ошибок, сколько сэкономлено байт), лог операций.

5. **Проверять поддержку форматов на сервере** через Imagick и GD. Показывать пользователю статус: Imagick, WebP, AVIF. Если формат не поддерживается — предупреждать и пропускать.

6. **Приоритет конвертации**: сначала через Imagick (более качественный результат, лучшее сжатие), если Imagick недоступен или упал — использовать GD через WP_Image_Editor.

## Технические требования

- PHP 8.0+.
- WordPress 6.0+.
- Классовая структура: отдельные классы для логгера, конвертера, настроек, массовой конвертации и фронтенда. Один главный класс-бутстрап.
- Безопасность: nonce в AJAX-запросах, `current_user_can('manage_options')` для всех админских действий, `sanitize_callback` для настроек, `esc_attr`/`esc_html` при выводе, `$wpdb->prepare()` для запросов к базе.
- Никаких внешних зависимостей, никаких трекеров, никакой отправки данных на сторонние серверы.
- Опции хранить через `update_option` с `autoload = false`, чтобы не грузить их на каждой странице.
- При удалении плагина через `uninstall.php` — удалять настройки, лог и статистику. **Но не удалять сконвертированные файлы** — они нужны сайту.

## Структура плагина

Сделай такую структуру файлов:

milyutin-image-optimizer/
├── milyutin-image-optimizer.php
├── uninstall.php
├── includes/
│   ├── class-mio-logger.php
│   ├── class-mio-converter.php
│   ├── class-mio-settings.php
│   ├── class-mio-bulk.php
│   └── class-mio-frontend.php
└── assets/
    ├── css/admin.css
    └── js/admin.js

## Обязательные элементы в шапке главного файла

Plugin Name: Milyutin Image Optimizer
Plugin URI:  https://milyutin-codes.ru
Description: Конвертация JPG/PNG в WebP и AVIF для ускорения сайта. Массовая и автоматическая конвертация, отдача через <picture>.
Version:     1.0.0
Author:      Константин Милютин
Author URI:  https://milyutin-codes.ru
License:     GPL-2.0-or-later
Text Domain: milyutin-image-optimizer
Requires at least: 6.0
Requires PHP: 8.0

## Брендинг автора

На странице настроек плагина сделай отдельный блок «Автор плагина» со ссылками:
- Сайт: https://milyutin-codes.ru
- Группа ВКонтакте: https://vk.com/milyutin_codes

Стилизуй блок мягко, с градиентом, чтобы он выделялся.

## Качество кода

- Не используй устаревшие функции и практики.
- Пиши чистый, читаемый код с комментариями на русском языке.
- Не используй `extract()`, не подавляй ошибки через `@` без необходимости.
- Все регулярные выражения и строки — с экранированием.
- Логику конвертации вынеси в отдельный метод, чтобы её можно было легко расширить.

## Формат ответа

Выдай ответ пошагово:

**Шаг 1.** Создайте папку wp-content/plugins/milyutin-image-optimizer/ и подпапки includes/, assets/css/, assets/js/.

**Шаг 2.** Создайте файл milyutin-image-optimizer.php и вставьте в него код: [полный код]

**Шаг 3.** Создайте файл includes/class-mio-logger.php и вставьте в него код: [полный код]

**Шаг 4.** Создайте файл includes/class-mio-converter.php и вставьте в него код: [полный код]

**Шаг 5.** Создайте файл includes/class-mio-settings.php и вставьте в него код: [полный код]

**Шаг 6.** Создайте файл includes/class-mio-bulk.php и вставьте в него код: [полный код]

**Шаг 7.** Создайте файл includes/class-mio-frontend.php и вставьте в него код: [полный код]

**Шаг 8.** Создайте файл assets/css/admin.css и вставьте в него код: [полный код]

**Шаг 9.** Создайте файл assets/js/admin.js и вставьте в него код: [полный код]

**Шаг 10.** Создайте файл uninstall.php и вставьте в него код: [полный код]

**Шаг 11.** Заархивируйте папку milyutin-image-optimizer целиком в ZIP.

**Шаг 12.** В админке WordPress: Плагины → Добавить новый → Загрузить плагин → выбрать ZIP → Установить → Активировать.

**Шаг 13.** Зайдите в меню «Milyutin Image Opt.» в админке. Проверьте статус сервера (Imagick, WebP, AVIF). Настройте ползунки качества (рекомендую WebP = 82, AVIF = 50). Сохраните.

**Шаг 14.** Нажмите «Запустить массовую конвертацию». Дождитесь завершения — страница перезагрузится автоматически.

**Шаг 15.** Опубликуйте новую статью с картинкой и проверьте: в исходном коде страницы изображение должно быть обёрнуто в `<picture>` с источником WebP или AVIF.

Начни с Шага 1 и выдай полный код каждого файла полностью, без сокращений. Не используй многоточия и не пиши «и так далее». Я хочу получить готовый к установке плагин.

Как использовать этот промпт

Откройте DeepSeek. Скопируйте текст промпта выше целиком. Вставьте в чат. Дождитесь ответа — DeepSeek выдаст код пошагово.

Если ответ получился неполным (например, DeepSeek сократил какой-то файл) — напишите ему: «Продолжи, ты сократил файл X. Выдай его полный код без многоточий».

Что делать после того, как DeepSeek выдаст код

Соберите плагин так, как описано в предыдущем разделе. Я специально дал в промпте пошаговую инструкцию, чтобы DeepSeek сам её продублировал в ответе — вам останется просто следовать.

Проверьте главное: в файле class-mio-converter.php должен быть метод make_webp() и make_avif(). Первый пробует Imagick, если не получается — падает на GD. Второй — только Imagick или GD с AVIF (PHP 8.1+). Если AVIF на вашем хостинге не поддерживается — плагин должен просто пропустить этот формат и работать только с WebP.

Проверьте безопасность: во всех AJAX-запросах должен быть check_ajax_referer, а в class-mio-settings.php — sanitize_callback в register_setting.

Если что-то не так — не стесняйтесь просить DeepSeek переписать конкретный файл. Он умеет.

Что делать вам

Шаг 1. Отключите WP Fastest Cache (и другие кэширующие плагины). Не удаляйте — отключите. Прогоните PageSpeed. Сравните с тем, что было.

Шаг 2. Если показатели выросли — оставьте плагин отключённым. Удалите его через панель WordPress.

Шаг 3. Скопируйте код из статьи в functions.php дочерней темы. Замените пути к шрифтам на свои.

Шаг 4. Настройте .htaccess для 410 Gone, если у вас есть удалённые страницы.

Шаг 5. Создайте плагин оптимизации картинок по промпту выше (если возникнут вопросы, то пишите мне).

Шаг 6. Прогоните PageSpeed ещё раз. Не ждите идеала сразу — сначала может быть 80–85. Дальше добираете мелочами.

Шаг 7. Отправьте sitemap в Яндекс.Вебмастер и Google Search Console. Запросите переобход изменённых страниц.

Чего не делать

Не ставьте новые плагины «для скорости». Не покупайте PRO-версии, не разобравшись, зачем они вам. Не кэшируйте всё подряд — кэш на маленьком сайте только вредит.

И не бойтесь удалять. Плагин — это инструмент. Если инструмент мешает — его выбрасывают.

Итог

Я удалил WP Fastest Cache и получил 97 баллов на мобильном (На ПК показатели моего сайта изначально почти все 100 баллов). Не потому что плагин плохой сам по себе. А потому что для моего сайта он был лишним звеном.

Потом я столкнулся с проблемой оптимизации картинок. Поставил Converter for Media — и понял, что бесплатная версия ничего не делает. Написал свой плагин. Теперь у меня есть инструмент, который я понимаю от начала до конца.

Всё, что я использовал для ускорения — это шесть блоков кода и один плагин. Их можно разобрать за вечер. И они не сломаются при обновлении чужих плагинов, потому что это мой код.

Мы строим то, что останется. А чужой код — остаётся чужим.

Автор: Константин Милютин
Группа ВК: vk.com/milyutin_codes
Email: milyutinkv@gmail.com

Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *