Свежие статьи

Рубрики

Рубрика: SEO и Продвижение

Как продвигать сайт на WordPress в Яндексе и Google: настройка SEO, работа с мета-тегами, индексация, анализ трафика. Конкретные шаги и примеры из личного опыта.

  • Почему я взял паузу в разработке и начал писать статьи: что изменилось в моём подходе

    Почему я взял паузу в разработке и начал писать статьи: что изменилось в моём подходе

    Меня зовут Константин Милютин. Это мой дневник — место, где я фиксирую свой путь в IT с помощью ИИ, свои ошибки и выводы. Это блог не для развлекательного контента. Этот блог мой отчёт о проделанной работе и об опыте, который я приобретаю каждый день. Цель блога — помочь Вам не совершать мои ошибки и быстрее прийти к результату.

    Если ты читал мои предыдущие записи, ты знаешь: я прошёл длинный путь. Начинал с PHP-плагина, перешёл на Python, написал с помощью ИИ плагин для автопостинга в ВК, выложил его в официальный репозиторий WordPress.org, а потом отказался его продавать. Я исправил больше 30 ошибок, прошёл четыре раунда ревью, настроил SEO вручную без плагинов. Для  SEO правил код и создал дочернюю тему с помощью deepseek. 

    И вот теперь я беру паузу. Не потому, что устал. И не потому, что всё сломалось. А потому, что понял одну важную вещь: я слишком долго воевал с платформами и забыл, зачем всё это делаю.

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

    Как я понял, что пора остановиться

    Всё началось с VK API. Мой плагин для автопостинга в ВК работал стабильно, но потом ВК ввёл новые ограничения. Из-за этого сервис, через который плагин получал доступ к API, перестал работать. И как следствие — мой плагин перестал публиковать картинки. Текст и ссылка на статью публикует, а изображения — нет.

    Я не переписывал код. Я не искал обходные пути. Потому что проблема была не в моём коде. Проблема была в том, что ВК изменил правила. И никакие мои усилия не могли это исправить.

    Потом WordPress.org заблокировал доступ к странице моего плагина. Просто так, без объяснений. Через несколько дней доступ вернули, но осадок остался. Я понял, что я не контролирую свою же работу. Я завишу от чужих решений.

    И тогда я задал себе вопрос: «А зачем мне это?»

    Я вспомнил, зачем я вообще начал программировать. Я хотел создавать полезные инструменты. Я хотел делиться опытом. Я хотел учиться и расти. Но вместо этого я тратил всё время на борьбу с платформами, которые меняют правила без предупреждения.

    Это выматывает. Это приносит временный результат. И это не то, чем я хочу заниматься.

    Что сломалось и чему это меня научило

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

    Что сломалось и чему это меня научило

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

    Второе — WordPress.org. Когда доступ к странице плагина заблокировали, я сначала подумал, что это ошибка. Потом — что это связано с обновлением. Но потом понял: это просто очередное событие с которым я ничего не мог сделать.

    И тогда я понял: я завишу от платформ. Я не контролирую свою работу. Я не могу гарантировать, что завтра всё будет работать так же, как сегодня.

    Это не то, что я хочу. Это не то, ради чего я начал этот путь.

    Почему пауза — это не отказ, а стратегия

    Я хочу зафиксировать одну вещь: я не бросаю программирование. Я не отказываюсь от своих проектов. Я просто меняю приоритеты.

    Пауза в разработке — это не слабость. Это осознанное решение. Я понял, что тратить всё время на борьбу с платформами — это путь в никуда. Я не могу построить что-то стабильное, если я завишу от чужих API и чужих правил.

    Но что я могу построить? Что не зависит от платформ? Что работает годами и не ломается при обновлении?

    Ответ прост: контент.

    Контент — это единственный актив, который работает годами. Он не зависит от API. Он не ломается при обновлении платформ. Он не блокируется без предупреждения. Он живёт своей жизнью и приносит пользу людям.

    Именно поэтому я решил сфокусироваться на контенте. Я буду писать статьи. Я буду делиться опытом. Я буду строить блог, который не зависит от капризов платформ.

    Разработка никуда не уходит, так как мне нужен опыт. Я просто пока что не буду делиться тем, над чем работаю. Когда я буду точно знать, что нужно моим читателям. Когда я буду уверен, что мой код не сломается через месяц. Я начну об этом рассказывать.

    А пока — контент. Полезные статьи. Живые истории. Честные выводы.

    Что я делаю, пока не пишу код и что изменилось в моём подходе

    Что я делаю, пока не пишу код

    Моя пауза в разработке — это не безделье. Я не сижу и не жду, когда что-то изменится. Я работаю. Просто иначе.

    Первое — я пишу статьи. Каждую неделю я публикую новый материал. Это может быть история о моём опыте, разбор ошибок, практический совет или просто размышление. Главное — чтобы это было полезно.

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

    Третье — я учусь писать лучше. Я работаю с ИИ, но не позволяю ему писать за меня. Я использую его как помощника: для структуры, для идей, для проверки. Но голос остаётся моим.

    Четвёртое — я строю сообщество единомышленников. Я не жду, что люди придут сами. Я делюсь статьями в группе ВК, отвечаю на вопросы, общаюсь с читателями. Я строю сообщество единомышленников.

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

    Это не значит, что я перестал быть разработчиком. Это значит, что я стал ещё и автором. И это только улучшило моё понимание того, что я делаю.

    Что дальше?

    Я не знаю, когда вернусь к разработке. Может быть, через месяц. Может быть, через год. Но я знаю точно: я не брошу программирование. Я просто меняю приоритеты.

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

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

    Потому что я понял главное: я не бросаю программирование. Я меняю свои приоритеты.

    И это — правильное решение.

    Если ты тоже чувствуешь и понимаешь, что застрял в борьбе с правилами платформ, если ты устал от того, что твои проекты ломаются из-за чужих решений — остановись. Подумай. Может быть, тебе тоже стоит взять паузу. Сфокусироваться на контенте. Сделать проект, который не зависит от капризов платформ.

    Это не слабость. Это стратегия. И это работает.

    Читай мой дневник, подписывайся на обновления, делись своим опытом. Вместе мы строим то, что останется.

    Группа ВК: vk.com/milyutin_codes
    Email: milyutinkv@gmail.com

  • Отказ от SEO-плагинов и переход на собственное SEO в WordPress

    Отказ от SEO-плагинов и переход на собственное SEO в WordPress

    Я давно присматривался к SEO-плагинам: Yoast, Rank Math, All in One SEO. Удобно, красиво, но есть проблема — в бесплатных версиях они ограничивают возможности. А покупать PRO-версии или ставить ломаные — не мой путь. Я хочу контролировать каждый тег, каждую мету, каждую строчку кода. И я решил: сделаю всё сам, вручную, через дочернюю тему.

    Это оказалось не так страшно, как звучит. Да, пришлось разбираться с хуками, фильтрами, nonce и экранированием. Но зато теперь я точно знаю, как работает SEO на моём сайте. И могу менять что угодно без оглядки на разработчиков плагинов.

    Сегодня расскажу, как я это сделал. По шагам. С кодом, который можно сразу скопировать и использовать.

    Создание дочерней темы

    Что такое дочерняя тема и зачем она нужна?

    Дочерняя тема — это тема-наследница. Она берёт все стили и шаблоны от родительской темы, но позволяет добавлять свой код без риска потерять изменения при обновлении родительской темы. Я вношу все изменения ТОЛЬКО в дочернюю тему.

    Шаг 1. Создаю папку для дочерней темы

    Через FTP или файловый менеджер хостинга я перешёл в папку:

    text
    /wp-content/themes/

    Создал новую папку с именем:

    text
    название-родительской-темы-child

    Пример: для темы Twenty Twenty-Five папка называется twentytwentyfive-child.

    Шаг 2. Создаю файл style.css

    Внутри папки дочерней темы создал файл style.css со следующим содержимым:

    css
    /*
    Theme Name: Название вашей дочерней темы
    Template: название-родительской-темы
    Description: Дочерняя тема для [Название родительской темы], созданная для ручного SEO
    Author: Ваше имя
    Version: 1.0
    */
    
    /* Импорт стилей родительской темы */
    @import url("../название-родительской-темы/style.css");
    
    /* Здесь потом будем добавлять свои стили (если понадобятся) */

    Пояснение:

    • Theme Name — название, которое увидите в админке WordPress.

    • Template — ОБЯЗАТЕЛЬНО должно совпадать с именем папки родительской темы (например, twentytwentyfive).

    • @import — подключает все стили родительской темы, чтобы внешний вид не сломался.

    Шаг 3. Создаю файл functions.php

    В той же папке создал файл functions.php:

    php
    <?php
    /**
     * Функции дочерней темы
     * Подключаем стили родителя и дочерние стили (если появятся)
     */
    function child_enqueue_styles() {
        // Регистрируем стиль родительской темы
        wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
    }
    add_action( 'wp_enqueue_scripts', 'child_enqueue_styles' );

    Пояснение: этот файл говорит WordPress загружать стили родительской темы правильным способом (более надёжно, чем @import в CSS).

    Шаг 4. Активирую дочернюю тему

    Зашёл в админку WordPress → «Внешний вид» → «Темы».

    Нашёл свою дочернюю тему и нажал «Активировать».

    Сайт выглядит так же, как и раньше.

    Базовые SEO-настройки (файл functions.php)

    ВСЕ ДАЛЬНЕЙШИЕ ИЗМЕНЕНИЯ вносятся в файл functions.php дочерней темы. Я открыл его и добавлял каждый следующий блок кода в конец файла (перед закрывающим ?> если он есть, или просто в конец).

    Блок 1. Управление заголовками (Title) и мета-описаниями (Description)

    Этот код добавляет на страницу редактирования каждого поста два новых поля: «SEO-заголовок» и «SEO-описание». Если заполнить эти поля, они будут использованы в заголовке страницы и в мета-теге description. Если поля пустые — заголовок строится автоматически, а описание генерируется из контента.

    php
    // ========== НАЧАЛО БЛОКА: заголовки и описания ==========
    
    /**
     * 1. Добавляем мета-боксы для SEO-заголовка и SEO-описания
     */
    function seo_add_meta_boxes() {
        add_meta_box(
            'seo_meta_box',
            'SEO-настройки',
            'seo_meta_box_html',
            'post',
            'normal',
            'high'
        );
    }
    add_action( 'add_meta_boxes', 'seo_add_meta_boxes' );
    
    /**
     * 2. Выводим HTML-поля внутри мета-бокса
     */
    function seo_meta_box_html( $post ) {
        $seo_title = get_post_meta( $post->ID, '_seo_title', true );
        $seo_desc   = get_post_meta( $post->ID, '_seo_description', true );
        
        wp_nonce_field( 'seo_save_meta_box_data', 'seo_meta_box_nonce' );
        
        echo '<p><label for="seo_title_field">SEO-заголовок (Title):</label><br>';
        echo '<input type="text" id="seo_title_field" name="seo_title_field" value="' . esc_attr( $seo_title ) . '" size="70" placeholder="Введите заголовок для SEO (до 60 символов)" /></p>';
        
        echo '<p><label for="seo_description_field">SEO-описание (Description):</label><br>';
        echo '<textarea id="seo_description_field" name="seo_description_field" rows="3" cols="70" placeholder="Введите описание для SEO (до 160 символов)">' . esc_textarea( $seo_desc ) . '</textarea></p>';
    }
    
    /**
     * 3. Сохраняем данные при сохранении поста
     */
    function seo_save_meta_box_data( $post_id ) {
        if ( ! isset( $_POST['seo_meta_box_nonce'] ) ||
             ! wp_verify_nonce( $_POST['seo_meta_box_nonce'], 'seo_save_meta_box_data' ) ) {
            return;
        }
        if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
            return;
        }
        if ( ! current_user_can( 'edit_post', $post_id ) ) {
            return;
        }
        if ( isset( $_POST['seo_title_field'] ) ) {
            update_post_meta( $post_id, '_seo_title', sanitize_text_field( $_POST['seo_title_field'] ) );
        }
        if ( isset( $_POST['seo_description_field'] ) ) {
            update_post_meta( $post_id, '_seo_description', sanitize_textarea_field( $_POST['seo_description_field'] ) );
        }
    }
    add_action( 'save_post', 'seo_save_meta_box_data' );
    
    /**
     * 4. Изменяем заголовок страницы (Title)
     */
    function seo_custom_title( $title ) {
        if ( is_singular() ) {
            $post_id = get_queried_object_id();
            $seo_title = get_post_meta( $post_id, '_seo_title', true );
            if ( ! empty( $seo_title ) ) {
                return $seo_title;
            }
        }
        return $title;
    }
    add_filter( 'pre_get_document_title', 'seo_custom_title', 10, 1 );
    
    /**
     * 5. Добавляем мета-тег description в <head>
     */
    function seo_add_description_meta() {
        if ( is_singular() ) {
            $post_id = get_queried_object_id();
            $seo_desc = get_post_meta( $post_id, '_seo_description', true );
            if ( ! empty( $seo_desc ) ) {
                echo '<meta name="description" content="' . esc_attr( $seo_desc ) . '" />' . "\n";
                return;
            }
            $post = get_post( $post_id );
            if ( $post ) {
                $content = strip_shortcodes( $post->post_content );
                $content = wp_strip_all_tags( $content );
                $content = trim( preg_replace( '/\s+/', ' ', $content ) );
                $excerpt = mb_substr( $content, 0, 160, 'UTF-8' );
                if ( ! empty( $excerpt ) ) {
                    echo '<meta name="description" content="' . esc_attr( $excerpt ) . '" />' . "\n";
                }
            }
        }
    }
    add_action( 'wp_head', 'seo_add_description_meta', 5 );
    
    // ========== КОНЕЦ БЛОКА ==========

    Что делает этот блок:

    • Добавляет поля «SEO-заголовок» и «SEO-описание» в редактор постов.

    • Сохраняет введённые значения в базу данных.

    • Подставляет их в <title> и <meta name="description">.

    • Если поля пустые — автоматически генерирует описание из первых 160 символов контента.

    Блок 2. Управление индексацией архивов

    Этот код добавляет на все архивные страницы (рубрики, метки, даты, авторские архивы) мета-тег noindex, follow, чтобы поисковики не индексировали эти страницы, но продолжали переходить по ссылкам.

    php
    // ========== НАЧАЛО БЛОКА: управление индексацией архивов ==========
    
    function seo_archive_noindex() {
        if ( is_archive() || is_tag() || is_category() || is_date() || is_author() ) {
            echo '<meta name="robots" content="noindex, follow" />' . "\n";
        }
        if ( is_paged() && ! is_singular() ) {
            echo '<meta name="robots" content="noindex, follow" />' . "\n";
        }
    }
    add_action( 'wp_head', 'seo_archive_noindex', 5 );
    
    // ========== КОНЕЦ БЛОКА ==========

    Что делает этот блок:

    • Закрывает от индексации все архивы (рубрики, метки, даты, авторов).

    • Закрывает от индексации страницы пагинации (кроме пагинации внутри постов).

    Блок 3. Канонические ссылки для пагинации

    php
    // ========== НАЧАЛО БЛОКА: канонические ссылки для пагинации ==========
    
    function seo_canonical_for_paged() {
        if ( is_paged() ) {
            $base_url = get_pagenum_link( 1 );
            echo '<link rel="canonical" href="' . esc_url( $base_url ) . '" />' . "\n";
        }
    }
    add_action( 'wp_head', 'seo_canonical_for_paged', 3 );
    
    // ========== КОНЕЦ БЛОКА ==========

    Что делает этот блок: Указывает поисковикам, что все страницы пагинации (например, /page/2/) — это вариант первой страницы, чтобы избежать дублей.

    Блок 4. Канонические ссылки без параметров

    Этот блок удаляет все GET-параметры из канонической ссылки (например, ?utm_source=...), чтобы поисковик всегда знал основную версию страницы.

    php
    // ========== НАЧАЛО БЛОКА: канонические ссылки без параметров ==========
    
    function seo_add_canonical_without_params() {
        $protocol = is_ssl() ? 'https://' : 'http://';
        $host = $_SERVER['HTTP_HOST'];
        $request_uri = $_SERVER['REQUEST_URI'];
        $path = strtok( $request_uri, '?' );
        $canonical_url = $protocol . $host . $path;
        $current_url = $protocol . $host . $request_uri;
        if ( $canonical_url !== $current_url ) {
            echo '<link rel="canonical" href="' . esc_url( $canonical_url ) . '" />' . "\n";
        }
    }
    add_action( 'wp_head', 'seo_add_canonical_without_params', 1 );
    
    // ========== КОНЕЦ БЛОКА ==========

    Что делает этот блок: Добавляет каноническую ссылку на чистый URL (без параметров) для всех страниц, к которым обращаются с параметрами в адресной строке.

    Open Graph и Twitter Cards

    Эти теги нужны для красивого отображения ссылок в социальных сетях и мессенджерах (Telegram, ВКонтакте, Facebook, Twitter и других).

    php
    // ========== НАЧАЛО БЛОКА: Open Graph и Twitter Cards ==========
    
    function seo_add_social_meta() {
        global $post;
    
        $site_name = get_bloginfo( 'name' );
        $site_description = get_bloginfo( 'description' );
        $default_image = ''; // Сюда можно вставить URL стандартной картинки
    
        $title = $site_name;
        $description = $site_description;
        $image = $default_image;
        $url = get_permalink();
        $type = 'website';
    
        if ( is_singular() ) {
            $post_id = get_queried_object_id();
            $url = get_permalink( $post_id );
            $type = 'article';
    
            $seo_title = get_post_meta( $post_id, '_seo_title', true );
            if ( ! empty( $seo_title ) ) {
                $title = $seo_title;
            } else {
                $title = get_the_title( $post_id ) . ' — ' . $site_name;
            }
    
            $seo_desc = get_post_meta( $post_id, '_seo_description', true );
            if ( ! empty( $seo_desc ) ) {
                $description = $seo_desc;
            } else {
                $content = strip_shortcodes( $post->post_content );
                $content = wp_strip_all_tags( $content );
                $content = trim( preg_replace( '/\s+/', ' ', $content ) );
                $description = mb_substr( $content, 0, 200, 'UTF-8' );
                if ( empty( $description ) ) {
                    $description = $site_description;
                }
            }
    
            if ( has_post_thumbnail( $post_id ) ) {
                $image = get_the_post_thumbnail_url( $post_id, 'full' );
            } else {
                $pattern = '/<img[^>]+src="([^">]+)"/';
                preg_match( $pattern, $post->post_content, $matches );
                if ( ! empty( $matches[1] ) ) {
                    $image = $matches[1];
                }
            }
        } elseif ( is_front_page() || is_home() ) {
            $title = $site_name;
            $description = $site_description;
            $url = home_url( '/' );
            $type = 'website';
        } else {
            $title = wp_get_document_title();
            $description = $site_description;
            $url = get_permalink();
            $type = 'website';
        }
    
        // РАСКОММЕНТИРУЙТЕ И УКАЖИТЕ СВОЙ URL, ЕСЛИ ХОТИТЕ СТАНДАРТНУЮ КАРТИНКУ-ЗАГЛУШКУ:
        // if ( empty( $image ) ) $image = 'https://ваш-сайт.ru/wp-content/uploads/logo.png';
    
        $title = esc_attr( $title );
        $description = esc_attr( $description );
        $image = esc_url( $image );
        $url = esc_url( $url );
    
        echo "\n<!-- Open Graph / SEO Meta -->\n";
        echo '<meta property="og:title" content="' . $title . '" />' . "\n";
        echo '<meta property="og:description" content="' . $description . '" />' . "\n";
        echo '<meta property="og:type" content="' . $type . '" />' . "\n";
        echo '<meta property="og:url" content="' . $url . '" />' . "\n";
        echo '<meta property="og:site_name" content="' . esc_attr( $site_name ) . '" />' . "\n";
        echo '<meta property="og:locale" content="' . get_locale() . '" />' . "\n";
    
        if ( ! empty( $image ) ) {
            echo '<meta property="og:image" content="' . $image . '" />' . "\n";
            echo '<meta property="og:image:width" content="1200" />' . "\n";
            echo '<meta property="og:image:height" content="630" />' . "\n";
        }
    
        echo "\n<!-- Twitter Card -->\n";
        echo '<meta name="twitter:card" content="summary_large_image" />' . "\n";
        echo '<meta name="twitter:title" content="' . $title . '" />' . "\n";
        echo '<meta name="twitter:description" content="' . $description . '" />' . "\n";
        if ( ! empty( $image ) ) {
            echo '<meta name="twitter:image" content="' . $image . '" />' . "\n";
        }
        // РАСКОММЕНТИРУЙТЕ, ЕСЛИ У ВАС ЕСТЬ АККАУНТ В TWITTER:
        // echo '<meta name="twitter:site" content="@ваш_аккаунт" />' . "\n";
    }
    add_action( 'wp_head', 'seo_add_social_meta', 3 );
    
    // ========== КОНЕЦ БЛОКА ==========

    Что делает этот блок:

    • Генерирует Open Graph-теги (og:titleog:descriptionog:imageog:url и др.) для соцсетей и мессенджеров.

    • Генерирует Twitter Cards для корректного отображения в Twitter/X.

    • Берёт данные из SEO-полей, если они заполнены, или генерирует автоматически.

    Schema-разметка (JSON-LD) для статей

    Schema-разметка помогает поисковым системам лучше понимать содержимое страницы: что это за статья, кто автор, когда опубликована, есть ли изображение.

    php
    // ========== НАЧАЛО БЛОКА: Schema-разметка JSON-LD ==========
    
    function seo_add_schema_org() {
        if ( ! is_singular() ) {
            return;
        }
    
        global $post;
        $post_id = $post->ID;
    
        $site_name = get_bloginfo( 'name' );
        $site_url = home_url( '/' );
        $post_title = get_the_title( $post_id );
        $post_url = get_permalink( $post_id );
        $post_date = get_the_date( 'c', $post_id );
        $post_modified = get_the_modified_date( 'c', $post_id );
        $author_name = get_the_author_meta( 'display_name', $post->post_author );
        $author_url = get_author_posts_url( $post->post_author );
    
        $image_url = '';
        if ( has_post_thumbnail( $post_id ) ) {
            $image_url = get_the_post_thumbnail_url( $post_id, 'full' );
        } else {
            $pattern = '/<img[^>]+src="([^">]+)"/';
            preg_match( $pattern, $post->post_content, $matches );
            if ( ! empty( $matches[1] ) ) {
                $image_url = $matches[1];
            }
        }
    
        $seo_desc = get_post_meta( $post_id, '_seo_description', true );
        if ( ! empty( $seo_desc ) ) {
            $description = $seo_desc;
        } else {
            $content = strip_shortcodes( $post->post_content );
            $content = wp_strip_all_tags( $content );
            $content = trim( preg_replace( '/\s+/', ' ', $content ) );
            $description = mb_substr( $content, 0, 200, 'UTF-8' );
        }
    
        $schema = array(
            '@context' => 'https://schema.org',
            '@type'    => 'Article',
            'headline' => $post_title,
            'description' => $description,
            'mainEntityOfPage' => array(
                '@type' => 'WebPage',
                '@id'   => $post_url,
            ),
            'datePublished' => $post_date,
            'dateModified'  => $post_modified,
            'author' => array(
                '@type' => 'Person',
                'name'  => $author_name,
                'url'   => $author_url,
            ),
            'publisher' => array(
                '@type' => 'Organization',
                'name'  => $site_name,
                'url'   => $site_url,
            ),
        );
    
        if ( ! empty( $image_url ) ) {
            $schema['image'] = array(
                '@type'  => 'ImageObject',
                'url'    => $image_url,
                'width'  => 1200,
                'height' => 630,
            );
        }
    
        echo "\n<!-- Schema.org JSON-LD -->\n";
        echo '<script type="application/ld+json">' . json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . '</script>' . "\n";
    }
    add_action( 'wp_head', 'seo_add_schema_org', 4 );
    
    // ========== КОНЕЦ БЛОКА ==========

    Что делает этот блок: Добавляет структурированные данные Schema.org для статей (тип Article) — это помогает поисковикам лучше понимать содержимое и может улучшить отображение в выдаче.

    Хлебные крошки (BreadcrumbList Schema)

    Этот блок добавляет микроразметку для хлебных крошек — цепочки навигации «Главная → Рубрика → Пост».

    php
    // ========== НАЧАЛО БЛОКА: хлебные крошки (BreadcrumbList) ==========
    
    function seo_add_breadcrumb_schema() {
        $breadcrumbs = array();
    
        $home_url = home_url( '/' );
        $home_title = get_bloginfo( 'name' );
        $breadcrumbs[] = array(
            '@type' => 'ListItem',
            'position' => 1,
            'name' => $home_title,
            'item' => $home_url,
        );
    
        if ( is_singular() ) {
            $post_id = get_queried_object_id();
            $post_type = get_post_type( $post_id );
    
            if ( $post_type == 'post' ) {
                $categories = get_the_category( $post_id );
                if ( ! empty( $categories ) ) {
                    $cat = $categories[0];
                    $cat_link = get_category_link( $cat->term_id );
                    $breadcrumbs[] = array(
                        '@type' => 'ListItem',
                        'position' => 2,
                        'name' => $cat->name,
                        'item' => $cat_link,
                    );
                }
                $breadcrumbs[] = array(
                    '@type' => 'ListItem',
                    'position' => count( $breadcrumbs ) + 1,
                    'name' => get_the_title( $post_id ),
                );
            } elseif ( $post_type == 'page' ) {
                $breadcrumbs[] = array(
                    '@type' => 'ListItem',
                    'position' => 2,
                    'name' => get_the_title( $post_id ),
                );
            } else {
                $breadcrumbs[] = array(
                    '@type' => 'ListItem',
                    'position' => 2,
                    'name' => get_the_title( $post_id ),
                );
            }
        } elseif ( is_category() ) {
            $cat_id = get_queried_object_id();
            $cat = get_category( $cat_id );
            $breadcrumbs[] = array(
                '@type' => 'ListItem',
                'position' => 2,
                'name' => $cat->name,
            );
        } elseif ( is_tag() ) {
            $tag_id = get_queried_object_id();
            $tag = get_tag( $tag_id );
            $breadcrumbs[] = array(
                '@type' => 'ListItem',
                'position' => 2,
                'name' => $tag->name,
            );
        } elseif ( is_author() ) {
            $author = get_queried_object();
            $breadcrumbs[] = array(
                '@type' => 'ListItem',
                'position' => 2,
                'name' => 'Автор: ' . $author->display_name,
            );
        } elseif ( is_date() ) {
            $breadcrumbs[] = array(
                '@type' => 'ListItem',
                'position' => 2,
                'name' => 'Архив дат',
            );
        } elseif ( is_search() ) {
            $breadcrumbs[] = array(
                '@type' => 'ListItem',
                'position' => 2,
                'name' => 'Результаты поиска: ' . get_search_query(),
            );
        } elseif ( is_404() ) {
            $breadcrumbs[] = array(
                '@type' => 'ListItem',
                'position' => 2,
                'name' => 'Страница не найдена',
            );
        }
    
        if ( count( $breadcrumbs ) > 1 ) {
            $schema = array(
                '@context' => 'https://schema.org',
                '@type' => 'BreadcrumbList',
                'itemListElement' => $breadcrumbs,
            );
            echo "\n<!-- Breadcrumb Schema -->\n";
            echo '<script type="application/ld+json">' . json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . '</script>' . "\n";
        }
    }
    add_action( 'wp_head', 'seo_add_breadcrumb_schema', 4 );
    
    // ========== КОНЕЦ БЛОКА ==========

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

    XML-карта сайта (Sitemap)

    Этот код создаёт динамическую XML-карту сайта, доступную по адресу https://ваш-сайт.ru/sitemap.xml.

    php
    // ========== НАЧАЛО БЛОКА: XML-карта сайта ==========
    
    function seo_add_sitemap_rewrite() {
        add_rewrite_rule(
            '^sitemap\.xml$',
            'index.php?seo_sitemap=1',
            'top'
        );
    }
    add_action( 'init', 'seo_add_sitemap_rewrite' );
    
    function seo_add_sitemap_query_var( $vars ) {
        $vars[] = 'seo_sitemap';
        return $vars;
    }
    add_filter( 'query_vars', 'seo_add_sitemap_query_var' );
    
    function seo_sitemap_handler() {
        if ( get_query_var( 'seo_sitemap' ) != 1 ) {
            return;
        }
    
        header( 'Content-Type: application/xml; charset=utf-8' );
        header( 'X-Robots-Tag: noindex, follow' );
        header( 'Cache-Control: no-cache, must-revalidate' );
        header( 'Expires: Thu, 01 Jan 1970 00:00:00 GMT' );
    
        echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
        echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
    
        // Главная страница
        $home_url = home_url( '/' );
        echo '<url>';
        echo '<loc>' . esc_url( $home_url ) . '</loc>';
        echo '<priority>1.0</priority>';
        echo '<changefreq>daily</changefreq>';
        echo '<lastmod>' . date( 'Y-m-d' ) . '</lastmod>';
        echo '</url>' . "\n";
    
        // Все опубликованные посты
        $posts = get_posts( array(
            'post_type'      => 'post',
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'orderby'        => 'modified',
            'order'          => 'DESC',
        ) );
    
        foreach ( $posts as $post ) {
            $url = get_permalink( $post->ID );
            $lastmod = get_the_modified_date( 'Y-m-d', $post->ID );
            echo '<url>';
            echo '<loc>' . esc_url( $url ) . '</loc>';
            echo '<priority>0.8</priority>';
            echo '<changefreq>weekly</changefreq>';
            echo '<lastmod>' . $lastmod . '</lastmod>';
            echo '</url>' . "\n";
        }
    
        // Все опубликованные страницы (Pages)
        $pages = get_pages( array(
            'post_status' => 'publish',
            'hierarchical' => 0,
        ) );
    
        foreach ( $pages as $page ) {
            if ( get_option( 'page_on_front' ) == $page->ID ) {
                continue;
            }
            $url = get_permalink( $page->ID );
            $lastmod = get_the_modified_date( 'Y-m-d', $page->ID );
            echo '<url>';
            echo '<loc>' . esc_url( $url ) . '</loc>';
            echo '<priority>0.6</priority>';
            echo '<changefreq>monthly</changefreq>';
            echo '<lastmod>' . $lastmod . '</lastmod>';
            echo '</url>' . "\n";
        }
    
        echo '</urlset>';
        exit;
    }
    add_action( 'template_redirect', 'seo_sitemap_handler' );
    
    // ========== КОНЕЦ БЛОКА ==========

    Что делает этот блок: Создаёт динамическую XML-карту сайта, которая автоматически обновляется при публикации новых постов. Включает главную страницу, все посты и все страницы (Pages).

    Важно: После добавления этого кода зайдите в админку → «Настройки» → «Постоянные ссылки» и нажмите «Сохранить изменения» — это обновит правила ЧПУ.

    Файл robots.txt

    Создайте в корневой папке сайта (там же, где лежат wp-config.php и wp-load.php) файл robots.txt со следующим содержимым:

    text
    User-agent: *
    Allow: /wp-content/uploads/
    Disallow: /wp-admin/
    Disallow: /wp-includes/
    Disallow: /wp-content/plugins/
    Disallow: /wp-content/themes/
    Disallow: /wp-login.php
    Disallow: /wp-register.php
    Disallow: /wp-json/
    Disallow: /xmlrpc.php
    Disallow: /?s=*
    Disallow: /search/
    Disallow: /page/
    Disallow: /tag/
    Disallow: /category/
    Disallow: /author/
    Disallow: /date/
    Disallow: /feed/
    Disallow: /comments/feed/
    Disallow: /trackback/
    Disallow: /*/feed
    Disallow: /*?*
    Disallow: /*.pdf$
    
    # Разрешаем индексацию только основных страниц с контентом
    Allow: /$
    
    # Карта сайта (ЗАМЕНИТЕ НА СВОЙ ДОМЕН)
    Sitemap: https://ваш-сайт.ru/sitemap.xml

    Что делает этот файл:

    • Закрывает от индексации служебные папки (/wp-admin//wp-includes/ и др.).

    • Закрывает архивы (рубрики, метки, авторы, даты), поиск, пагинацию и страницы с параметрами.

    • Указывает поисковикам путь к карте сайта.

    Важно: Замените https://ваш-сайт.ru на адрес вашего сайта.

    Настройка ЧПУ (постоянных ссылок)

    Зайдите в админку WordPress → «Настройки» → «Постоянные ссылки».

    Выберите вариант «Название записи» (Post name).

    Нажмите «Сохранить изменения».

    Почему это важно: ЧПУ вида https://сайт/название-статьи/ лучше для SEO, чем https://сайт/?p=123.

    Заполнение SEO-полей, Отрывка и SEO для изображений

    9.1. SEO-заголовок и SEO-описание

    Для каждой записи (поста) и страницы:

    • Откройте запись в редакторе.

    • Прокрутите вниз до блока «SEO-настройки».

    • Заполните поле «SEO-заголовок (Title)» — рекомендуемая длина до 60 символов.

    • Заполните поле «SEO-описание (Description)» — рекомендуемая длина 150–160 символов.

    Совет: Заголовок должен содержать ключевое слово и быть привлекательным для клика. Описание — кратко и ёмко передавать суть страницы.

    9.2. Отрывок (Excerpt)

    Отрывок — это краткая выжимка из поста, которая отображается на главной странице, в архивах и в результатах внутреннего поиска. Его тоже нужно заполнять.

    Где находится: В редакторе поста, обычно под основным полем ввода текста (вкладка «Отрывок»). Если её нет — включите через «Настройки экрана» в правом верхнем углу.

    Рекомендуемая длина: 25–35 слов (примерно 150–160 символов).

    Зачем заполнять:

    • Улучшает внешний вид анонсов на сайте.

    • Помогает посетителям быстрее понять, о чём статья.

    • Если SEO-описание не заполнено, WordPress может использовать отрывок как мета-описание.

    9.3. SEO для изображений

    Правильное оформление изображений — важная часть SEO. Для каждого изображения в посте:

    • Заполните атрибут alt (альтернативный текст) — он должен описывать содержание изображения и содержать ключевое слово (если уместно).

    • Дайте файлу осмысленное имя перед загрузкой (например, seo-nastrojka-wordpress.jpg, а не IMG_1234.jpg).

    • Используйте миниатюру поста (Featured Image) — она будет использоваться в Open Graph и Schema-разметке.

    Как заполнить alt в WordPress:

    • При вставке изображения в пост заполните поле «Альтернативный текст».

    • Или в медиатеке откройте изображение и заполните поле «Alt-текст».

    Финальная проверка и отправка sitemap

    10.1. Проверьте работу всех мета-тегов

    Откройте любую страницу сайта, нажмите правой кнопкой мыши → «Посмотреть код страницы» и проверьте наличие:

    • <title> — ваш SEO-заголовок.

    • <meta name="description"> — ваше SEO-описание.

    • Open Graph-теги (og:titleog:descriptionog:image).

    • Twitter Cards.

    • Schema-разметка (application/ld+json).

    • Каноническая ссылка (<link rel="canonical">).

    10.2. Проверьте sitemap.xml

    Откройте в браузере https://ваш-сайт.ru/sitemap.xml — должен открыться XML-файл со списком всех страниц.

    10.3. Проверьте robots.txt

    Откройте https://ваш-сайт.ru/robots.txt — должен открыться текстовый файл с вашими правилами.

    10.4. Отправьте sitemap в поисковые системы

    Яндекс.Вебмастер:

    • Зайдите в панель Яндекс.Вебмастера.

    • Выберите свой сайт → раздел «Индексирование» → «Sitemap-файлы».

    • Добавьте адрес: https://ваш-сайт.ru/sitemap.xml.

    • Нажмите «Добавить».

    Google Search Console:

    • Зайдите в Search Console → выберите сайт.

    • В левом меню «Sitemap» (или «Карты сайта»).

    • Введите sitemap.xml и нажмите «Отправить».

    Полный список файлов и их назначение

    Файл Местоположение Назначение
    style.css /wp-content/themes/название-темы-child/ Стили дочерней темы, подключение родительских стилей
    functions.php /wp-content/themes/название-темы-child/ ГЛАВНЫЙ ФАЙЛ — содержит весь SEO-код (заголовки, описания, OG, Schema, sitemap и др.)
    robots.txt Корень сайта (/public_html/ или /) Управление индексацией для поисковых роботов

    Что делать, если возникли вопросы?

    Если какой-то шаг непонятен или код не работает — спросите у DeepSeek или другого ИИ-помощника. Опишите проблему максимально подробно:

    • Что вы сделали?

    • Что ожидали увидеть?

    • Что увидели вместо этого?

    • Приложите код (если есть) и сообщение об ошибке (если есть).

    ИИ-помощники отлично справляются с отладкой PHP-кода и могут подсказать, где допущена ошибка.

    А также можете написать мне https://vk.ru/milyutink — я обязательно постараюсь помочь!

    Почему я не стал продавать плагины и начал их отдавать бесплатно.

    Читай также: Почему я взял паузу в разработке и начал писать статьи