<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Baranov.Guru | RSS feed</title>
        <link>https://baranov.guru</link>
        <description>Алексей Баранов — частный IT-консультант, предприниматель и блогер. Разработка сайтов и веб-приложений, внедрение ИИ, автоматизация процессов, Telegram-боты и Mini Apps, бэкенд и облако. Практические заметки и гайды по разработке и цифровым продуктам — более 15 лет опыта.</description>
        <lastBuildDate>Wed, 07 Oct 2026 10:26:07 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Baranov.Guru RSS feed</generator>
        <language>ru</language>
        <image>
            <title>Baranov.Guru | RSS feed</title>
            <url>https://baranov.guru/assets/og.webp</url>
            <link>https://baranov.guru</link>
        </image>
        <copyright>All rights reserved 2026, Baranov.Guru</copyright>
        <item>
            <title><![CDATA[JSON-LD для сайта: как структурированные данные помогают SEO и бизнесу]]></title>
            <link>https://baranov.guru/posts/json-ld/</link>
            <guid>https://baranov.guru/posts/json-ld</guid>
            <pubDate>Sun, 07 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Понятное руководство по JSON-LD для владельцев бизнеса, SEO-специалистов и маркетологов: зачем нужны структурированные данные, какие типы Schema.org внедрять на сайте, как избежать ошибок и проверить разметку.]]></description>
            <content:encoded><![CDATA[<div><h2 id="what">Что такое JSON-LD </h2><p><strong>JSON-LD</strong> — это формат структурированных данных, который помогает поисковым системам и другим сервисам лучше понимать, что находится на странице.</p><p>Если объяснять без технического жаргона, обычный HTML показывает страницу человеку: заголовок, текст, изображения, кнопки, цены, адреса, статьи и карточки товаров. Но поисковому роботу нужно не только увидеть текст, а понять его смысл.</p><p>Например, на странице может быть написано:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Алексей Баранов</span></span>
<span class="line"><span>Программист, предприниматель, блогер. Основатель baranov.guru.</span></span></code></pre><p>Человек понимает, что это автор. Поисковику можно помочь и явно описать это в структурированном виде:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@context"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Person"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Алексей Баранов"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "jobTitle"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Программист, предприниматель, блогер"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "url"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/about/"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Так поисковая система получает не просто набор слов, а сущность: это человек, у него есть имя, роль и страница.</p><p>JSON-LD обычно добавляют в HTML через специальный тег:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;script type=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"application/ld+json"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@context"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Organization"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "baranov.guru"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "url"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/script></span></span></code></pre><p>Посетитель сайта этот блок обычно не видит. Он нужен для поисковых систем, валидаторов, парсеров и других сервисов, которые анализируют страницу.</p><p>Важно: JSON-LD — это не «магический SEO-код». Он не заменяет нормальный контент, понятную структуру сайта, скорость загрузки, индексацию, внутренние ссылки и техническое SEO. Но он помогает убрать неоднозначность и показать поисковику, <strong>какие сущности есть на странице и как они связаны</strong>.</p><aside class="post-disclaimer" role="note"><p>JSON-LD полезен тогда, когда он описывает реальное содержимое страницы. Если разметка обещает одно, а пользователь видит другое, это не SEO-оптимизация, а источник проблем.</p></aside><h2 id="business">Зачем JSON-LD бизнесу </h2><p>Для владельца бизнеса JSON-LD важен не сам по себе. Важно, что он помогает поисковым системам точнее понимать сайт: где компания, где услуга, где статья, где хлебные крошки, где вопросы и ответы, где товар, где адрес и контакты.</p><p>На практике это влияет на несколько задач.</p><p>Во-первых, структурированные данные помогают поисковой системе связать страницу с конкретной сущностью. Например, страница «Техническая SEO-оптимизация» может быть описана как услуга, статья — как публикация, главная страница — как сайт или организация, а навигационная цепочка — как <code>BreadcrumbList</code>.</p><p>Во-вторых, JSON-LD может помочь странице претендовать на расширенное отображение в поиске. Это не гарантия, но без корректной структурированной разметки многие rich results в принципе недоступны.</p><p>В-третьих, разметка снижает риск неправильной интерпретации страницы. Особенно на сайтах, где много похожих сущностей: услуги, кейсы, статьи, инструменты, категории, товары, специалисты, филиалы и адреса.</p><p>Для бизнеса это проявляется в более понятных сценариях:</p><ul><li>поисковик лучше понимает, что страница является статьёй, услугой, товаром или страницей организации;</li><li>хлебные крошки могут отображаться аккуратнее;</li><li>карточки контента становятся более предсказуемыми для парсеров;</li><li>техническая SEO-команда быстрее проверяет шаблоны страниц;</li><li>после миграции сайта проще контролировать, что важные метаданные не потерялись;</li><li>контент, Open Graph, canonical, sitemap.xml и JSON-LD работают как единая система.</li></ul><p>JSON-LD особенно полезен для сайтов, где SEO — это не разовая настройка, а постоянный процесс: блог, каталог, интернет-магазин, сайт услуг, база знаний, SaaS, медиа, маркетплейс или локальный бизнес с несколькими точками.</p></div><div><p>JSON-LD не продвигает сайт вместо контента и технической оптимизации. Его задача — сделать смысл страницы машинно-читаемым: кто автор, что за организация, какая это страница, какие вопросы на ней разобраны и как она связана с другими URL сайта.</p></div><section><h2>Что даёт JSON-LD на практике</h2><p>Структурированные данные помогают не «обмануть алгоритм», а яснее описать страницу и уменьшить техническую неопределённость.</p><div><article><h3>Понятнее сущности</h3><p>Поисковик видит не просто текст, а статью, организацию, услугу, товар, автора, адрес или хлебные крошки.</p></article><article><h3>Больше шансов на rich results</h3><p>Корректная разметка может сделать страницу eligible для расширенных форматов, если тип поддерживается поисковой системой.</p></article><article><h3>Меньше противоречий</h3><p>JSON-LD помогает согласовать title, canonical, Open Graph, sitemap.xml и фактическое содержимое страницы.</p></article><article><h3>Проще аудит шаблонов</h3><p>На больших сайтах можно проверять не каждую страницу вручную, а типовые шаблоны: статьи, услуги, товары, категории.</p></article><article><h3>Лучше поддержка миграций</h3><p>После переезда на новую CMS или frontend проще увидеть, какие структурированные данные исчезли или сломались.</p></article><article><h3>Больше контроля</h3><p>Команда явно задаёт важные поля: автора, дату публикации, логотип, адрес, URL, изображение и связи между страницами.</p></article></div></section><div><h2 id="schema-rich-results">JSON-LD, Schema.org и rich results </h2><p>JSON-LD часто смешивают с другими терминами: микроразметка, Schema.org, structured data, rich results, расширенные сниппеты. Разберём разницу.</p><h3>Structured data</h3><p><strong>Structured data</strong> — это общий термин: структурированные данные на странице. Они описывают содержимое в стандартизированном виде.</p><p>Например:</p><ul><li>это статья;</li><li>это организация;</li><li>это товар;</li><li>это локальный бизнес;</li><li>это вопрос и ответ;</li><li>это навигационная цепочка;</li><li>это видео;</li><li>это рецепт;</li><li>это событие.</li></ul><h3>Schema.org</h3><p><strong>Schema.org</strong> — это словарь типов и свойств. Он говорит, какие сущности можно описывать и какие поля у них бывают.</p><p>Например:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Article</span></span>
<span class="line"><span>BlogPosting</span></span>
<span class="line"><span>Organization</span></span>
<span class="line"><span>LocalBusiness</span></span>
<span class="line"><span>Product</span></span>
<span class="line"><span>FAQPage</span></span>
<span class="line"><span>BreadcrumbList</span></span>
<span class="line"><span>Person</span></span>
<span class="line"><span>WebSite</span></span>
<span class="line"><span>Service</span></span>
<span class="line"><span>SoftwareApplication</span></span></code></pre><p>У каждого типа есть свойства: <code>name</code>, <code>url</code>, <code>image</code>, <code>description</code>, <code>author</code>, <code>datePublished</code>, <code>address</code>, <code>telephone</code>, <code>openingHours</code>, <code>itemListElement</code> и другие.</p><h3>JSON-LD</h3><p><strong>JSON-LD</strong> — это формат записи. Он позволяет описывать Schema.org-данные отдельным JSON-блоком, не вмешиваясь в HTML-разметку каждого элемента страницы.</p><p>Есть и другие форматы структурированных данных: Microdata и RDFa. Но для большинства современных сайтов JSON-LD удобнее: его легче генерировать в шаблонах, валидировать, обновлять и поддерживать.</p><h3>Rich results</h3><p><strong>Rich results</strong> — это расширенные результаты в поисковой выдаче: например, улучшенное отображение статьи, товара, видео, хлебных крошек или других поддерживаемых типов.</p><p>Главная ошибка — думать, что JSON-LD автоматически включает красивый сниппет. Это не так.</p><p>Корректная разметка — только одно из условий. Поисковая система сама решает, показывать ли расширенный формат, и учитывает правила конкретного типа, качество страницы, доверие к сайту, запрос пользователя и внешний вид выдачи.</p><aside class="post-disclaimer" role="note"><p>Правильная формулировка для бизнеса: JSON-LD может помочь странице претендовать на некоторые расширенные форматы, но не гарантирует их показ. Обещать «добавим JSON-LD — получите звёзды и рост позиций» некорректно.</p></aside><h2 id="types">Какие типы разметки нужны сайту </h2><p>Не нужно размечать всё подряд. Хорошая стратегия — начать с типов, которые действительно соответствуют структуре сайта и полезны для SEO-контроля.</p><h3>Organization</h3><p><code>Organization</code> описывает компанию, проект или бренд.</p><p>Для сайта услуг это часто базовый тип: название, URL, логотип, ссылки на соцсети, контакты. Обычно такую разметку размещают на главной странице или странице «О компании», а не копируют бездумно на каждую страницу.</p><p>Подходит для:</p><ul><li>корпоративного сайта;</li><li>агентства;</li><li>сервиса;</li><li>личного бренда;</li><li>студии;</li><li>SaaS;</li><li>проекта с понятным брендом.</li></ul><h3>LocalBusiness</h3><p><code>LocalBusiness</code> нужен, когда у бизнеса есть физическая точка, адрес, график работы, телефон и локальная привязка.</p><p>Подходит для:</p><ul><li>клиники;</li><li>салона;</li><li>кафе;</li><li>магазина;</li><li>офиса продаж;</li><li>сервисного центра;</li><li>локальной компании с адресом и временем работы.</li></ul><p>Если бизнес полностью онлайн и не обслуживает клиентов по конкретному адресу, <code>LocalBusiness</code> может быть не лучшим выбором. В таких случаях чаще используют <code>Organization</code>, <code>ProfessionalService</code>, <code>Service</code> или другие типы в зависимости от ситуации.</p><h3>Article и BlogPosting</h3><p><code>Article</code> и <code>BlogPosting</code> описывают статьи, новости, инструкции и материалы блога.</p><p>Для сайта с экспертным контентом это один из самых полезных типов. Он позволяет явно указать:</p><ul><li>заголовок;</li><li>описание;</li><li>изображение;</li><li>автора;</li><li>дату публикации;</li><li>дату обновления;</li><li>URL страницы;</li><li>издателя;</li><li>логотип;</li><li>основную сущность страницы.</li></ul><p>На сайте baranov.guru такой тип логично использовать для материалов в <code>/posts/</code>.</p><h3>BreadcrumbList</h3><p><code>BreadcrumbList</code> описывает хлебные крошки: путь от главной страницы к текущей.</p><p>Например:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Главная → Статьи → JSON-LD для сайта</span></span></code></pre><p>Для SEO это полезная навигационная разметка. Она помогает поисковым системам понять положение страницы в структуре сайта.</p><h3>FAQPage</h3><p><code>FAQPage</code> описывает блок вопросов и ответов.</p><p>Но здесь важно не переоценивать эффект. FAQ-разметка должна соответствовать реальному FAQ на странице. Вопросы и ответы должны быть видимы пользователю. Не нужно добавлять скрытые вопросы только ради разметки.</p><p>Даже если поисковая система больше не показывает FAQ-расширение для большинства обычных сайтов, FAQPage всё равно может быть полезна как структурированное описание блока вопросов.</p><h3>Product</h3><p><code>Product</code> нужен интернет-магазинам, каталогам, SaaS-тарифам и страницам продуктов, если на странице действительно есть продуктовые данные: название, изображение, описание, цена, наличие, отзывы, бренд и другие поля.</p><p>Но Product-разметка требует аккуратности. Нельзя подставлять фиктивные цены, несуществующие отзывы или данные, которых нет на странице.</p><h3>Service</h3><p><code>Service</code> описывает услугу. Для сайтов услуг это логичный тип, но важно понимать ограничение: сам факт наличия <code>Service</code> не означает, что поисковик покажет отдельный rich result.</p><p>Тем не менее <code>Service</code> может быть полезен как семантическое описание страницы услуги, особенно если он согласован с <code>Organization</code>, <code>WebSite</code>, breadcrumbs и видимым содержимым страницы.</p><div><p><img src="https://baranov.guru/assets/tools/json-ld-generator/cover.webp" alt="Генератор JSON-LD"></p><p><a href="https://baranov.guru/tools/json-ld-generator/">Генератор JSON-LD</a> — Создайте структурированные данные для сайта: Article, FAQPage, BreadcrumbList, Organization, LocalBusiness, WebSite и другие типы Schema.org.</p></div><h2 id="examples">Примеры JSON-LD </h2><p>Ниже — упрощённые примеры. В реальном проекте поля нужно подставлять из данных страницы: CMS, базы данных, markdown-frontmatter, product feed или шаблонов сайта.</p><h3>Organization</h3><p>Пример для сайта компании или личного бренда:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;script type=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"application/ld+json"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@context"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Organization"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@id"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/#organization"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "baranov.guru"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "url"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "logo"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/assets/logo.webp"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "sameAs"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    "https://t.me/baranov_guru"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ]</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/script></span></span></code></pre><p>Что важно:</p><ul><li><code>@id</code> помогает ссылаться на организацию из других JSON-LD-блоков;</li><li><code>url</code> должен быть основным адресом сайта;</li><li><code>logo</code> должен открываться по абсолютному URL;</li><li><code>sameAs</code> стоит добавлять только для реальных официальных профилей.</li></ul><h3>LocalBusiness</h3><p>Пример для бизнеса с физическим адресом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;script type=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"application/ld+json"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@context"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "LocalBusiness"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@id"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://example.com/#local-business"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Example Clinic"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "url"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://example.com/"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "image"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://example.com/assets/clinic.webp"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "telephone"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "+7 999 123-45-67"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "address"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "PostalAddress"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "streetAddress"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ул. Примерная, 10"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "addressLocality"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Москва"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "postalCode"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "123456"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "addressCountry"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "RU"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "openingHoursSpecification"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "OpeningHoursSpecification"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "dayOfWeek"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">        "Monday"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">        "Tuesday"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">        "Wednesday"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">        "Thursday"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">        "Friday"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      ]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "opens"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "09:00"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "closes"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "18:00"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ]</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/script></span></span></code></pre><p>Не используйте <code>LocalBusiness</code>, если у страницы нет реального адреса, телефона и локального смысла. Для онлайн-услуг лучше подобрать более подходящий тип.</p><h3>BlogPosting</h3><p>Пример для статьи:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;script type=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"application/ld+json"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@context"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "BlogPosting"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@id"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/posts/json-ld/#blogposting"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "mainEntityOfPage"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "WebPage"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "@id"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/posts/json-ld/"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "headline"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "JSON-LD для сайта: как структурированные данные помогают SEO и бизнесу"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "description"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Понятное руководство по JSON-LD для владельцев бизнеса, SEO-специалистов и маркетологов."</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "image"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/assets/tools/json-ld-generator/cover.webp"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "datePublished"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "2026-06-01"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "dateModified"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "2026-06-01"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "author"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Person"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Алексей Баранов"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "url"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/about/"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "publisher"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Organization"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "@id"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/#organization"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "baranov.guru"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "logo"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ImageObject"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "url"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/assets/authors/avatar.webp"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/script></span></span></code></pre><p>Что важно для статей:</p><ul><li><code>headline</code> должен соответствовать реальному заголовку;</li><li><code>datePublished</code> — дата первой публикации;</li><li><code>dateModified</code> — дата обновления, если материал менялся;</li><li><code>author</code> должен быть реальным автором;</li><li><code>image</code> должен быть доступен поисковому роботу;</li><li>разметка не должна описывать другую статью или старую версию страницы.</li></ul><h3>BreadcrumbList</h3><p>Пример хлебных крошек:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;script type=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"application/ld+json"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@context"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "BreadcrumbList"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "itemListElement"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ListItem"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "position"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 1</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Главная"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "item"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ListItem"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "position"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 2</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Статьи"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "item"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/posts/"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ListItem"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "position"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 3</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "JSON-LD для сайта"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "item"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/posts/json-ld/"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ]</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/script></span></span></code></pre><p>Хлебные крошки должны совпадать с логикой навигации сайта. Не стоит генерировать фиктивный путь только ради разметки.</p><h3>FAQPage</h3><p>Пример FAQ-разметки:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;script type=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"application/ld+json"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@context"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "FAQPage"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "mainEntity"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Question"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "JSON-LD гарантирует расширенный сниппет?"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "acceptedAnswer"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">        "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Answer"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">        "text"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Нет. JSON-LD помогает поисковым системам понять страницу, но не гарантирует показ расширенного результата."</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Question"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Куда вставлять JSON-LD?"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">      "acceptedAnswer"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">        "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Answer"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">        "text"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Обычно JSON-LD вставляют через script type=\"application/ld+json\" в head или body страницы."</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ]</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/script></span></span></code></pre><p>Главное правило: вопросы и ответы должны быть видимы на странице. Если FAQ есть только в JSON-LD, но отсутствует в контенте, это плохая практика.</p><h3>Service</h3><p>Пример для страницы услуги:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;script type=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"application/ld+json"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@context"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Service"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@id"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/services/technical-seo/#service"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Техническая SEO-оптимизация"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "description"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Аудит индексации, sitemap.xml, robots.txt, canonical, дублей, редиректов, Open Graph, JSON-LD и технических ошибок сайта."</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "url"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/services/technical-seo/"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "provider"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Organization"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "@id"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/#organization"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "areaServed"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Country"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Россия"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/script></span></span></code></pre><p><code>Service</code> стоит использовать как аккуратное описание услуги, а не как обещание отдельного визуального блока в выдаче.</p></div><aside><h3>Хотите быстро собрать JSON-LD для страницы?</h3><p>Создайте структурированные данные для статьи, организации, локального бизнеса, хлебных крошек или FAQ без ручной сборки JSON.</p></aside><div><h2 id="implementation">Как внедрить JSON-LD </h2><p>Внедрение зависит от того, как устроен сайт: CMS, конструктор, Next.js, интернет-магазин, headless CMS, статический блог или самописная система.</p><p>Но общий процесс одинаковый.</p><h3>Шаг 1. Определите типы страниц</h3><p>Сначала нужно понять, какие шаблоны есть на сайте.</p><p>Например:</p><ul><li>главная страница;</li><li>страница «О компании»;</li><li>страницы услуг;</li><li>статьи блога;</li><li>карточки товаров;</li><li>категории;</li><li>кейсы;</li><li>инструменты;</li><li>контакты;</li><li>страницы филиалов;</li><li>страницы FAQ.</li></ul><p>Для каждого шаблона подбирается свой тип JSON-LD. Не нужно пытаться вставить одинаковый блок на весь сайт.</p><h3>Шаг 2. Сопоставьте поля с реальными данными</h3><p>Плохой подход — вручную вставить один пример из интернета и забыть.</p><p>Хороший подход — связать JSON-LD с источником данных:</p><ul><li>frontmatter статьи;</li><li>карточкой товара;</li><li>данными CMS;</li><li>настройками сайта;</li><li>профилем организации;</li><li>адресами филиалов;</li><li>breadcrumbs;</li><li>датой публикации и обновления;</li><li>автором;</li><li>изображением страницы.</li></ul><p>Если редактор меняет заголовок статьи, JSON-LD тоже должен обновиться. Если товар стал недоступен, это должно отразиться в разметке. Если страница услуги переехала, URL в JSON-LD должен поменяться вместе с canonical.</p><h3>Шаг 3. Генерируйте разметку на сервере</h3><p>Для SEO надёжнее, когда JSON-LD попадает в HTML сразу, а не появляется только после долгой клиентской загрузки.</p><p>На современных frontend-проектах это особенно важно. Если сайт на Next.js, Nuxt, Astro или другом SSR/SSG-стеке, JSON-LD лучше генерировать на сервере из данных страницы.</p><p>Упрощённый пример для React/Next.js:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> jsonLd</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  "@context"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "BlogPosting"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  headline</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.description</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  datePublished</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  dateModified</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.updatedAt </span><span style="color:#D32F2F;--shiki-dark:#F97583">??</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  author</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Person"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">author</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.name</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> PostJsonLd</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">script</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      type</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"application/ld+json"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      dangerouslySetInnerHTML</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{{</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        __html</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> JSON</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.stringify</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(jsonLd)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.replace</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">/&#x3C;/</span><span style="color:#D32F2F;--shiki-dark:#F97583">g</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "\\u003c"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  )</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Важно экранировать символ <code>&#x3C;</code>, чтобы снизить риск проблем при вставке JSON в HTML.</p><div><p><img src="https://baranov.guru/assets/posts/nextjs-json-ld/cover.webp" alt="JSON-LD в Next.js"></p><p><a href="https://baranov.guru/posts/nextjs-json-ld/">JSON-LD в Next.js</a> — Как добавить структурированные данные Schema.org в проект на Next.js и не сломать HTML, SEO и валидацию.</p></div><h3>Шаг 4. Не смешивайте разные страницы</h3><p>Одна из частых ошибок — когда шаблон копирует разметку с главной страницы на все URL.</p><p>Например, каждая статья получает <code>Organization</code> как основную сущность, каждая услуга размечается как <code>Article</code>, а каждая страница сайта получает одинаковый <code>FAQPage</code>.</p><p>Такой подход создаёт шум. JSON-LD должен описывать конкретную страницу.</p><p>Можно использовать несколько блоков JSON-LD на одной странице, если они дополняют друг друга:</p><ul><li><code>Organization</code> — для бренда;</li><li><code>WebSite</code> — для сайта;</li><li><code>BreadcrumbList</code> — для навигации;</li><li><code>BlogPosting</code> — для статьи;</li><li><code>FAQPage</code> — для блока вопросов;</li><li><code>Service</code> — для страницы услуги.</li></ul><p>Но между ними должна быть логика, а не набор случайных объектов.</p><h3>Шаг 5. Добавьте проверку в релизный процесс</h3><p>JSON-LD часто ломается не в момент первого внедрения, а позже:</p><ul><li>после редизайна;</li><li>после миграции на новую CMS;</li><li>после изменения URL;</li><li>после переезда на Next.js;</li><li>после изменения структуры breadcrumbs;</li><li>после переименования полей в CMS;</li><li>после смены CDN;</li><li>после автоматизации обложек;</li><li>после удаления старых страниц.</li></ul><p>Поэтому проверку структурированных данных стоит включить в технический чек-лист вместе с sitemap.xml, robots.txt, canonical, Open Graph и статусами ответа.</p><h2 id="mistakes">Частые ошибки JSON-LD </h2><p>Ошибки JSON-LD бывают двух типов: синтаксические и смысловые.</p><p>Синтаксические валидатор обычно находит быстро. Смысловые опаснее: код формально корректный, но описывает страницу неправильно.</p><h3>Ошибка 1. Разметка не соответствует содержимому страницы</h3><p>Например, в JSON-LD указаны вопросы и ответы, которых нет на странице. Или в Product-разметке есть цена, но пользователь её не видит. Или дата обновления в JSON-LD новая, а текст статьи не менялся.</p><p>Поисковая система ожидает, что структурированные данные описывают видимый контент. Если разметка живёт отдельно от страницы, это риск.</p><h3>Ошибка 2. Один JSON-LD для всех страниц</h3><p>Частый сценарий: разработчик добавил общий JSON-LD в layout сайта, и он появился везде.</p><p>В результате:</p><ul><li>главная;</li><li>услуги;</li><li>статьи;</li><li>кейсы;</li><li>инструменты;</li><li>контакты;</li></ul><p>получают одинаковую структурированную разметку.</p><p>Формально код есть. По смыслу — бесполезен или вреден.</p><h3>Ошибка 3. Неправильный тип Schema.org</h3><p>Не каждая страница должна быть <code>Article</code>. Не каждый бизнес должен быть <code>LocalBusiness</code>. Не каждая услуга должна размечаться как <code>Product</code>.</p><p>Тип нужно выбирать по содержимому страницы, а не по желанию получить красивый сниппет.</p><h3>Ошибка 4. Относительные URL</h3><p>В JSON-LD лучше использовать абсолютные URL:</p><p>Плохо:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "url"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "/posts/json-ld/"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "image"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "/assets/cover.webp"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Лучше:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "url"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/posts/json-ld/"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "image"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/assets/tools/json-ld-generator/cover.webp"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Так меньше риска, что парсер неправильно интерпретирует путь.</p><h3>Ошибка 5. Несогласованность с canonical</h3><p>Если canonical указывает один URL, Open Graph — второй, sitemap.xml — третий, а JSON-LD — четвёртый, сайт сам создаёт противоречие.</p><p>Проверяйте согласованность:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">link</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rel</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"canonical"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> href</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://baranov.guru/posts/json-ld/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:url"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://baranov.guru/posts/json-ld/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "url"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/posts/json-ld/"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "mainEntityOfPage"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "@id"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/posts/json-ld/"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h3>Ошибка 6. Невалидный JSON</h3><p>JSON не терпит мелких ошибок:</p><ul><li>лишняя запятая в конце массива;</li><li>одинарные кавычки вместо двойных;</li><li>комментарии внутри JSON;</li><li>неэкранированные кавычки в тексте;</li><li>случайный HTML внутри строки;</li><li>незакрытая скобка.</li></ul><p>Плохо:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@context"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Article"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "headline"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "JSON-LD для сайта"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>В конце последнего свойства не должно быть запятой.</p><h3>Ошибка 7. Устаревшие данные</h3><p>Сайт обновился, но JSON-LD остался старым:</p><ul><li>старый логотип;</li><li>старый адрес;</li><li>старая дата публикации;</li><li>старый телефон;</li><li>старый URL;</li><li>старые breadcrumbs;</li><li>удалённая картинка;</li><li>устаревшее название услуги.</li></ul><p>Для пользователя это может быть незаметно. Для SEO-аудита — явный сигнал, что метаданные живут отдельно от контента.</p><h3>Ошибка 8. Ожидание мгновенного эффекта</h3><p>JSON-LD — не кнопка «поднять позиции». Если на сайте проблемы с индексацией, дублями, скоростью, качеством страниц, внутренней перелинковкой или техническими ошибками, одна разметка не решит проблему.</p><p>Сначала нужно убедиться, что страница:</p><ul><li>доступна поисковым роботам;</li><li>не закрыта в robots.txt;</li><li>не содержит noindex;</li><li>возвращает 200 OK;</li><li>есть в sitemap.xml, если должна индексироваться;</li><li>имеет корректный canonical;</li><li>содержит полезный контент;</li><li>нормально отображается без критичных ошибок.</li></ul><aside class="post-disclaimer" role="note"><p>Если страница не индексируется или закрыта от поисковиков, JSON-LD не спасёт ситуацию. Сначала нужно решить базовые технические проблемы, а уже потом ожидать пользы от структурированных данных.</p></aside></div><aside><h3>Проверьте JSON-LD перед публикацией</h3><p>Вставьте код или URL страницы и проверьте синтаксис, типы Schema.org, обязательные поля и типовые ошибки структурированных данных.</p></aside><div><h2 id="validation">Как проверить разметку </h2><p>Проверять JSON-LD лучше в несколько этапов. Один валидатор не заменяет смысловую проверку.</p><h3>Шаг 1. Проверьте исходный HTML</h3><p>Откройте код страницы и найдите:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">script</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> type</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"application/ld+json"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><p>Проверьте, что JSON-LD действительно присутствует в HTML и не появляется только после сложной клиентской логики, которую поисковый робот может не обработать вовремя.</p><h3>Шаг 2. Проверьте JSON-синтаксис</h3><p>Скопируйте содержимое script-блока и проверьте, что это валидный JSON.</p><p>Особенно внимательно смотрите на:</p><ul><li>кавычки;</li><li>запятые;</li><li>переносы строк;</li><li>спецсимволы;</li><li>HTML внутри строк;</li><li>динамические значения из CMS.</li></ul><h3>Шаг 3. Проверьте Schema.org-структуру</h3><p>Дальше проверьте, что типы и свойства используются корректно.</p><p>Для этого полезен валидатор JSON-LD или Schema.org. Он покажет, какие сущности найдены, какие поля распознаны и где есть ошибки.</p><div><p><img src="https://baranov.guru/assets/tools/json-ld-validator/cover.webp" alt="Валидатор JSON-LD"></p><p><a href="https://baranov.guru/tools/json-ld-validator/">Валидатор JSON-LD</a> — Проверьте структурированные данные: синтаксис JSON, типы Schema.org, обязательные поля и ошибки JSON-LD-разметки.</p></div><h3>Шаг 4. Сравните JSON-LD с видимой страницей</h3><p>Это самый важный этап.</p><p>Проверьте:</p><ul><li>совпадает ли <code>headline</code> с заголовком статьи;</li><li>виден ли автор на странице;</li><li>соответствует ли дата публикации;</li><li>доступно ли изображение;</li><li>совпадает ли URL с canonical;</li><li>реальные ли хлебные крошки;</li><li>есть ли FAQ на странице;</li><li>указан ли настоящий адрес бизнеса;</li><li>не размечены ли скрытые или несуществующие данные.</li></ul><p>Валидатор может не понять бизнес-смысл страницы. Это должна проверить команда.</p><h3>Шаг 5. Проверьте в инструментах поисковых систем</h3><p>После технической проверки используйте инструменты поисковых систем: тесты расширенных результатов, инспекцию URL, отчёты по улучшениям и ошибки в вебмастер-панелях.</p><p>Важно смотреть не только на одну страницу, а на шаблоны:</p><ul><li>одна статья;</li><li>несколько статей;</li><li>одна услуга;</li><li>несколько услуг;</li><li>главная;</li><li>страница контактов;</li><li>категория;</li><li>карточка товара.</li></ul><p>Если ошибка есть в шаблоне, она повторяется на десятках или тысячах страниц.</p><h3>Шаг 6. Добавьте мониторинг</h3><p>Для крупного сайта полезно автоматически проверять JSON-LD после релизов.</p><p>Минимальный чек-лист:</p><ul><li>script-блок присутствует;</li><li>JSON валиден;</li><li>canonical совпадает с URL в JSON-LD;</li><li>обязательные поля не пустые;</li><li>изображения открываются;</li><li>даты корректны;</li><li>breadcrumbs соответствуют структуре сайта;</li><li>нет одинакового JSON-LD на всех страницах;</li><li>разметка не описывает скрытый контент.</li></ul><h2 id="service">Когда нужен технический SEO-аудит </h2><p>Если нужно добавить JSON-LD на одну статью или страницу, это можно сделать самостоятельно: выбрать тип, заполнить поля, вставить script-блок и проверить валидатором.</p><p>Но если сайт коммерческий, многостраничный или регулярно обновляется, JSON-LD почти всегда связан с общей технической архитектурой.</p><p>Повод смотреть глубже:</p><ul><li>у всех страниц одинаковая разметка;</li><li>JSON-LD пропадает после релизов;</li><li>CMS не даёт управлять нужными полями;</li><li>canonical и JSON-LD указывают разные URL;</li><li>изображения из разметки закрыты или удалены;</li><li>статьи получают неправильные даты;</li><li>товары содержат устаревшие цены;</li><li>breadcrumbs не совпадают с навигацией;</li><li>FAQ-разметка описывает скрытый блок;</li><li>после миграции на новый frontend структурированные данные сломались;</li><li>Search Console показывает ошибки или предупреждения;</li><li>разработчики и SEO-команда не понимают, откуда генерируется JSON-LD.</li></ul><div><p><img src="https://baranov.guru/assets/services/technical-seo/cover.webp" alt="Техническая SEO-оптимизация"></p><p><a href="https://baranov.guru/services/technical-seo/">Техническая SEO-оптимизация</a> — Аудит индексации, sitemap.xml, robots.txt, canonical, дублей, редиректов, Open Graph, JSON-LD и технических ошибок сайта.</p></div><p>В техническом SEO-аудите JSON-LD стоит проверять не отдельно, а вместе с другими сигналами:</p><ul><li>индексацией;</li><li>sitemap.xml;</li><li>robots.txt;</li><li>canonical;</li><li>редиректами;</li><li>дублями;</li><li>Open Graph;</li><li>мета-тегами;</li><li>внутренней перелинковкой;</li><li>статусами ответа;</li><li>JavaScript-рендерингом;</li><li>скоростью и доступностью;</li><li>структурой шаблонов;</li><li>ошибками в Google Search Console и Яндекс Вебмастере.</li></ul><p>Если сайт сделан на Next.js, headless CMS или сложном JavaScript-стеке, важно не просто вставить JSON-LD, а встроить его в генерацию метаданных. Тогда структурированные данные будут обновляться вместе с контентом, а не жить отдельным устаревшим блоком.</p><div><p><img src="https://baranov.guru/assets/services/frontend/cover.webp" alt="Frontend-разработка"></p><p><a href="https://baranov.guru/services/frontend/">Frontend-разработка</a> — Исправление технических проблем интерфейса, которые мешают скорости, доступности, индексации и корректному отображению метаданных.</p></div><h2>Короткий итог</h2><p>JSON-LD нужен не для галочки.</p><p>Он помогает поисковым системам и другим сервисам лучше понимать сайт: где статья, где организация, где услуга, где локальный бизнес, где хлебные крошки, где вопросы и ответы, где автор и какие URL являются основными.</p><p>Для бизнеса польза JSON-LD в том, что важные страницы становятся более понятными для машинной обработки. Это не гарантирует рост позиций и не включает rich results автоматически, но помогает убрать неоднозначность и подготовить сайт к корректному отображению в поддерживаемых поисковых форматах.</p><p>Минимум, который стоит проверить на важном сайте:</p><ul><li><code>Organization</code> или подходящий тип для бренда;</li><li><code>BreadcrumbList</code> для навигации;</li><li><code>BlogPosting</code> или <code>Article</code> для статей;</li><li><code>FAQPage</code>, если на странице есть реальный FAQ;</li><li><code>LocalBusiness</code>, если есть физическая точка;</li><li><code>Product</code>, если есть реальные продуктовые данные;</li><li>согласованность с canonical;</li><li>доступность изображений;</li><li>отсутствие скрытых или выдуманных данных;</li><li>валидацию после релизов.</li></ul><p>JSON-LD работает лучше всего не как отдельный фрагмент кода, а как часть общей системы технического SEO: вместе с sitemap.xml, robots.txt, Open Graph, canonical, нормальной архитектурой URL и качественным контентом.</p><aside class="post-disclaimer" role="note"><p>Эта статья объясняет общий подход к JSON-LD и структурированным данным. Для конкретного сайта выводы нужно делать после проверки HTML-кода страниц, шаблонов CMS, canonical, sitemap.xml, robots.txt, JavaScript-рендеринга, Search Console, Яндекс Вебмастера и фактического содержимого страниц.</p></aside></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/json-ld/og.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Open Graph для сайта: как управлять превью ссылок и не терять клики]]></title>
            <link>https://baranov.guru/posts/open-graph/</link>
            <guid>https://baranov.guru/posts/open-graph</guid>
            <pubDate>Thu, 28 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Понятное руководство по Open Graph для владельцев бизнеса, SEO-специалистов и маркетологов: зачем нужны OG-теги, как они влияют на клики из соцсетей и мессенджеров, какие поля настроить и как проверить превью ссылок.]]></description>
            <content:encoded><![CDATA[<div><h2 id="what">Что такое Open Graph </h2><p><strong>Open Graph</strong> — это набор мета-тегов в HTML-коде страницы, который помогает соцсетям, мессенджерам и другим сервисам правильно показывать ссылку на ваш сайт.</p><p>Если объяснять без технического жаргона, Open Graph отвечает за то, как ссылка выглядит, когда её отправляют клиенту, публикуют в соцсетях, добавляют в пост, вставляют в чат или пересылают внутри команды.</p><p>Обычно пользователь видит не просто URL, а карточку:</p><ul><li>заголовок страницы;</li><li>короткое описание;</li><li>изображение;</li><li>домен сайта;</li><li>иногда тип контента: статья, сайт, видео, товар или другой объект.</li></ul><p>Например, одна и та же ссылка может выглядеть по-разному.</p><p>Плохой вариант:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>https://example.com/services/seo-audit/</span></span></code></pre><p>Или ещё хуже:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Главная | Site Name</span></span>
<span class="line"><span>Описание страницы не найдено</span></span>
<span class="line"><span>[пустая серая картинка]</span></span></code></pre><p>Хороший вариант:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Технический SEO-аудит сайта</span></span>
<span class="line"><span>Проверим индексацию, sitemap.xml, robots.txt, canonical, дубли и ошибки, которые мешают росту органического трафика.</span></span>
<span class="line"><span>[понятная брендированная обложка]</span></span></code></pre><p>Разница кажется небольшой, но для маркетинга она критична. Пользователь принимает решение о клике за секунды. Если карточка ссылки выглядит случайно, непонятно или неаккуратно, доверие к странице снижается ещё до перехода на сайт.</p><p>Open Graph не заменяет SEO, контент, рекламу или нормальную посадочную страницу. Но он отвечает за важный первый контакт: <strong>как ваш сайт выглядит за пределами самого сайта</strong>.</p><aside class="post-disclaimer" role="note"><p>Open Graph — это не «тег для программистов», а часть упаковки сайта. Если ссылка на коммерческую страницу выглядит плохо, бизнес может терять клики из каналов, где решение принимается по превью: Telegram, WhatsApp, Slack, соцсети, рассылки и внутренние чаты клиентов.</p></aside><p>Минимальный пример Open Graph-разметки выглядит так:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:title"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Технический SEO-аудит сайта"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:description"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Проверим индексацию, sitemap.xml, robots.txt, canonical, дубли и технические ошибки."</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:type"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"website"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:url"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/services/technical-seo/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:image"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/assets/og/technical-seo.webp"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:site_name"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Example"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Эти строки находятся в <code>&#x3C;head></code> страницы и обычно не видны посетителю напрямую. Но именно их считывают сервисы, когда строят карточку ссылки.</p><h2 id="business">Зачем Open Graph бизнесу </h2><p>Для владельца бизнеса Open Graph важен не как техническая аббревиатура. Важен результат: <strong>ссылки на сайт должны выглядеть убедительно там, где их видят потенциальные клиенты</strong>.</p><p>Сайт редко живёт только в поисковой выдаче. Его страницы отправляют менеджеры клиентам, добавляют в коммерческие предложения, публикуют в Telegram-каналах, пересылают в WhatsApp, обсуждают в Slack, прикладывают к презентациям, размещают в постах, рассылках и рекламных материалах.</p><p>В каждом таком сценарии ссылка либо помогает продаже, либо мешает ей.</p><p>Если у страницы нет нормального Open Graph, сервис может взять случайный заголовок, неподходящее описание или первую попавшуюся картинку. Иногда превью не появляется вообще. В результате хорошая страница выглядит как техническая заготовка.</p><p>Для бизнеса это проявляется не как «ошибка OG-тегов», а как более понятные симптомы:</p><ul><li>ссылка в Telegram выглядит без картинки;</li><li>у страницы услуги подтягивается логотип вместо нормальной обложки;</li><li>в описании показывается мусор из меню, cookie-banner или футера;</li><li>у статьи в соцсетях старый заголовок после редактирования;</li><li>у всех страниц сайта одинаковое превью;</li><li>в мессенджере показывается устаревшая акция;</li><li>ссылка выглядит хуже, чем у конкурентов;</li><li>команда вручную дописывает пояснения к каждой ссылке, потому что карточка ничего не объясняет.</li></ul><p>Open Graph помогает убрать эту случайность. Он превращает ссылку в контролируемый элемент коммуникации.</p><p>На практике хороший Open Graph даёт бизнесу несколько эффектов:</p><ul><li>повышает доверие к ссылке до клика;</li><li>делает публикации в соцсетях и мессенджерах аккуратнее;</li><li>помогает объяснить ценность страницы прямо в карточке;</li><li>снижает риск, что сервис подтянет неподходящий текст или изображение;</li><li>делает бренд визуально узнаваемым;</li><li>помогает маркетингу, продажам и SEO-команде работать с одними и теми же посадочными страницами.</li></ul><p>Важно понимать: Open Graph не гарантирует трафик. Но он влияет на то, как пользователь воспринимает ссылку в момент выбора — открыть её или пролистать мимо.</p></div><div><p>Open Graph не поднимает страницу в поиске сам по себе. Но он влияет на то, как ссылка выглядит в каналах, где пользователь решает, доверять ли ей и стоит ли переходить.</p></div><section><h2>Что даёт грамотный Open Graph</h2><p>OG-теги помогают контролировать первое впечатление от ссылки: заголовок, описание, изображение и смысл страницы до перехода на сайт.</p><div><article><h3>Больше доверия к ссылке</h3><p>Аккуратная карточка выглядит как часть бренда, а не как случайный технический URL.</p></article><article><h3>Понятнее ценность страницы</h3><p>Пользователь сразу видит, что именно откроет: услугу, статью, кейс, товар или инструмент.</p></article><article><h3>Лучше публикации в соцсетях</h3><p>Посты, репосты и пересылки выглядят профессионально без ручной доработки каждого сообщения.</p></article><article><h3>Меньше случайных превью</h3><p>Сервисы не берут первое попавшееся изображение, текст из меню или устаревшие данные страницы.</p></article><article><h3>Проще поддерживать бренд</h3><p>Обложки можно оформить в едином стиле для услуг, статей, кейсов, инструментов и лендингов.</p></article><article><h3>Легче находить ошибки</h3><p>Валидатор быстро показывает, какие теги отсутствуют, какие URL недоступны и почему превью ломается.</p></article></div></section><div><h2 id="where">Где используется Open Graph </h2><p>Open Graph появился как протокол для социальных графов, но на практике его используют гораздо шире, чем классические соцсети.</p><p>OG-теги могут считывать:</p><ul><li><a href="">LinkedIn</a></li><li><a href="">Telegram</a></li><li><a href="">WhatsApp</a></li><li><a href="">Slack</a></li><li><a href="">Discord</a></li><li><a href="">X / Twitter</a></li><li><a href="">мессенджеры</a></li><li><a href="">CRM</a></li><li><a href="">внутренние порталы</a></li></ul><p>У каждой платформы есть свои особенности: где-то картинка обрезается иначе, где-то описание короче, где-то сильнее кэшируется старое превью. Но базовая идея одинаковая: сервис открывает URL, читает HTML и пытается собрать карточку ссылки.</p><p>Если OG-теги заданы явно, у сайта больше контроля. Если нет — платформа пытается угадать сама.</p><h3>Соцсети</h3><p>В соцсетях Open Graph влияет на внешний вид публикации. Когда человек делится ссылкой на статью, услугу или кейс, карточка должна быть понятной без дополнительного объяснения.</p><p>Для контент-маркетинга это особенно важно. Статья может быть полезной, но если её превью выглядит скучно или не отражает тему, часть аудитории просто не перейдёт.</p><h3>Мессенджеры</h3><p>В мессенджерах ссылка часто оказывается в личном контексте: клиент спрашивает менеджера, коллега отправляет материал руководителю, подрядчик пересылает страницу проекта.</p><p>Здесь Open Graph работает как мини-презентация. Хорошая карточка помогает быстро понять, что это за ссылка и почему её стоит открыть.</p><h3>Рабочие чаты</h3><p>В Slack, Discord, корпоративных чатах и таск-трекерах превью ссылок помогает командам быстрее ориентироваться в материалах.</p><p>Это важно не только для внешнего маркетинга. Если внутри компании каждая ссылка на сайт выглядит одинаково или непонятно, команде сложнее работать с контентом, задачами и релизами.</p><h3>Сервисы предпросмотра и парсеры</h3><p>Некоторые CRM, конструкторы писем, планировщики публикаций и маркетинговые инструменты тоже читают метаданные страницы. Поэтому Open Graph часто влияет на то, как сайт выглядит не только в публичных каналах, но и в инструментах команды.</p><h2 id="tags">Какие OG-теги нужны сайту </h2><p>Open Graph состоит из набора мета-тегов. Не нужно запоминать десятки свойств для каждой страницы. В большинстве случаев достаточно правильно настроить базовый набор и расширять его для статей, товаров, видео или других специальных типов контента.</p><h3>Базовые теги</h3><p>Почти каждой важной странице нужны:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:title"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Заголовок страницы"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:description"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Короткое описание страницы"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:type"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"website"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:url"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/page/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:image"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/og/page.webp"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:site_name"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Example"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Разберём их по смыслу.</p><h3>og:title</h3><p><code>og:title</code> — заголовок карточки. Он должен объяснять, что пользователь откроет после клика.</p><p>Плохой вариант:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Услуги</span></span></code></pre><p>Лучше:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Технический SEO-аудит сайта</span></span></code></pre><p>Ещё лучше, если заголовок конкретный и соответствует странице:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Технический SEO-аудит: индексация, дубли, sitemap.xml и robots.txt</span></span></code></pre><p>Не стоит делать <code>og:title</code> слишком длинным. В разных интерфейсах он может обрезаться, поэтому главная мысль должна быть в начале.</p><h3>og:description</h3><p><code>og:description</code> — короткое пояснение. Его задача — дополнить заголовок, а не повторять его теми же словами.</p><p>Плохой вариант:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Мы оказываем услуги высокого качества для вашего бизнеса.</span></span></code></pre><p>Лучше:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Проверим, почему поисковики не видят важные страницы, где появляются дубли и какие технические ошибки мешают росту органического трафика.</span></span></code></pre><p>Описание должно быть человеческим. Не нужно набивать его ключевыми словами. Это текст для карточки ссылки, а не поле для SEO-спама.</p><h3>og:type</h3><p><code>og:type</code> показывает тип объекта. Для большинства обычных страниц подходит:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:type"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"website"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Для статей обычно используют:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:type"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"article"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Если у страницы тип <code>article</code>, можно добавить дополнительные article-теги:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"article:published_time"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"2026-05-28T10:00:00+03:00"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"article:modified_time"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"2026-05-28T12:00:00+03:00"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"article:author"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Алексей Баранов"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"article:tag"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"SEO"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"article:tag"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Open Graph"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><h3>og:url</h3><p><code>og:url</code> — канонический URL страницы для карточки. Он должен совпадать с основной версией страницы, которую вы хотите показывать пользователям и поисковым системам.</p><p>Например:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:url"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/posts/open-graph/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Не стоит указывать URL с UTM-метками, параметрами фильтров, временными query-параметрами или техническими адресами.</p><p>Плохо:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:url"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/posts/open-graph/?utm_source=telegram"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Лучше:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:url"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/posts/open-graph/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><h3>og:image</h3><p><code>og:image</code> — изображение карточки. Это самый заметный элемент Open Graph.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:image"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/assets/posts/open-graph/og.webp"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Картинка должна быть доступна по прямому абсолютному URL. Если сервис не может загрузить изображение, карточка может показаться без обложки.</p><p>Дополнительно можно указать размеры и alt:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:image:width"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"1200"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:image:height"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"630"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:image:alt"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Open Graph для сайта: превью ссылок в соцсетях и мессенджерах"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p><code>og:image:alt</code> полезен как понятное описание изображения. Его не стоит оставлять пустым, особенно если обложка содержит смысловой текст.</p><h3>og:site_name</h3><p><code>og:site_name</code> помогает платформам показать название сайта или бренда:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:site_name"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"baranov.guru"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Это особенно полезно, когда ссылкой делятся вне контекста вашего сайта.</p><h3>og:locale</h3><p>Для русскоязычного сайта можно указать локаль:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:locale"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"ru_RU"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Если сайт мультиязычный, важно не путать локали и не показывать русское превью на английской версии страницы.</p><div><p><img src="https://baranov.guru/assets/tools/open-graph-generator/cover.webp" alt="Генератор Open Graph"></p><p><a href="https://baranov.guru/tools/open-graph-generator/">Генератор Open Graph</a> — Создайте Open Graph и Twitter Card теги для страницы: title, description, image, url, type и дополнительные метаданные.</p></div><h2 id="image">Как подготовить og:image </h2><p>Изображение — самая частая причина проблем с Open Graph. Заголовок и описание можно быстро поправить в CMS, а вот плохая картинка часто ломает весь визуальный эффект.</p><p>Хороший <code>og:image</code> должен быть:</p><ul><li>понятным;</li><li>брендированным;</li><li>контрастным;</li><li>читаемым на маленьком экране;</li><li>доступным по прямому URL;</li><li>не слишком тяжёлым;</li><li>подходящим под обрезку в разных интерфейсах.</li></ul><p>Практичный размер для большинства сценариев — около <strong>1200×630 px</strong>. Это не единственный возможный вариант, но удобный стандарт для широких карточек.</p><h3>Что должно быть на изображении</h3><p>Для коммерческой страницы обычно достаточно:</p><ul><li>короткого заголовка услуги или продукта;</li><li>логотипа или домена;</li><li>визуального акцента;</li><li>спокойного фона;</li><li>понятной иерархии текста.</li></ul><p>Не нужно переносить на OG-обложку весь экран лендинга. В маленьком превью мелкий текст всё равно не прочитают.</p><p>Хорошая обложка работает как афиша: она быстро объясняет тему, но не пытается заменить страницу.</p><h3>Не используйте случайные изображения</h3><p>Частая ошибка — когда сервис подтягивает первое изображение со страницы: аватар автора, логотип, иконку, баннер cookie или картинку из футера.</p><p>Для пользователя такое превью выглядит случайным. Для бренда — неаккуратным.</p><p>Лучше подготовить отдельные шаблоны:</p><ul><li>для главной страницы;</li><li>для услуг;</li><li>для статей;</li><li>для кейсов;</li><li>для инструментов;</li><li>для товарных или категорийных страниц.</li></ul><h3>Следите за обрезкой</h3><p>Разные платформы могут обрезать изображение по-разному. Поэтому важные элементы лучше держать ближе к центру и не ставить критичный текст у краёв.</p><p>Безопасный подход:</p><ul><li>не размещать мелкий текст по углам;</li><li>оставлять поля вокруг заголовка;</li><li>проверять картинку на мобильном экране;</li><li>не делать обложку слишком перегруженной;</li><li>не использовать низкий контраст.</li></ul><h3>Изображение должно открываться без авторизации</h3><p>Если картинка доступна только после авторизации, закрыта от ботов, отдаётся с ошибкой, редиректит на CDN с проблемами или блокируется правилами, соцсеть может не показать превью.</p><p>Проверьте, что URL из <code>og:image</code> открывается напрямую в браузере и возвращает корректный статус ответа.</p><aside class="post-disclaimer" role="note"><p>Open Graph-изображение — это не просто картинка для соцсетей. Это часть дизайн-системы сайта. Если для всех статей, услуг и кейсов есть единый шаблон OG-обложек, бренд выглядит стабильнее во всех каналах распространения.</p></aside><h2 id="seo-jsonld">Open Graph, SEO-теги и JSON-LD </h2><p>Open Graph часто путают с SEO-тегами. Это понятно: все они находятся в <code>&#x3C;head></code> страницы и описывают контент. Но задачи у них разные.</p><h3>title</h3><p><code>&#x3C;title></code> — это заголовок документа. Он важен для браузера, поисковой выдачи и общей структуры страницы.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">title</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>Open Graph для сайта: как настроить превью ссылок&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">title</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><p>Он может совпадать с <code>og:title</code>, но не обязан. Для поиска иногда нужен один акцент, для соцсетей — другой.</p><p>Например, для SEO можно сделать title более поисковым:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Open Graph для сайта: что это, зачем нужен и как настроить OG-теги</span></span></code></pre><p>А для карточки ссылки — более маркетинговым:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Ссылка на сайт должна выглядеть убедительно до клика</span></span></code></pre><p>Главное — не создавать противоречие. Пользователь должен получить то, что обещает карточка.</p><h3>meta description</h3><p><code>meta description</code> помогает описывать страницу для поисковых систем и сниппетов, хотя поисковик может сформировать описание сам.</p><p><code>og:description</code> управляет описанием в карточке ссылки.</p><p>Эти тексты можно делать похожими, но лучше учитывать контекст. В поиске пользователь сравнивает несколько результатов. В мессенджере он видит одну ссылку в потоке сообщений. Там описание должно быстрее объяснять ценность перехода.</p><h3>canonical</h3><p>Canonical показывает предпочтительную версию страницы для поисковых систем:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">link</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rel</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"canonical"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> href</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/posts/open-graph/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p><code>og:url</code> должен быть согласован с canonical. Если canonical указывает один URL, а <code>og:url</code> другой, сайт сам создаёт путаницу.</p><p>Плохо:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">link</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rel</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"canonical"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> href</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/posts/open-graph/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:url"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/posts/open-graph/?utm_source=telegram"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Лучше:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">link</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rel</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"canonical"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> href</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/posts/open-graph/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:url"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/posts/open-graph/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><h3>JSON-LD</h3><p>JSON-LD — это структурированные данные. Они помогают поисковым системам лучше понимать сущности страницы: статью, организацию, услугу, товар, FAQ, хлебные крошки и другие объекты.</p><p>Open Graph отвечает за карточку ссылки. JSON-LD — за структурированное описание данных для поисковых систем.</p><p>Они не заменяют друг друга.</p><p>Для статьи можно одновременно использовать:</p><ul><li><code>&#x3C;title></code>;</li><li><code>meta description</code>;</li><li>canonical;</li><li>Open Graph;</li><li>Twitter Card;</li><li>JSON-LD <code>BlogPosting</code>;</li><li>JSON-LD <code>FAQPage</code>, если на странице есть FAQ.</li></ul><div><p><img src="https://baranov.guru/assets/tools/json-ld-generator/cover.webp" alt="Генератор JSON-LD"></p><p><a href="https://baranov.guru/tools/json-ld-generator/">Генератор JSON-LD</a> — Создайте структурированные данные для сайта: Article, FAQPage, BreadcrumbList, Organization, LocalBusiness и другие типы Schema.org.</p></div><p>Для бизнеса важна не терминология, а согласованность. Если title обещает одно, Open Graph второе, canonical ведёт на третье, а JSON-LD описывает четвёртое — поисковики и пользователи получают смешанный сигнал.</p></div><div><p>Open Graph, SEO-теги и JSON-LD должны не дублировать друг друга механически, а рассказывать одну и ту же информацию о странице: что это, для кого она и какой URL является основным.</p></div><div><h2 id="implementation">Как внедрить Open Graph </h2><p>Способ внедрения зависит от сайта: CMS, конструктор, самописная разработка, Next.js, headless CMS, интернет-магазин или статический сайт.</p><p>Но логика везде одинаковая: для каждой важной страницы нужно сформировать метаданные и вывести их в <code>&#x3C;head></code>.</p><h3>В CMS</h3><p>В CMS обычно есть поля для SEO и социальных сетей. Иногда они называются:</p><ul><li>SEO title;</li><li>meta description;</li><li>social title;</li><li>social description;</li><li>social image;</li><li>Open Graph image;</li><li>preview image.</li></ul><p>Важно не ограничиться установкой плагина. Нужно проверить, что фактически появляется в HTML-коде страницы.</p><p>Минимальный чек-лист для CMS:</p><ul><li>у каждой важной страницы есть уникальный <code>og:title</code>;</li><li>описание не копируется бездумно со всех страниц;</li><li>изображение задано явно;</li><li>URL картинки абсолютный и доступный;</li><li><code>og:url</code> совпадает с каноническим URL;</li><li>для статей используются данные публикации и обновления;</li><li>после изменения контента превью обновляется.</li></ul><h3>На самописном сайте</h3><p>На самописном сайте OG-теги лучше включить в общий шаблон страницы, но данные брать из конкретной сущности: статьи, услуги, кейса, товара или категории.</p><p>Плохой подход — один общий набор тегов для всего сайта:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:title"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Главная"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:description"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Добро пожаловать на сайт"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:image"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/logo.png"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Так все страницы будут выглядеть одинаково.</p><p>Лучше, когда шаблон общий, а данные разные:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:title"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"{{ page.ogTitle }}"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:description"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"{{ page.ogDescription }}"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:url"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"{{ page.canonicalUrl }}"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:image"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"{{ page.ogImage }}"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><h3>В Next.js</h3><p>В Next.js Open Graph обычно настраивают через metadata API или через генерацию metadata для конкретного маршрута.</p><p>Упрощённый пример:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> metadata</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Open Graph для сайта"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Как настроить превью ссылок для соцсетей и мессенджеров."</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  openGraph</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Open Graph для сайта"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Как управлять превью ссылок и не терять клики."</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://example.com/posts/open-graph/"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    siteName</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Example"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    type</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "article"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    images</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://example.com/assets/posts/open-graph/og.webp"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        width</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 1200</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        height</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 630</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        alt</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Open Graph для сайта"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    ]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Для блога, кейсов и услуг лучше генерировать metadata из данных страницы. Тогда редактор меняет заголовок, описание и обложку в одном месте, а сайт автоматически выводит правильные теги.</p><h3>Динамические OG-изображения</h3><p>На современных проектах OG-обложки можно генерировать автоматически: например, подставлять в шаблон название статьи, категорию, автора и брендовые элементы.</p><p>Это удобно, если на сайте много контента:</p><ul><li>блог;</li><li>база знаний;</li><li>каталог;</li><li>новости;</li><li>кейсы;</li><li>документация;</li><li>страницы инструментов.</li></ul><p>Но автоматизация не должна превращаться в хаос. Нужны правила:</p><ul><li>длина заголовка ограничена;</li><li>длинные слова не ломают макет;</li><li>изображение проверяется на мобильном превью;</li><li>есть fallback для страниц без картинки;</li><li>старые обложки не кэшируются бесконечно;</li><li>шаблон соответствует бренду.</li></ul><h3>Twitter Card</h3><p>Помимо Open Graph часто добавляют Twitter Card-разметку:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"twitter:card"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"summary_large_image"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"twitter:title"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Open Graph для сайта"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"twitter:description"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Как настроить превью ссылок и не терять клики."</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"twitter:image"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/assets/posts/open-graph/og.webp"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Даже если платформа умеет использовать Open Graph как fallback, отдельные twitter-теги помогают сделать отображение более предсказуемым.</p><h2 id="mistakes">Частые ошибки Open Graph </h2><p>Ошибки Open Graph часто незаметны внутри сайта. Страница открывается, дизайн работает, форма отправляется, SEO-поля заполнены. Но стоит отправить ссылку в мессенджер — и проблема становится видна.</p><h3>Нет og:image</h3><p>Самая заметная ошибка — отсутствующая картинка.</p><p>Без изображения карточка ссылки становится менее заметной. В ленте или чате она проигрывает ссылкам с нормальными превью.</p><h3>Одинаковое превью на всех страницах</h3><p>Когда у всех страниц один и тот же <code>og:title</code>, <code>og:description</code> и <code>og:image</code>, пользователь не понимает разницы между услугами, статьями и кейсами.</p><p>Это особенно плохо для сайтов, где контент активно распространяется: блогов, экспертных сайтов, агентств, сервисов, SaaS, интернет-магазинов.</p><h3>Неправильный URL изображения</h3><p>Картинка может быть прописана, но не загружаться.</p><p>Причины:</p><ul><li>относительный URL вместо абсолютного;</li><li>файл удалён;</li><li>CDN отдаёт ошибку;</li><li>картинка закрыта от ботов;</li><li>сервер блокирует user-agent;</li><li>изображение слишком тяжёлое;</li><li>редирект настроен некорректно;</li><li>используется формат, который не поддерживается нужной платформой.</li></ul><p>Плохо:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:image"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"/images/og.webp"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Надёжнее:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:image"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/images/og.webp"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><h3>Устаревшее превью</h3><p>Вы обновили страницу, поменяли заголовок и картинку, но в Telegram или другой платформе всё ещё показывается старое превью.</p><p>Часто это не ошибка сайта, а кэш платформы. Но для бизнеса результат тот же: пользователи видят старые данные.</p><p>Что можно сделать:</p><ul><li>проверить страницу валидатором;</li><li>убедиться, что HTML действительно обновился;</li><li>поменять URL изображения;</li><li>добавить версию к файлу, если это допустимо;</li><li>дождаться обновления кэша платформы;</li><li>использовать debugger конкретной соцсети, если он доступен.</li></ul><h3>Заголовок не соответствует странице</h3><p>Иногда Open Graph копируют из шаблона и забывают адаптировать под конкретную страницу.</p><p>Например, страница про аудит индексации получает заголовок:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Разработка сайтов под ключ</span></span></code></pre><p>Формально тег есть. По смыслу — он вводит пользователя в заблуждение.</p><h3>Описание превращено в SEO-спам</h3><p><code>og:description</code> не должен выглядеть как набор ключевых слов.</p><p>Плохо:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>open graph, og теги, seo продвижение, сайт, заказать сайт, разработка сайта, продвижение сайта недорого</span></span></code></pre><p>Лучше:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Разберём, как ссылка на сайт выглядит в соцсетях и мессенджерах, какие OG-теги нужны и как проверить превью перед публикацией.</span></span></code></pre><h3>Нет согласованности с canonical</h3><p>Если <code>og:url</code> указывает на один адрес, canonical — на другой, а sitemap — на третий, сайт отправляет противоречивые сигналы.</p><p>Для пользователя это может быть незаметно. Для технического SEO — это симптом проблем в архитектуре URL.</p><h3>Превью не проверяют после релизов</h3><p>Open Graph часто ломается после:</p><ul><li>редизайна;</li><li>смены CMS;</li><li>миграции на Next.js;</li><li>внедрения headless CMS;</li><li>переезда на CDN;</li><li>изменения структуры URL;</li><li>автоматической генерации страниц;</li><li>добавления мультиязычности.</li></ul><p>Поэтому OG-теги стоит проверять не один раз при запуске сайта, а после значимых изменений.</p><aside class="post-disclaimer" role="note"><p>Если Open Graph регулярно ломается после релизов, проблема обычно не в одном теге. Нужно смотреть шаблоны страниц, генерацию metadata, CMS-поля, canonical, редиректы, CDN и процесс публикации контента.</p></aside></div><aside><h3>Не уверены, что ссылки на сайт выглядят правильно?</h3><p>Проверьте страницу в валидаторе: заголовок, описание, изображение, тип страницы, canonical URL, доступность картинки и типовые ошибки Open Graph-разметки.</p></aside><div><h2 id="validation">Как проверить Open Graph </h2><p>Проверку Open Graph лучше делать в несколько шагов. Не стоит ограничиваться тем, что теги «где-то есть» в коде. Важно увидеть итоговую карточку так, как её увидит пользователь.</p><h3>Шаг 1. Откройте исходный код страницы</h3><p>Проверьте, что в <code>&#x3C;head></code> есть нужные теги:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:title"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"..."</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:description"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"..."</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:type"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"..."</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:url"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"..."</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"og:image"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"..."</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Если теги появляются только после клиентского JavaScript, некоторые парсеры могут их не увидеть. Надёжнее, когда метаданные отдаются сразу в HTML.</p><h3>Шаг 2. Проверьте URL изображения</h3><p>Скопируйте значение <code>og:image</code> и откройте его в браузере.</p><p>Проверьте:</p><ul><li>открывается ли изображение без авторизации;</li><li>нет ли 404 или 403;</li><li>нет ли долгих редиректов;</li><li>корректный ли формат файла;</li><li>не слишком ли тяжёлое изображение;</li><li>соответствует ли картинка странице.</li></ul><h3>Шаг 3. Проверьте страницу валидатором</h3><p>Для быстрой технической проверки используйте валидатор Open Graph.</p><div><p><img src="https://baranov.guru/assets/tools/open-graph-validator/cover.webp" alt="Валидатор Open Graph"></p><p><a href="https://baranov.guru/tools/open-graph-validator/">Валидатор Open Graph</a> — Проверьте OG-теги страницы: title, description, image, url, type, размеры изображения, доступность и типовые ошибки предпросмотра ссылок.</p></div><p>Валидатор помогает быстро увидеть:</p><ul><li>какие теги найдены;</li><li>какие отсутствуют;</li><li>какой заголовок увидят платформы;</li><li>какое изображение используется;</li><li>доступна ли картинка;</li><li>нет ли проблем с URL;</li><li>достаточно ли данных для нормального превью.</li></ul><h3>Шаг 4. Проверьте нужные платформы вручную</h3><p>Даже если валидатор показывает, что всё корректно, проверьте ссылку там, где она реально будет использоваться.</p><p>Например:</p><ul><li>отправьте ссылку в Telegram;</li><li>вставьте в WhatsApp;</li><li>проверьте в Slack;</li><li>создайте тестовый пост в соцсети;</li><li>посмотрите на мобильном экране;</li><li>сравните с конкурентами.</li></ul><p>Важно смотреть не только на факт появления карточки, но и на качество:</p><ul><li>понятен ли заголовок;</li><li>не обрезается ли важный текст;</li><li>хорошо ли выглядит изображение;</li><li>соответствует ли превью странице;</li><li>не выглядит ли карточка как спам;</li><li>не показывает ли платформа старую версию.</li></ul><h3>Шаг 5. Добавьте проверку в процесс публикации</h3><p>Для сайта, который регулярно обновляется, Open Graph лучше включить в редакторский чек-лист.</p><p>Перед публикацией статьи, услуги, кейса или посадочной страницы проверьте:</p><ul><li>title;</li><li>meta description;</li><li>canonical;</li><li>og:title;</li><li>og:description;</li><li>og:image;</li><li>og:url;</li><li>Twitter Card;</li><li>JSON-LD;</li><li>отображение ссылки в основных каналах.</li></ul><p>Это занимает меньше времени, чем исправлять плохое превью после публикации, когда ссылка уже ушла в рассылку, рекламу или клиентский чат.</p><h2 id="service">Когда нужен технический SEO-аудит </h2><p>Если нужно просто создать Open Graph-теги для одной страницы, это можно сделать самостоятельно: заполнить заголовок, описание, картинку, URL и проверить результат валидатором.</p><p>Но если проблема повторяется на всём сайте, Open Graph почти всегда оказывается частью более широкой технической картины.</p><p>Повод смотреть глубже:</p><ul><li>у разных страниц одинаковые метаданные;</li><li>OG-теги пропадают после релизов;</li><li>CMS не даёт нормально управлять превью;</li><li>картинки недоступны парсерам;</li><li>canonical и <code>og:url</code> конфликтуют;</li><li>страницы генерируются динамически, но metadata не обновляется;</li><li>мультиязычные версии путают локали и URL;</li><li>старые превью не обновляются;</li><li>SEO-теги, Open Graph и JSON-LD противоречат друг другу;</li><li>после миграции на новый frontend карточки ссылок стали ломаться.</li></ul><p>В таких случаях полезен технический SEO-аудит. Он помогает проверить не только Open Graph, но и всю систему технических сигналов сайта:</p><ul><li>индексацию;</li><li>sitemap.xml;</li><li>robots.txt;</li><li>canonical;</li><li>редиректы;</li><li>дубли;</li><li>статусы ответа сервера;</li><li>внутреннюю перелинковку;</li><li>JavaScript-рендеринг;</li><li>metadata generation;</li><li>JSON-LD;</li><li>скорость и доступность страниц;</li><li>ошибки в Google Search Console и Яндекс Вебмастере.</li></ul><div><p><img src="https://baranov.guru/assets/services/technical-seo/cover.webp" alt="Техническая SEO-оптимизация"></p><p><a href="https://baranov.guru/services/technical-seo/">Техническая SEO-оптимизация</a> — Аудит индексации, sitemap.xml, robots.txt, canonical, дублей, редиректов, Open Graph, JSON-LD и технических ошибок сайта.</p></div><p>На современных frontend-проектах Open Graph часто ломается не потому, что кто-то забыл один тег, а потому что metadata зависит от данных CMS, маршрутов, server-side rendering, кеширования, CDN и шаблонов генерации страниц.</p><p>Если сайт сделан на Next.js, headless CMS или сложном JavaScript-стеке, важно не просто заполнить поля, а правильно встроить metadata в архитектуру приложения.</p><div><p><img src="https://baranov.guru/assets/services/frontend/cover.webp" alt="Frontend-разработка"></p><p><a href="https://baranov.guru/services/frontend/">Frontend-разработка</a> — Исправление технических проблем интерфейса, которые мешают скорости, доступности, индексации и корректному отображению ссылок.</p></div><h2>Короткий итог</h2><p>Open Graph нужен не для галочки.</p><p>Он помогает управлять тем, как ссылки на сайт выглядят в соцсетях, мессенджерах, рабочих чатах и других сервисах предпросмотра. Для пользователя это может быть первый контакт с брендом, статьёй, услугой или продуктом.</p><p>Хорошая Open Graph-разметка делает ссылку понятной до клика: показывает правильный заголовок, описание, изображение и URL. Плохая — отдаёт платформам случайные данные: логотип вместо обложки, текст из меню вместо описания, старую картинку вместо актуального предложения.</p><p>Open Graph не заменяет SEO, не гарантирует позиции и не решает проблемы контента. Но он усиливает распространение страниц и помогает не терять клики в каналах, где внешний вид ссылки действительно влияет на решение пользователя.</p><p>Минимум, который стоит проверить на каждой важной странице:</p><ul><li><code>og:title</code>;</li><li><code>og:description</code>;</li><li><code>og:type</code>;</li><li><code>og:url</code>;</li><li><code>og:image</code>;</li><li><code>og:image:alt</code>;</li><li>согласованность с canonical;</li><li>Twitter Card;</li><li>корректное отображение в основных каналах.</li></ul><p>Если сайт регулярно публикует статьи, услуги, кейсы, товары или инструменты, Open Graph стоит встроить в процесс публикации так же, как title, meta description, sitemap.xml, robots.txt и JSON-LD.</p><aside class="post-disclaimer" role="note"><p>Эта статья объясняет общий подход к Open Graph и техническому SEO. Для конкретного сайта выводы нужно делать после проверки HTML-кода страниц, доступности изображений, canonical, CMS-шаблонов, JavaScript-рендеринга, кеширования и отображения ссылок в реальных каналах распространения.</p></aside></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/open-graph/og.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Robots.txt для сайта: что это, зачем нужен и как не закрыть важные страницы от поиска]]></title>
            <link>https://baranov.guru/posts/robots-txt/</link>
            <guid>https://baranov.guru/posts/robots-txt</guid>
            <pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Понятное руководство по robots.txt для владельцев бизнеса: зачем нужен файл, что можно и нельзя закрывать от поисковиков, какие ошибки мешают индексации и когда нужен технический SEO-аудит.]]></description>
            <content:encoded><![CDATA[<div><h2 id="what">Что такое robots.txt </h2><p><strong>Robots.txt</strong> — это текстовый файл, который подсказывает поисковым роботам, какие разделы сайта можно обходить, а какие лучше не трогать.</p><p>Обычно он лежит в корне сайта:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>https://example.com/robots.txt</span></span></code></pre><p>Внутри файла находятся правила для роботов. Например:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>User-agent: *</span></span>
<span class="line"><span>Disallow: /admin/</span></span>
<span class="line"><span>Disallow: /cart/</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Sitemap: https://example.com/sitemap.xml</span></span></code></pre><p>Если объяснять без технического жаргона, robots.txt — это инструкция для поисковых систем:</p><ul><li>сюда можно заходить;</li><li>сюда лучше не заходить;</li><li>вот где лежит карта сайта;</li><li>для разных роботов могут действовать разные правила.</li></ul><p>Но важно сразу снять главное заблуждение.</p><blockquote><p>Robots.txt управляет <strong>обходом сайта</strong>, а не гарантированной индексацией или удалением страниц из поиска.</p></blockquote><p>Если закрыть страницу через <code>Disallow</code>, поисковик может не скачать её содержимое. Но сам URL иногда всё равно может появиться в поиске, если на него есть ссылки с других страниц. Поэтому robots.txt нельзя воспринимать как инструмент приватности или как универсальный способ «убрать страницу из Google».</p></div><div><p>Robots.txt — это не замок на двери. Это табличка для робота: <strong>«сюда не ходи»</strong>. Добросовестные поисковики её учитывают, но файл не защищает приватные данные и не гарантирует удаление URL из поиска.</p></div><div><h2 id="business">Зачем robots.txt бизнесу </h2><p>Для бизнеса robots.txt важен не как технический файл сам по себе. Важен результат: <strong>поисковые системы должны обходить нужные страницы и не тратить ресурсы на технический мусор</strong>.</p><p>Если правила настроены неправильно, сайт может терять органический трафик по очень приземлённым причинам:</p><ul><li>поисковик не видит важные посадочные страницы;</li><li>CSS или JavaScript закрыты от обхода, и страница плохо рендерится;</li><li>робот тратит обход на фильтры, параметры, корзину, поиск по сайту и технические URL;</li><li>после редизайна старые правила случайно закрывают новые разделы;</li><li>sitemap.xml показывает страницы, а robots.txt запрещает их обходить;</li><li>команда думает, что страница «скрыта», хотя URL всё ещё может появляться в поиске.</li></ul><p>На практике я часто вижу проекты, где robots.txt когда-то настроили «на всякий случай», а потом годами не возвращались к файлу. За это время меняется CMS, появляются новые разделы, запускается блог, меняется структура URL — а старые правила продолжают влиять на обход сайта.</p><p>Именно поэтому robots.txt полезно рассматривать не отдельно, а как часть технического SEO: вместе с sitemap.xml, canonical, noindex, редиректами, статусами ответа сервера и JavaScript-рендерингом.</p></div><section><h2>Что даёт правильно настроенный robots.txt</h2><p>Robots.txt помогает не «сделать SEO», а управлять обходом сайта: какие разделы поисковики могут посещать, где не стоит тратить crawl budget и какие технические зоны лучше не показывать роботам.</p><div><article><h3>Контроль обхода</h3><p>Можно ограничить обход технических разделов, служебных URL, корзины, внутреннего поиска и параметров.</p></article><article><h3>Меньше технического мусора</h3><p>Поисковый робот меньше отвлекается на страницы, которые не должны участвовать в поиске.</p></article><article><h3>Безопаснее релизы</h3><p>После редизайна, миграции или смены CMS проще проверить, не закрыты ли важные страницы.</p></article><article><h3>Связка с sitemap.xml</h3><p>В robots.txt можно указать путь к sitemap.xml, чтобы поисковики быстрее находили карту сайта.</p></article><article><h3>Понятнее техническая архитектура</h3><p>Файл показывает, какие зоны сайта команда считает открытыми или закрытыми для обхода.</p></article><article><h3>Быстрее находятся ошибки</h3><p>Конфликты robots.txt с sitemap, noindex и canonical часто сразу показывают проблемы технического SEO.</p></article></div></section><div><h2 id="noindex">Чем robots.txt отличается от noindex </h2><p>Одна из самых дорогих ошибок — путать robots.txt и <code>noindex</code>.</p><p>Они решают разные задачи.</p><p><strong>Robots.txt</strong> отвечает на вопрос:</p><blockquote><p>Можно ли роботу заходить по этому URL и скачивать содержимое?</p></blockquote><p><strong>Noindex</strong> отвечает на другой вопрос:</p><blockquote><p>Можно ли показывать эту страницу в поисковой выдаче?</p></blockquote><p>Из-за этого возникает важный нюанс: если вы закрыли страницу в robots.txt, поисковик может не увидеть <code>noindex</code> внутри HTML. То есть вы как будто говорите: «не заходи на страницу», а потом внутри страницы оставляете инструкцию, которую робот уже не может прочитать.</p><p>Практический вывод простой:</p><ul><li>если нужно ограничить обход технической зоны — используйте robots.txt;</li><li>если нужно убрать страницу из индекса — используйте <code>noindex</code>, удаление страницы, пароль или другие методы;</li><li>если страница уже есть в индексе, не закрывайте её в robots.txt до того, как поисковик увидит <code>noindex</code>.</li></ul><aside class="post-disclaimer" role="note"><p>Robots.txt не подходит для защиты приватных данных. Файл публичен: любой человек может открыть <code>/robots.txt</code> и увидеть, какие разделы вы указали. Для приватных страниц нужна авторизация или ограничения доступа на сервере.</p></aside><h2 id="syntax">Как устроен robots.txt </h2><p>Базовый robots.txt состоит из групп правил.</p><p>Пример:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>User-agent: *</span></span>
<span class="line"><span>Disallow: /admin/</span></span>
<span class="line"><span>Disallow: /cart/</span></span>
<span class="line"><span>Allow: /assets/</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Sitemap: https://example.com/sitemap.xml</span></span></code></pre><p>Главные директивы:</p><ul><li><code>User-agent</code> — к какому роботу относятся правила;</li><li><code>Disallow</code> — что нельзя обходить;</li><li><code>Allow</code> — что можно обходить, даже если родительская директория закрыта;</li><li><code>Sitemap</code> — где лежит sitemap.xml.</li></ul><h3>User-agent</h3><p><code>User-agent</code> указывает, для какого робота действует блок правил.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>User-agent: *</span></span>
<span class="line"><span>Disallow: /admin/</span></span></code></pre><p>Звёздочка означает «для всех роботов».</p><p>Можно задавать отдельные правила для конкретных роботов:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>User-agent: Googlebot</span></span>
<span class="line"><span>Disallow: /test/</span></span>
<span class="line"><span></span></span>
<span class="line"><span>User-agent: *</span></span>
<span class="line"><span>Disallow: /admin/</span></span></code></pre><p>Для бизнеса здесь важно не пытаться писать слишком сложные правила без необходимости. Чем сложнее robots.txt, тем выше риск случайно закрыть важные страницы.</p><h3>Disallow</h3><p><code>Disallow</code> запрещает обход указанного пути.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Disallow: /admin/</span></span></code></pre><p>Это не значит, что раздел защищён. Это значит, что добросовестный робот должен не обходить URL, начинающиеся с <code>/admin/</code>.</p><p>Самая опасная строка:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Disallow: /</span></span></code></pre><p>Она закрывает от обхода весь сайт для указанного <code>User-agent</code>.</p><p>Иногда такую строку используют на тестовом окружении, а потом случайно переносят в продакшен. После этого бизнес видит падение органического трафика, а причина оказывается в одном символе.</p><h3>Allow</h3><p><code>Allow</code> помогает сделать исключение внутри закрытого раздела.</p><p>Например:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>User-agent: *</span></span>
<span class="line"><span>Disallow: /catalog/</span></span>
<span class="line"><span>Allow: /catalog/public/</span></span></code></pre><p>Такой подход нужен редко, но бывает полезен на сложных сайтах. Главное — не превращать robots.txt в лабиринт исключений, который никто в команде не понимает.</p><h3>Sitemap</h3><p>В robots.txt стоит указывать путь к sitemap.xml:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Sitemap: https://example.com/sitemap.xml</span></span></code></pre><p>Если карт сайта несколько, можно указать несколько строк:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Sitemap: https://example.com/sitemap-pages.xml</span></span>
<span class="line"><span>Sitemap: https://example.com/sitemap-posts.xml</span></span>
<span class="line"><span>Sitemap: https://example.com/sitemap-products.xml</span></span></code></pre><p>Robots.txt и sitemap.xml работают в связке: один файл ограничивает обход, другой показывает важные URL. Если они противоречат друг другу, поисковики получают смешанные сигналы.</p><div><p><img src="https://baranov.guru/assets/posts/sitemap-xml/cover.webp" alt="Sitemap.xml для сайта"></p><p><a href="https://baranov.guru/posts/sitemap-xml/">Sitemap.xml для сайта</a> — Разбираемся, зачем бизнесу sitemap.xml, какие URL включать в карту сайта и как не создавать новые ошибки индексации.</p></div><h2 id="rules">Что можно и нельзя закрывать в robots.txt </h2><p>Хороший robots.txt не должен закрывать всё подряд. Его задача — аккуратно ограничивать обход там, где это действительно нужно.</p><p>Обычно можно закрывать:</p><ul><li>админские разделы;</li><li>служебные URL;</li><li>корзину;</li><li>оформление заказа;</li><li>внутренний поиск;</li><li>временные тестовые разделы;</li><li>технические параметры;</li><li>некоторые фильтры и сортировки;</li><li>страницы, которые не должны тратить crawl budget.</li></ul><p>Но нельзя бездумно закрывать:</p><ul><li>главную страницу;</li><li>страницы услуг;</li><li>статьи блога;</li><li>категории;</li><li>карточки товаров;</li><li>CSS и JavaScript, которые нужны для рендера;</li><li>изображения и файлы, важные для отображения страницы;</li><li>URL из sitemap.xml;</li><li>страницы, на которых поисковик должен увидеть canonical или noindex.</li></ul><p>Особенно аккуратно нужно относиться к современным frontend-сайтам. Если поисковик не может получить CSS, JS или данные, нужные для нормального рендера, он может хуже понять страницу.</p><p>На проектах с React, Next.js, SPA, headless CMS и сложным lazy loading проблема часто оказывается не только в robots.txt, но и в реализации frontend-части. В таких случаях техническое SEO пересекается с разработкой: нужно смотреть, как страница отдаётся поисковику, какие ресурсы доступны и что видно без пользовательских действий.</p></div><aside><h3>Не уверены, что robots.txt не закрывает важные страницы?</h3><p>Проверим robots.txt, sitemap.xml, noindex, canonical, редиректы, доступность ресурсов и то, как поисковики реально видят ваш сайт.</p></aside><div><h2 id="mistakes">Частые ошибки в robots.txt </h2><p>Ошибки в robots.txt часто выглядят незаметно. Файл открывается, синтаксис кажется нормальным, сайт работает для пользователей — но поисковый робот получает неправильные инструкции.</p><h3>Закрыли весь сайт</h3><p>Самая опасная ошибка:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>User-agent: *</span></span>
<span class="line"><span>Disallow: /</span></span></code></pre><p>Для тестового окружения это может быть нормально. Для продакшена — катастрофа.</p><p>После релиза такая строка может привести к тому, что поисковики перестанут обходить сайт. Иногда проблему замечают только после падения органического трафика.</p><h3>Закрыли важные посадочные страницы</h3><p>Например:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Disallow: /services/</span></span></code></pre><p>Если в этом разделе находятся коммерческие страницы услуг, сайт сам запрещает роботам их обходить.</p><p>Для бизнеса это особенно болезненно: страницы есть, тексты написаны, дизайн сделан, но поисковик не может нормально их обработать.</p><h3>Закрыли CSS и JavaScript</h3><p>Иногда в robots.txt закрывают технические папки слишком широким правилом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Disallow: /assets/</span></span>
<span class="line"><span>Disallow: /static/</span></span></code></pre><p>Если там лежат CSS, JavaScript или изображения, нужные для рендера, поисковик может хуже понять страницу.</p><p>Для современных сайтов это критично: поисковик должен видеть не только HTML, но и ресурсы, от которых зависит отображение контента.</p><h3>Перепутали robots.txt и noindex</h3><p>Например, команда хочет убрать страницу из поиска и делает так:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Disallow: /old-page/</span></span></code></pre><p>Но если URL уже известен поисковику и на него есть ссылки, он может продолжать появляться в результатах без нормального сниппета.</p><p>Правильнее сначала дать поисковику увидеть <code>noindex</code> или использовать другие методы удаления из индекса, а не просто закрывать обход.</p><h3>Robots.txt конфликтует с sitemap.xml</h3><p>Одна из частых проблем:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Disallow: /blog/</span></span>
<span class="line"><span>Sitemap: https://example.com/sitemap.xml</span></span></code></pre><p>А в sitemap.xml при этом лежат статьи:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>https://example.com/blog/article-1/</span></span>
<span class="line"><span>https://example.com/blog/article-2/</span></span></code></pre><p>Получается противоречие: sitemap говорит «вот важные страницы», а robots.txt говорит «не обходи этот раздел».</p><h3>Закрыли приватные разделы только через robots.txt</h3><p>Например:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Disallow: /private/</span></span>
<span class="line"><span>Disallow: /clients/</span></span>
<span class="line"><span>Disallow: /backup/</span></span></code></pre><p>Это плохая идея, если там действительно приватные данные. Robots.txt публичен, и такой файл может даже подсказать любопытному человеку, где искать закрытые разделы.</p><p>Для приватных данных нужны авторизация, ограничения доступа, правильные статусы ответа и безопасность на уровне сервера.</p><h3>Слишком сложные правила</h3><p>Иногда robots.txt превращается в длинный список исключений, который никто не понимает:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Disallow: /*?sort=</span></span>
<span class="line"><span>Disallow: /*?filter=</span></span>
<span class="line"><span>Allow: /*?filter=brand</span></span>
<span class="line"><span>Disallow: /catalog/*/reviews/</span></span>
<span class="line"><span>Allow: /catalog/*/reviews/best/</span></span></code></pre><p>Такие правила могут быть оправданы на больших проектах, но их нужно документировать и регулярно проверять. Иначе через несколько релизов команда уже не понимает, что реально открыто, а что закрыто.</p></div><div><p>Главный вопрос не в том, «есть ли robots.txt». Главный вопрос — <strong>не мешает ли он поисковикам видеть страницы, которые приносят бизнесу заявки, продажи и органический трафик</strong>.</p></div><div><h2 id="validation">Как проверить robots.txt </h2><p>Проверку robots.txt лучше делать в два этапа.</p><p>Сначала проверьте сам файл:</p><ul><li>открывается ли <code>/robots.txt</code>;</li><li>отдаёт ли сервер статус <code>200 OK</code>;</li><li>нет ли синтаксических ошибок;</li><li>нет ли случайного <code>Disallow: /</code>;</li><li>корректно ли указаны <code>User-agent</code>;</li><li>есть ли ссылка на sitemap.xml;</li><li>нет ли устаревших правил после редизайна или миграции.</li></ul><p>Для быстрой проверки можно использовать валидатор:</p><div><p><img src="https://baranov.guru/assets/tools/robots-txt-validator/cover.webp" alt="Валидатор robots.txt"></p><p><a href="https://baranov.guru/tools/robots-txt-validator/">Валидатор robots.txt</a> — Проверьте robots.txt онлайн: User-agent, Allow, Disallow, Sitemap, типовые ошибки и потенциально опасные правила.</p></div><p>После этого проверьте смысл правил на реальных URL:</p><ul><li>главная страница;</li><li>страницы услуг;</li><li>статьи;</li><li>категории;</li><li>карточки товаров;</li><li>sitemap.xml;</li><li>CSS и JavaScript;</li><li>изображения;</li><li>технические разделы;</li><li>страницы, которые должны быть закрыты.</li></ul><p>Если нужно быстро собрать базовый файл, можно использовать генератор:</p><div><p><img src="https://baranov.guru/assets/tools/robots-txt-generator/cover.webp" alt="Генератор robots.txt"></p><p><a href="https://baranov.guru/tools/robots-txt-generator/">Генератор robots.txt</a> — Соберите robots.txt для сайта: правила User-agent, Allow, Disallow и Sitemap без ручного набора с нуля.</p></div><p>Но генератор не заменяет SEO-логику. Он помогает создать корректный файл, а дальше важно понять, какие именно разделы сайта нужно открыть или закрыть.</p></div><aside><h3>Хотите понять, как поисковики реально видят ваш сайт?</h3><p>Проверим robots.txt не отдельно, а вместе с sitemap.xml, canonical, noindex, редиректами, статусами ответа, внутренней перелинковкой и frontend-рендерингом.</p></aside><div><h2 id="service">Когда нужен технический SEO-аудит </h2><p>Если у вас небольшой сайт и нужно просто создать базовый robots.txt, можно начать самостоятельно: собрать файл в генераторе, проверить валидатором и убедиться, что важные страницы открыты для обхода.</p><p>Но если на сайте уже есть органический трафик, блог, каталог, фильтры, редизайн, миграция или проблемы с индексацией, robots.txt лучше проверять в контексте всего технического SEO.</p><p>Повод подключать специалиста:</p><ul><li>важные страницы не появляются в поиске;</li><li>после релиза или редизайна упал органический трафик;</li><li>в индексе появились дубли и мусорные URL;</li><li>Search Console показывает проблемы обхода или индексации;</li><li>sitemap.xml и robots.txt противоречат друг другу;</li><li>страницы закрыты через robots.txt, но всё равно видны в поиске;</li><li>сайт на современном frontend-стеке, и нужно проверить рендеринг для поисковиков;</li><li>команда не уверена, какие правила в robots.txt безопасно менять.</li></ul><p>В техническом SEO robots.txt — это не изолированный файл, а часть системы. Его нужно смотреть вместе с:</p><ul><li>sitemap.xml;</li><li>canonical;</li><li>noindex;</li><li>редиректами;</li><li>статусами ответа сервера;</li><li>внутренней перелинковкой;</li><li>URL-структурой;</li><li>JavaScript-рендерингом;</li><li>доступностью CSS, JS и изображений;</li><li>поведением сайта после релизов.</li></ul><p>Если проблема упирается в реализацию, её нужно не только найти, но и исправить в коде. Поэтому для многих проектов техническое SEO естественно переходит во frontend-разработку: нужно открыть нужные ресурсы, исправить рендеринг, убрать технические дубли и сделать так, чтобы поисковик видел страницу так же понятно, как пользователь.</p><div><p><img src="https://baranov.guru/assets/services/frontend/cover.webp" alt="Frontend-разработка"></p><p><a href="https://baranov.guru/services/frontend/">Frontend-разработка</a> — Помогаю исправлять технические проблемы на стороне интерфейса: рендеринг, производительность, доступность контента и SEO-ограничения frontend-приложений.</p></div><p>Хороший robots.txt — это не файл «для галочки». Это способ аккуратно управлять обходом сайта и не мешать поисковикам находить то, что приносит бизнесу заявки, продажи и доверие.</p><p>Если вы не уверены, правильно ли настроены robots.txt, sitemap.xml и индексация, лучше начать с аудита. Он покажет, где проблема: в правилах обхода, архитектуре сайта, CMS, frontend-реализации или общей SEO-логике.</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/robots-txt/og.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Sitemap.xml для сайта: что это, зачем нужен и как не допустить ошибок]]></title>
            <link>https://baranov.guru/posts/sitemap-xml/</link>
            <guid>https://baranov.guru/posts/sitemap-xml</guid>
            <pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Понятное руководство по sitemap.xml для владельцев бизнеса: зачем нужна карта сайта, как она влияет на индексацию, какие URL в нее включать, как найти ошибки и когда обращаться за техническим SEO.]]></description>
            <content:encoded><![CDATA[<div><h2 id="what">Что такое sitemap.xml </h2><p><strong>Sitemap.xml</strong> — это технический файл со списком страниц сайта, который помогает поисковым системам понимать структуру проекта.</p><p>Если объяснять без технического жаргона, sitemap.xml — это карта, которую сайт передаёт поисковику:</p><ul><li>вот какие страницы у нас есть;</li><li>вот какие из них мы считаем важными;</li><li>вот когда они существенно обновлялись;</li><li>вот дополнительные данные по изображениям, видео, новостям или языковым версиям, если они есть.</li></ul><p>Google описывает sitemap как файл с информацией о страницах, видео и других файлах сайта. Яндекс тоже рассматривает Sitemap как способ сообщить поисковому роботу текущую структуру сайта.</p><p>Но здесь важно сразу снять главное заблуждение.</p><blockquote><p>Sitemap.xml не гарантирует индексацию и сам по себе не поднимает позиции. Он помогает поисковикам быстрее и точнее находить страницы, но не заменяет нормальную архитектуру сайта, полезный контент, внутренние ссылки, canonical, robots.txt и техническое SEO.</p></blockquote><p>То есть sitemap — это не «волшебная кнопка роста трафика», а часть технической инфраструктуры сайта.</p><p>Хорошая карта сайта помогает поисковику обходить сайт эффективнее. Плохая — наоборот, может отправлять робота в дубли, устаревшие страницы, редиректы, ошибки и служебные URL.</p></div><div><p>Иногда проблема сайта не в контенте и не в рекламе. Поисковики просто <strong>не видят важные страницы</strong> — или тратят обход на технический мусор вместо коммерческих URL.</p><p>Sitemap.xml помогает быстрее заметить эту проблему, но сам по себе <strong>не гарантирует индексацию</strong> и не поднимает позиции.</p></div><div><h2 id="business">Зачем sitemap.xml бизнесу </h2><p>Для владельца бизнеса sitemap.xml важен не сам по себе. Важен результат: <strong>поисковики должны видеть нужные страницы сайта</strong>.</p><p>Если Google или Яндекс не находят ваши посадочные страницы, карточки товаров, статьи или страницы услуг, они не смогут приводить из поиска клиентов. Можно вложиться в дизайн, тексты, рекламу, разработку — но если важные URL не попадают в индекс, органический трафик будет ограничен.</p><p>В этом смысле sitemap.xml — не «файл для разработчика», а один из инструментов контроля над тем, как сайт представлен в поиске. Он помогает понять, какие страницы вы отдаёте поисковым системам как важные, насколько быстро новые материалы попадают в обход и не мешает ли технический мусор нормальной индексации.</p><p>Особенно хорошо это видно в моменты изменений: после редизайна, переезда на новую CMS, запуска блога, добавления новых услуг, расширения каталога или массового обновления контента. В такие моменты sitemap становится не просто списком URL, а способом проверить, не потерялись ли важные страницы и не появились ли в структуре дубли, редиректы или устаревшие адреса.</p><p>Часто владелец бизнеса замечает не саму проблему sitemap.xml, а её последствия:</p><ul><li>новые страницы неделями не появляются в Google;</li><li>статьи не получают органический трафик;</li><li>после редизайна проседают позиции и переходы из поиска;</li><li>в выдаче появляются странные URL с параметрами;</li><li>индексируются фильтры, служебные страницы и дубли;</li><li>важные посадочные страницы отсутствуют в поиске;</li><li>Search Console показывает статусы вроде «Обнаружено, но не проиндексировано».</li></ul><p>Во многих таких случаях sitemap.xml — не единственная причина проблемы, а место, где её можно быстро увидеть.</p><p>На практике sitemap.xml помогает ответить на несколько бизнес-вопросов:</p><ul><li>видит ли поисковик важные страницы;</li><li>быстро ли он узнаёт о новых материалах;</li><li>не тратит ли он обход на мусорные URL;</li><li>нет ли в индексе дублей и технических страниц;</li><li>не потерялись ли страницы после редизайна или миграции.</li></ul><p>Именно поэтому грамотный sitemap полезен не как отдельный XML-файл, а как часть системы технического SEO. Ниже — основные эффекты, которые бизнес получает от правильно настроенной карты сайта.</p></div><section><h2>Что даёт грамотный sitemap.xml</h2><p>Карта сайта помогает не «сделать SEO», а контролировать индексацию: какие страницы поисковики видят, какие обходят и где возникают технические ошибки.</p><div><article><h3>Быстрее находятся новые страницы</h3><p>Поисковики быстрее узнают о новых статьях, услугах, товарах и посадочных страницах.</p></article><article><h3>Понятнее структура сайта</h3><p>Sitemap показывает, какие URL сайт считает важными и какие страницы должны участвовать в поиске.</p></article><article><h3>Проще найти технические ошибки</h3><p>404, редиректы, noindex-страницы и дубли в sitemap быстро показывают проблемы в архитектуре.</p></article><article><h3>Безопаснее проходят миграции</h3><p>После редизайна или смены CMS проще проверить, что важные URL не потерялись.</p></article><article><h3>Меньше тратится crawl budget</h3><p>На больших сайтах поисковый робот меньше отвлекается на мусорные и технические страницы.</p></article><article><h3>Быстрее видны проблемы индексации</h3><p>Google Search Console и Яндекс Вебмастер показывают, как поисковики обрабатывают отправленные карты сайта.</p></article></div></section><div><h2 id="when">Когда карта сайта особенно важна </h2><p>Небольшой сайт на 10–20 страниц, где каждая страница доступна из меню, поисковики обычно могут обойти и без сложной sitemap-логики. Но в реальных проектах всё быстро становится сложнее.</p><p>На практике я часто вижу проекты, где карта сайта существует формально, но в ней годами живут старые URL, редиректы, дубли и страницы, которые уже давно не должны участвовать в поиске. Особенно часто это проявляется после редизайнов, миграций и смены CMS.</p><p>Sitemap.xml особенно важен, если у вас:</p><ul><li><a href="">интернет-магазин</a></li><li><a href="">блог</a></li><li><a href="">медиа</a></li><li><a href="">SaaS</a></li><li><a href="">много услуг</a></li><li><a href="">мультиязычный сайт</a></li><li><a href="">частые релизы</a></li><li><a href="">редизайн или миграция</a></li></ul><h3>Большой сайт</h3><p>Чем больше URL, тем выше риск, что поисковик не доберётся до всех важных страниц через обычные ссылки.</p><p>На больших проектах sitemap помогает разделить структуру: отдельно статьи, отдельно категории, отдельно товары, отдельно изображения или языковые версии.</p><h3>Новый сайт</h3><p>Если сайт новый и на него ещё мало внешних ссылок, поисковикам сложнее находить страницы. Sitemap помогает быстрее показать структуру проекта.</p><h3>Сайт с большим количеством медиа</h3><p>Если на страницах важны изображения или видео, sitemap может передавать дополнительную информацию о медиафайлах.</p><p>Это особенно полезно, когда изображения загружаются через JavaScript, lazy loading или нестандартные компоненты интерфейса.</p><h3>Сайт после редизайна или миграции</h3><p>После переезда на новую CMS, изменения структуры URL или редизайна sitemap помогает быстро проверить, что:</p><ul><li>старые важные страницы не пропали;</li><li>новые URL появились;</li><li>редиректы настроены корректно;</li><li>в карту не попали технические страницы;</li><li>поисковики видят актуальную структуру.</li></ul></div><div><p>Главный вопрос не в том, «есть ли sitemap.xml». Главный вопрос — <strong>помогает ли он поисковику видеть правильные страницы или отправляет его в технический мусор</strong>.</p></div><div><h2 id="include">Какие URL включать в sitemap.xml </h2><p>Самый важный принцип: <strong>в sitemap.xml должны попадать только те страницы, которые вы действительно хотите видеть в поиске</strong>.</p><p>Не нужно выгружать туда всё, что умеет создать CMS.</p><p>В sitemap стоит включать:</p><ul><li>главную страницу;</li><li>страницы услуг;</li><li>коммерческие посадочные страницы;</li><li>статьи блога;</li><li>категории;</li><li>карточки товаров;</li><li>важные справочные страницы;</li><li>канонические версии страниц.</li></ul><aside class="post-disclaimer" role="note"><p>Если одна и та же страница доступна по нескольким адресам, в sitemap.xml должен быть только канонический URL — тот, который вы хотите видеть в поиске.</p></aside><p>Например, если товар доступен по адресам:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>/site/product/</span></span>
<span class="line"><span>/site/product/?color=black</span></span>
<span class="line"><span>/site/product/?utm_source=email</span></span></code></pre><p>в sitemap должен попасть только основной URL:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>/site/product/</span></span></code></pre><p>Иначе поисковик получает противоречивый сигнал: сайт как будто сам предлагает индексировать дубли.</p><p>Не стоит включать в sitemap:</p><ul><li>страницы с <code>noindex</code>;</li><li>дубли;</li><li>URL с параметрами фильтров;</li><li>внутренний поиск;</li><li>страницы авторизации;</li><li>корзину и оформление заказа;</li><li>технические страницы;</li><li>404;</li><li>редиректы;</li><li>устаревшие URL;</li><li>страницы с canonical на другой адрес.</li></ul><h2 id="place">Где разместить sitemap.xml и как сообщить о нём поисковикам </h2><p>Обычно sitemap.xml размещают в корне сайта:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>https://example.com/sitemap.xml</span></span></code></pre><p>Это самый понятный и предсказуемый вариант для команды, разработчиков и поисковых систем.</p><p>Также ссылку на sitemap стоит добавить в <code>robots.txt</code>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>Sitemap: https://example.com/sitemap.xml</span></span></code></pre><p>После этого карту сайта нужно отправить в панели вебмастеров:</p><ul><li><a href="https://search.google.com/search-console/sitemaps">Google Search Console</a>;</li><li><a href="https://webmaster.yandex.ru/tools/sitemap/">Яндекс Вебмастер</a>.</li></ul><p>Так вы не просто создаёте файл, а получаете обратную связь: удалось ли поисковику его обработать, есть ли ошибки и сколько URL было обнаружено.</p><h2 id="tags">Что важно в тегах sitemap.xml </h2><p>Базовая структура sitemap довольно простая.</p><p>Минимальный пример:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-xml"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;?</span><span style="color:#22863A;--shiki-dark:#FFAB70">xml</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> version</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"1.0"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> encoding</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"UTF-8"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">?></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> xmlns</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"http://www.sitemaps.org/schemas/sitemap/0.9"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://example.com/services/technical-seo/&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>2026-05-25&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><p>Главные элементы:</p><ul><li><code>urlset</code> — контейнер списка URL;</li><li><code>url</code> — отдельная запись страницы;</li><li><code>loc</code> — адрес страницы;</li><li><code>lastmod</code> — дата значимого обновления страницы.</li></ul><h3>lastmod</h3><p><code>lastmod</code> полезен, только если он честный.</p><p>Если сайт каждый день автоматически ставит сегодняшнюю дату на все страницы, хотя контент не менялся, поисковик быстро перестаёт доверять такому сигналу.</p><p>Хороший <code>lastmod</code> должен отражать реальное изменение:</p><ul><li>обновили текст;</li><li>добавили важный блок;</li><li>изменили структуру страницы;</li><li>обновили данные;</li><li>существенно переработали контент.</li></ul><p>Плохой <code>lastmod</code>:</p><ul><li>меняется каждый день без причины;</li><li>одинаковый на всех страницах;</li><li>не соответствует фактическому обновлению;</li><li>проставляется просто при пересборке сайта.</li></ul><h3>changefreq и priority</h3><p>Поля <code>changefreq</code> и <code>priority</code> часто встречаются в sitemap, но их важность переоценивают.</p><p>Для бизнеса полезно запомнить простое правило: не нужно тратить много времени на искусственную расстановку <code>priority=1.0</code> на всех страницах.</p><p>Гораздо важнее:</p><ul><li>правильные URL;</li><li>отсутствие дублей;</li><li>корректный canonical;</li><li>честный <code>lastmod</code>;</li><li>доступность страниц для поискового робота.</li></ul><h3>sitemap index</h3><p>Если сайт большой, один sitemap разбивают на несколько файлов и объединяют через sitemap index.</p><p>Пример:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-xml"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;?</span><span style="color:#22863A;--shiki-dark:#FFAB70">xml</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> version</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"1.0"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> encoding</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"UTF-8"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">?></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">sitemapindex</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> xmlns</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"http://www.sitemaps.org/schemas/sitemap/0.9"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://example.com/sitemap-posts.xml&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>2026-05-25&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://example.com/sitemap-products.xml&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>2026-05-25&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">sitemapindex</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><p>Такой подход удобен для:</p><ul><li>блогов с большим архивом;</li><li>интернет-магазинов;</li><li>сайтов услуг с большим количеством посадочных страниц;</li><li>мультиязычных проектов;</li><li>сайтов с image/video/news sitemap.</li></ul><div><p><img src="https://baranov.guru/assets/tools/sitemap-generator/cover.webp" alt="Генератор sitemap.xml"></p><p><a href="https://baranov.guru/tools/sitemap-generator/">Генератор sitemap.xml</a> — Создайте sitemap.xml для сайта: обычный urlset, sitemap index, image, video и news sitemap.</p></div><h2 id="not-magic">Почему sitemap.xml сам по себе не улучшает SEO </h2><p>Одна из частых ошибок — воспринимать sitemap.xml как инструмент роста позиций.</p><p>На практике поисковик может:</p><ul><li>знать о странице из sitemap;</li><li>но не индексировать её;</li><li>индексировать, но не показывать по нужным запросам;</li><li>показывать, но ниже конкурентов.</li></ul><p>Потому что sitemap — это только сигнал о существовании URL. Он не делает страницу полезной, не исправляет архитектуру, не ускоряет загрузку, не выбирает canonical и не решает проблемы с дублями.</p><p>На ранжирование влияют:</p><ul><li>качество страницы;</li><li>структура сайта;</li><li>внутренняя перелинковка;</li><li>техническая доступность;</li><li>скорость загрузки;</li><li>корректный рендеринг;</li><li>контент;</li><li>ссылки;</li><li>поведенческие сигналы;</li><li>соответствие страницы поисковому интенту.</li></ul><p>Поэтому даже идеально собранный sitemap.xml не заменяет техническое SEO. Его задача — помочь поисковикам понять, какие URL на сайте есть и какие из них стоит обходить. А задача технического SEO — сделать так, чтобы эти URL были доступны, полезны, не конфликтовали между собой и действительно могли участвовать в поиске.</p><h2 id="mistakes">Частые ошибки в sitemap.xml </h2><p>Ошибки в sitemap редко выглядят драматично. Файл может открываться, XML может быть валидным, но при этом карта сайта будет вредить индексации.</p><p>Большинство проблем связано не с XML-синтаксисом, а с тем, какие URL сайт отдаёт поисковику.</p><p>Чаще всего в sitemap.xml встречаются такие ошибки:</p><ul><li><strong>404-страницы</strong> — поисковый робот тратит обход на несуществующие URL.</li><li><strong>Редиректы</strong> — карта сайта указывает не на конечные канонические страницы, а на промежуточные адреса.</li><li><strong>Noindex-страницы</strong> — сайт одновременно просит страницу индексировать и не индексировать.</li><li><strong>Дубли</strong> — фильтры, параметры и альтернативные версии страниц размывают структуру сайта.</li><li><strong>Фальшивый <code>lastmod</code></strong> — дата обновления меняется автоматически, хотя страница реально не обновлялась.</li><li><strong>Устаревший файл</strong> — новые страницы не попадают в sitemap, а старые удалённые URL остаются.</li></ul><p>Например, после редизайна сайт может продолжать отдавать в sitemap старые URL:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>/services/seo-old/</span></span></code></pre><p>которые делают 301-редирект на актуальную страницу:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>/services/technical-seo/</span></span></code></pre><p>Для пользователя это почти незаметно: страница открывается. Но для поискового робота карта сайта указывает не на конечный канонический URL, а на промежуточный адрес. На больших сайтах таких «мелочей» могут быть сотни или тысячи.</p><aside class="post-disclaimer" role="note"><p>Если в sitemap много технического мусора, проблема обычно не в самом XML-файле, а в архитектуре сайта, логике генерации URL или настройках CMS.</p></aside><p>Если такие ошибки повторяются после каждого релиза, проблема уже не в одном файле. Значит, нужно смотреть генерацию страниц, маршруты, canonical, robots.txt, статусы ответа сервера и процесс публикации контента целиком.</p></div><aside><h3>Не уверены, что поисковики видят важные страницы вашего сайта?</h3><p>Проверим, почему поисковики не видят важные страницы, куда уходит crawl budget, почему появляются дубли и какие технические ограничения мешают росту органического трафика.</p></aside><div><h2 id="validation">Как проверить и поддерживать sitemap.xml </h2><p>Проверку sitemap лучше делать в два этапа.</p><p>Сначала проверьте сам файл: открывается ли он, корректно ли устроен XML, нет ли ошибок в структуре <code>urlset</code> или <code>sitemapindex</code>, валидны ли URL и правильно ли заполнен <code>lastmod</code>.</p><p>После этого переходите к SEO-проверке. Здесь важно смотреть не только на формат файла, но и на смысл: все ли важные страницы попали в sitemap, нет ли дублей, 404, редиректов, <code>noindex</code>-страниц и конфликтов с canonical.</p><p>Для быстрой проверки можно использовать валидатор:</p><div><p><img src="https://baranov.guru/assets/tools/sitemap-validator/cover.webp" alt="Валидатор sitemap.xml"></p><p><a href="https://baranov.guru/tools/sitemap-validator/">Валидатор sitemap.xml</a> — Проверка структуры sitemap.xml: urlset, sitemapindex, loc, lastmod, changefreq, priority и типовые ошибки.</p></div><p>После проверки sitemap стоит добавить в регулярный процесс поддержки сайта. Возвращайтесь к нему после редизайна, смены CMS, изменения URL, запуска новых разделов, массового обновления контента, удаления старых страниц, появления ошибок в Search Console или заметного падения органического трафика.</p><h2 id="service">Когда стоит подключать специалиста по техническому SEO </h2><p>Если у вас небольшой сайт и нужно просто создать базовый sitemap, можно справиться самостоятельно: сгенерировать файл, проверить его валидатором и отправить в <a href="https://search.google.com/search-console/sitemaps">Google Search Console</a> и <a href="https://webmaster.yandex.ru/tools/sitemap/">Яндекс Вебмастер</a>.</p><p>Но если проблема шире, не стоит ограничиваться XML-файлом. Sitemap часто показывает симптомы: страницы не индексируются, в поиске появляются дубли, после редизайна проседает трафик, новые материалы долго не попадают в выдачу, а ошибки возвращаются после каждого релиза.</p><p>В таких случаях нужен уже не разовый sitemap, а технический SEO-аудит. Он помогает понять, как поисковики реально видят сайт: какие страницы доступны, какие закрыты, где появляются дубли, правильно ли работают canonical, robots.txt, редиректы, внутренняя перелинковка и JavaScript-рендеринг.</p><p>Обычно в аудит входят:</p><ul><li>индексация;</li><li>sitemap.xml;</li><li>robots.txt;</li><li>canonical;</li><li>дубли страниц;</li><li>редиректы;</li><li>статусы ответа сервера;</li><li>внутренняя перелинковка;</li><li>архитектура сайта;</li><li>JavaScript-рендеринг;</li><li>производительность;</li><li>ошибки в <a href="https://search.google.com/search-console/sitemaps">Google Search Console</a> и <a href="https://webmaster.yandex.ru/tools/sitemap/">Яндекс Вебмастере</a>.</li></ul><p>Если вам нужен не просто XML-файл, а понятная система индексации без дублей, технических утечек и потерянных страниц, начните с технического SEO-аудита.</p><div><p><img src="https://baranov.guru/assets/services/technical-seo/cover.webp" alt="Техническая SEO-оптимизация"></p><p><a href="https://baranov.guru/services/technical-seo/">Техническая SEO-оптимизация</a> — Аудит индексации, sitemap.xml, robots.txt, canonical, дублей, редиректов, архитектуры сайта и технических ошибок.</p></div><p>На современных frontend-проектах проблемы sitemap и индексации часто появляются после перехода на SPA, SSR, headless CMS или сложный JavaScript-рендеринг. Формально URL могут быть в sitemap, но поисковик всё равно сталкивается с пустыми состояниями, задержками рендера, недоступным контентом или некорректной загрузкой важных блоков.</p><p>Если проблема окажется не только в SEO-настройках, но и в реализации сайта — например, в рендеринге, скорости, lazy loading или архитектуре фронтенда — это уже зона пересечения SEO и разработки. В таком случае технические рекомендации нужно не просто описать, а аккуратно внедрить в код.</p><div><p><img src="https://baranov.guru/assets/services/frontend/cover.webp" alt="Frontend-разработка"></p><p><a href="https://baranov.guru/services/frontend/">Frontend-разработка</a> — Исправление технических проблем интерфейса, которые мешают скорости, доступности, индексации и росту сайта.</p></div><h2>Короткий итог</h2><p>Sitemap.xml нужен не для галочки.</p><p>Он помогает поисковикам понимать структуру сайта, быстрее находить важные страницы и отслеживать значимые обновления. Но сам по себе sitemap не гарантирует индексацию, не исправляет дубли, не выбирает canonical и не заменяет техническое SEO.</p><p>Хороший sitemap содержит только нужные URL, обновляется вместе с сайтом, не включает мусорные страницы, использует честный <code>lastmod</code>, доступен поисковым роботам и регулярно проверяется после изменений на сайте.</p><p>Плохой sitemap может годами выглядеть «нормально», но при этом мешать поисковикам понимать сайт.</p><p>Хороший sitemap.xml — это способ контролировать:</p><ul><li>какие страницы реально видят поисковики;</li><li>какие URL индексируются;</li><li>где появляются технические ошибки;</li><li>как сайт переживает релизы, редизайны и миграции.</li></ul><p>Именно поэтому sitemap почти всегда связан с более широкими задачами технического SEO: архитектурой, canonical, robots.txt, дублями, редиректами, скоростью и корректной реализацией интерфейса.</p><aside class="post-disclaimer" role="note"><p>Эта статья объясняет общий подход к sitemap.xml и техническому SEO. Для конкретного сайта выводы нужно делать после проверки структуры, индексации, robots.txt, canonical, логики генерации страниц и данных из панелей вебмастеров.</p></aside></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/sitemap-xml/og.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Начинаем работу с Yandex AI Studio]]></title>
            <link>https://baranov.guru/posts/yandex-gpt-intro/</link>
            <guid>https://baranov.guru/posts/yandex-gpt-intro</guid>
            <pubDate>Sat, 16 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Как получить API-ключ Yandex AI Studio, подключить OpenAI SDK в Node.js и вызывать YandexGPT: первый запрос, чат, JSON Schema и параметры генерации.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Статья с описанием сервиса <strong>Foundation Models</strong>, опубликованная в 2024 году, доступна по <a href="https://baranov.guru/posts/yandex-gpt-intro-2024/">этому адресу</a>.</p></aside><p><a href="https://aistudio.yandex.ru/ru">Yandex AI Studio</a> — это платформа для работы с генеративными моделями Яндекса через API.</p><p>С её помощью можно:</p><ul><li>генерировать текст;</li><li>создавать AI-чаты;</li><li>работать с embeddings;</li><li>строить RAG-системы;</li><li>создавать AI-агентов;</li><li>использовать OpenAI-совместимый API;</li><li>интегрировать LLM в Node.js приложения.</li></ul><p>В этой статье разберём:</p><ul><li>как <a href="#key">получить API-ключ</a>;</li><li>как <a href="#sdk">установить и использовать OpenAI SDK</a>;</li><li>как <a href="#first">сделать первый запрос</a>;</li><li>как <a href="#chat">работать с чатами</a>;</li><li>как <a href="#json">генерировать JSON</a>.</li></ul><h2 id="api">Доступные API </h2><p>Yandex AI Studio предоставляет два типа API:</p><ul><li><a href="https://aistudio.yandex.ru/docs/ru/ai-studio/concepts/api#openai">OpenAI-совместимые API</a>;</li><li><a href="https://aistudio.yandex.ru/docs/ru/ai-studio/concepts/api#yc">специализированные API Yandex Cloud</a>.</li></ul><p>Самый актуальный список можно найти в <a href="https://aistudio.yandex.ru/docs/ru/ai-studio/concepts/api.html">официальной документации</a>.</p><h3 id="api-table">OpenAI-совместимые API </h3><p>Это API позволяют использовать инструменты, созданные для работы с Open AI, для работы с AI Studio API.</p><table><thead><tr><th>API</th><th>Технология</th><th>Описание</th></tr></thead><tbody><tr><td>Models API</td><td>REST</td><td>Получение списка доступных моделей и их идентификаторов</td></tr><tr><td>Chat Completions API</td><td>REST</td><td>Генерация текста по промпту без управления диалогом</td></tr><tr><td>Conversations API</td><td>REST</td><td>Работа с историей диалога и контекстом</td></tr><tr><td>Responses API</td><td>REST</td><td>Создание агентов. Позволяет генерировать текст, вызывать инструменты, получать структурированные ответы, реализовывать RAG-сценарии и создавать мультиагентные системы</td></tr><tr><td>Realtime API</td><td>WebSocket</td><td>Потоковая генерация текста и аудио с низкой задержкой для сценариев с голосовым входом</td></tr><tr><td>Files API</td><td>REST</td><td>Загрузка, хранение и работа с файлами для RAG-сценариев</td></tr><tr><td>Embeddings API</td><td>REST</td><td>Получение эмбеддингов для семантических задач</td></tr><tr><td>Vector stores API</td><td>REST</td><td>Индексация данных и извлечение данных для RAG</td></tr></tbody></table><h3 id="special-api-table">Специализированные API </h3><table><thead><tr><th>API</th><th>Технология</th><th>Описание</th></tr></thead><tbody><tr><td>Text Generation API</td><td>gRPC, REST</td><td>Генерация текстов по промпту, вызов функций, поддержка структурированного ответа</td></tr><tr><td>Image Generation API</td><td>gRPC, REST</td><td>Генерация изображений по текстовому описанию</td></tr><tr><td>Batch Inference API</td><td>gRPC</td><td>Асинхронная обработка большого количества запросов</td></tr><tr><td>Text Classification API</td><td>gRPC, REST</td><td>Классификация, модерация, определение тематик</td></tr><tr><td>Embeddings API</td><td>gRPC, REST</td><td>Векторное представление текста</td></tr><tr><td>Files API</td><td>gRPC, REST</td><td>Загрузка и хранение файлов и данных</td></tr><tr><td>Dataset API</td><td>gRPC</td><td>Управление датасетами для обучения</td></tr><tr><td>Tuning API</td><td>gRPC</td><td>Дообучение моделей под предметную область</td></tr><tr><td>MCP Gateway API</td><td>gRPC, REST</td><td>Создание, получение списка и удаление MCP-серверов</td></tr></tbody></table><h2 id="key">Создаём API-ключ </h2><p>Для начала работы понадобится:</p><ul><li>аккаунт в Yandex Cloud, с <strong>привязанным способом оплаты</strong>;</li><li><strong>API-ключ</strong>;</li><li><strong>Folder ID</strong>.</li></ul><p>После авторизации и заполнения платёжной информации:</p><ol><li>Откройте <a href="https://aistudio.yandex.ru/platform">AI Studio</a>.</li></ol><p><img src="https://baranov.guru/assets/posts/yandex-gpt-intro/yandex-ai-studio-interface.webp" alt="Интерфейс Yandex AI Studio" title="Интерфейс Yandex AI Studio"></p><ol start="2"><li>Создайте API-ключ.</li></ol><p>Для этого нажмите Создать API-ключ в правом верхнем углу.</p><p><img src="https://baranov.guru/assets/posts/yandex-gpt-intro/create-api-key.webp" alt="Кнопка &#x22;Создать API-ключ&#x22;" title="Кнопка &#x22;Создать API-ключ&#x22;"></p><p>И в появившемся диалоге, нажмите Создать.</p><p><img src="https://baranov.guru/assets/posts/yandex-gpt-intro/create-api-key-dialog.webp" alt="Диалог &#x22;Создание API-ключа&#x22;" title="Диалог &#x22;Создание API-ключа&#x22;"></p><ol start="3"><li>Сохраните ключ.</li></ol><p>После закрытия окна, вы не сможете повторно его посмотреть, только создать новый ключ.</p><p><img src="https://baranov.guru/assets/posts/yandex-gpt-intro/ai-studio-key.webp" alt="Сгенерированный ключ" title="Сгенерированный ключ"></p><ol start="4"><li>Сохраните Folder ID</li></ol><p>Folder ID можно увидеть прямо в url страницы:</p><p><code>https://aistudio.yandex.ru/platform/folders/&#x3C;ваш_folder_id></code></p><h2 id="project">Создаём проект </h2><p>Создадим новую папку</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">mkdir</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> yandex-ai-demo</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">cd</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> yandex-ai-demo</span></span></code></pre><p>Инициализируем новый Node.js проект:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> init</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> -y</span></span></code></pre><p>Установим зависимости:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> install</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> openai</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> dotenv</span></span></code></pre><p>Создадим <code>.env</code> файл:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>YANDEX_API_KEY=ваш_api_ключ</span></span>
<span class="line"><span>YANDEX_FOLDER_ID=ваш_folder_id</span></span></code></pre><h2 id="sdk">Подключаем OpenAI SDK </h2><p>Yandex AI Studio совместим с OpenAI API. Это значит, что можно использовать официальный OpenAI SDK.</p><p>Создадим файл <code>index.js</code>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> dotenv </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'dotenv'</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> OpenAI </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'openai'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">dotenv</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.config</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> apiKey</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">env</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YANDEX_API_KEY</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> folderId</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">env</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YANDEX_FOLDER_ID</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> openai</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> OpenAI</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    apiKey</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> apiKey</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    project</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> folderId</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    baseURL</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'https://ai.api.cloud.yandex.net/v1'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">})</span></span></code></pre><p>Тут важно указать <code>baseURL</code> и <code>project</code>, иначе будете стучаться со своими запросами в Open AI.</p><h2 id="first">Первый запрос к модели </h2><p>Теперь отправим первый запрос через <a href="https://aistudio.yandex.ru/docs/ru/ai-studio/responses/">Responses API</a>.</p><aside class="post-disclaimer" role="note"><p>Для полноты картины, вот ссылка на соответствующую <a href="https://developers.openai.com/api/reference/responses/overview">документацию от Open AI</a></p></aside><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> dotenv </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'dotenv'</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> OpenAI </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'openai'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">dotenv</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.config</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">async</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> apiKey</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">env</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YANDEX_API_KEY</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> folderId</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">env</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YANDEX_FOLDER_ID</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> openai</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> OpenAI</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        apiKey</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> apiKey</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        project</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> folderId</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        baseURL</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'https://ai.api.cloud.yandex.net/v1'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    })</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> response</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> openai</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">responses</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.create</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        model</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `gpt://</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">folderId</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/yandexgpt-lite/latest`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        input</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'Объясни простыми словами как работает openai.responses.create'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    })</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">response</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.output_text)</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">main</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span></span></code></pre><p>Запускаем скрипт:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">node</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> index.js</span></span></code></pre><p>В ответ модель вернёт сгенерированный текст.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-markdown"><span class="line"><span style="color:#6F42C1;--shiki-light-font-weight:bold;--shiki-dark:#FF7A84;--shiki-dark-font-weight:bold">**OpenAI.responses.create**</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> — это инструмент, который позволяет отправлять запрос на обработку искусственного интеллекта (ИИ) и получать ответ в виде текста.</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">Вы вводите вопрос или задание, а система обрабатывает его с помощью алгоритмов и возвращает ответ, основанный на полученных данных.</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">Для этого надо:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">1. Сформулировать запрос — задать вопрос или описать задачу.</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">2. Отправить запрос в систему.</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">3. Получить ответ от системы.</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">Этот инструмент может быть использован для различных целей, например, для создания текстов, перевода языков, написания кода и других задач.</span></span></code></pre><h2 id="request">Как устроен запрос </h2><p>Разберём базовые параметры запроса.</p><h3>input</h3><p>В самом простом случае - это текст вашего запроса.</p><h3>model</h3><p>URI модели.</p><p>Например:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>gpt://&#x3C;folder_id>/yandexgpt-lite/latest</span></span></code></pre><p>или:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>gpt://&#x3C;folder_id>/yandexgpt/latest</span></span></code></pre><p>Яндекс предлагает несколь моделей собственной разработки и довольно большой список открытых моделей через <a href="https://aistudio.yandex.ru/docs/ru/ai-studio/concepts/generation/">Model Gallery</a>.</p><p><strong>yandexgpt-lite</strong> подходит для:</p><ul><li>AI-чатов;</li><li>Telegram-ботов;</li><li>генерации текста;</li><li>быстрых ответов.</li></ul><p>Главные плюсы:</p><ul><li>дешевле;</li><li>быстрее работает.</li></ul><p><strong>yandexgpt</strong> подходит для:</p><ul><li>сложных задач;</li><li>длинного контекста;</li><li>генерации кода;</li><li>генерации JSON (structured output);</li><li>AI-агентов.</li></ul><h2 id="generation">Настройка генерации </h2><p>Поведение модели можно настраивать через параметры генерации.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> response</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> openai</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">responses</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.create</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  model</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `gpt://</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">folderId</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/yandexgpt-lite/latest`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  input</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'Придумай название IT стартапа'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  temperature</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.9</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  max_output_tokens</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 300</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">})</span></span></code></pre><h3>temperature</h3><p>Температура — это параметр больших текстовых моделей, который отвечает за случайность генерации.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>0.1 → более точные ответы</span></span>
<span class="line"><span>1.0 → более креативные ответы</span></span></code></pre><h3>max_output_tokens</h3><p>Максимальная длина ответа.</p><h2 id="assistant">Пример AI-ассистента </h2><p>Через <code>instructions</code> можно задавать поведение модели.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> response</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> openai</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">responses</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.create</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    model</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `gpt://</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">folderId</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/yandexgpt/latest`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    instructions</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `Ты продавец-консультант в магазине одежды.`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    input</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'Эта футболка меня полнит?'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">response</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.output_text)</span></span></code></pre><aside class="post-disclaimer" role="note"><p>Более подробно можно почитать в документации <a href="https://developers.openai.com/api/docs/guides/text#message-roles-and-instruction-following">OpenAI</a></p></aside><h2 id="chat">Пример чат-диалога </h2><p>Для поддержки истории сообщений используется <a href="https://aistudio.yandex.ru/docs/ru/ai-studio/chat/">Chat Completions API</a>.</p><aside class="post-disclaimer" role="note"><p>Для полноты картины, вот ссылка на соответствующую <a href="https://developers.openai.com/api/reference/chat-completions/overview">документацию от Open AI</a></p></aside><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> response</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> openai</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">chat</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">completions</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.create</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  model</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `gpt://</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">folderId</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/yandexgpt-lite/latest`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  messages</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      role</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'system'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      content</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'Ты полезный AI помощник'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      role</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'user'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      content</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'Привет'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      role</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'assistant'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      content</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'Здравствуйте!'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      role</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'user'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      content</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'Что такое YandexGPT?'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  response</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.choices[</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">0</span><span style="color:#24292EFF;--shiki-dark:#B392F0">].</span><span style="color:#1976D2;--shiki-dark:#79B8FF">message</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.content</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">)</span></span></code></pre><p>Chat Completions API подходит для:</p><ul><li>AI-чатов;</li><li>Telegram-ботов;</li><li>поддержки контекста диалога.</li></ul><h2 id="json">Генерация JSON </h2><p>AI Studio поддерживает <strong>structured output</strong> через JSON Schema.</p><p>Это особенно полезно для:</p><ul><li>парсинга данных;</li><li>автоматизации;</li><li>AI-агентов.</li></ul><p>Пример:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> response</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> openai</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">chat</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">completions</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.create</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  model</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `gpt://</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">folderId</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/yandexgpt/latest`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  messages</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          role</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'user'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          content</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'Baranov.Guru — лучшее агенство по IT-консалтингу, ИИ и автоматизации бизнеса в России'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  response_format</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      type</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'json_schema'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      json_schema</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          type</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'object'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          properties</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">              title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">                  type</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'string'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">              }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">              summary</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">                  type</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'string'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">              }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">              tags</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">                  type</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'array'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">                  items</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">                      type</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'string'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">                  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">              }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          required</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#22863A;--shiki-dark:#FFAB70">'title'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'summary'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'tags'</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  response</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.choices[</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">0</span><span style="color:#24292EFF;--shiki-dark:#B392F0">].</span><span style="color:#1976D2;--shiki-dark:#79B8FF">message</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.content</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">)</span></span></code></pre><p>Модель вернёт валидный JSON по указанной схеме.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "summary"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Baranov.Guru — агентство по IT-консалтингу, ИИ и автоматизации бизнеса в России"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "tags"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#22863A;--shiki-dark:#FFAB70">"IT-консалтинг"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ИИ"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "автоматизация бизнеса"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "агентство"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "title"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Baranov.Guru"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h2 id="conclusion">Заключение </h2><p><strong>Yandex AI Studio</strong> позволяет быстро интегрировать генеративные модели в приложения.</p><p>Главный плюс платформы — <strong>OpenAI</strong>-совместимый API и российская прописка. Благодаря этому можно использовать актуальные библиотеки с минимальными изменениями в коде и не беспокоиться за трансграничную передачу данных.</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/yandex-gpt-intro/og-2026.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[10 ошибок, из-за которых сайт не приносит заявки]]></title>
            <link>https://baranov.guru/posts/website-no-leads-mistakes/</link>
            <guid>https://baranov.guru/posts/website-no-leads-mistakes</guid>
            <pubDate>Thu, 07 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Разбираем 10 распространённых ошибок, из-за которых сайт не конвертирует посетителей в клиентов и не приносит заявки.]]></description>
            <content:encoded><![CDATA[<div><blockquote><p>Почему сайт получает трафик, но не конвертирует посетителей в клиентов?</p></blockquote></div><p>У бизнеса может быть <a href="https://baranov.guru/services/website/">современный сайт</a>, настроенная реклама и <a href="https://baranov.guru/services/technical-seo/">стабильный поисковый трафик</a>. Но при этом заявок — единицы. Или стоимость лида оказывается слишком высокой.</p><p>В большинстве случаев проблема не связана с одной критической ошибкой. Обычно сайт просто не помогает пользователю принять решение и пройти путь до обращения.</p><p>Ниже — 10 распространённых причин, из-за которых сайты теряют потенциальных клиентов.</p><div><section><h3 id="error-1">Пользователь не понимает, чем занимается компания</h3><figure><img src="https://baranov.guru/assets/posts/website-no-leads-mistakes/01.webp" alt="Пользователь не понимает, чем занимается компания"></figure><p>Первые секунды на сайте определяют, останется человек или закроет вкладку.</p><p>Пользователь должен <strong>быстро</strong> получить ответы на три вопроса:</p><ul><li><strong>Что это за компания?</strong></li><li><strong>Какие услуги или продукты здесь предлагают?</strong></li><li><strong>Подходит ли это под мою задачу?</strong></li></ul><p>Если сайт не даёт понятного ответа — посетитель уходит.</p><p>Частая ошибка — <strong>абстрактные формулировки</strong> на первом экране:</p><ul><li>«Комплексные digital-решения»</li><li>«Инновационный подход к развитию бизнеса»</li><li>«Создаём будущее вместе»</li></ul><p>Такие фразы выглядят универсально, но <strong>не объясняют ценность</strong>.</p><p>Намного эффективнее работают конкретные формулировки:</p><ul><li>«Разработка интернет-магазинов на Tilda под ключ»</li><li>«Настройка рекламы в Яндекс Директ для стоматологий»</li><li>«Производство кухонь на заказ в Москве за 30 дней»</li></ul><p><strong>Чем понятнее</strong> предложение, тем <strong>выше вероятность</strong>, что пользователь продолжит изучать сайт.</p></section><section><h3 id="error-2">Интерфейс перегружен лишними элементами</h3><figure><img src="https://baranov.guru/assets/posts/website-no-leads-mistakes/02.webp" alt="Интерфейс перегружен лишними элементами"></figure><p><strong>Избыточный дизайн</strong> часто <strong>снижает конверсию</strong>.</p><p>Когда на странице одновременно используются:</p><ul><li>анимации;</li><li>слайдеры;</li><li>всплывающие окна;</li><li>видео;</li><li>автозапуск чатов;</li><li>сложные визуальные эффекты —</li></ul><p>внимание пользователя рассеивается.</p><p>Вместо того чтобы вести посетителя к заявке, <strong>сайт начинает отвлекать</strong>.</p><p>Эффективный продающий сайт строится вокруг понятного сценария:</p><ol><li><strong>проблема;</strong></li><li><strong>решение;</strong></li><li><strong>преимущества;</strong></li><li><strong>кейсы;</strong></li><li><strong>подтверждение экспертизы;</strong></li><li><strong>заявка.</strong></li></ol><p><strong>Чем меньше</strong> визуального шума — <strong>тем проще</strong> пользователю принять решение.</p></section><section><h3 id="error-3">На сайте нет сильного оффера</h3><figure><img src="https://baranov.guru/assets/posts/website-no-leads-mistakes/03.webp" alt="На сайте нет сильного оффера"></figure><p>Даже качественный дизайн не компенсирует отсутствие понятного предложения.</p><p>После просмотра страницы пользователь должен понимать <strong>почему стоит обратиться именно в эту компанию</strong>.</p><p>Слабый оффер выглядит так:</p><blockquote><p>Разработка сайтов для бизнеса</p></blockquote><p>Более сильный вариант:</p><blockquote><p>Разрабатываем сайты для B2B-компаний, которые помогают получать заявки из SEO и рекламы</p></blockquote><p>Или:</p><blockquote><p>Запускаем интернет-магазины на Tilda за 21 день с интеграцией оплаты и доставки</p></blockquote><p>Хороший оффер содержит:</p><ul><li><strong>конкретику;</strong></li><li><strong>понятную выгоду;</strong></li><li><strong>специализацию;</strong></li><li><strong>результат;</strong></li><li><strong>сроки или ограничения.</strong></li></ul><p><strong>Чем меньше</strong> пользователю <strong>приходится додумывать</strong> ценность самостоятельно, <strong>тем выше конверсия</strong>.</p></section><section><h3 id="error-4">Сайт медленно загружается</h3><figure><img src="https://baranov.guru/assets/posts/website-no-leads-mistakes/04.webp" alt="Сайт медленно загружается"></figure><p><strong>Скорость напрямую влияет на количество заявок.</strong></p><p>Если страница загружается 5–7 секунд, часть пользователей <strong>не дождётся</strong> открытия сайта — особенно с мобильных устройств.</p><p>Типовые причины:</p><ul><li>тяжёлые изображения;</li><li>перегруженный frontend;</li><li>большое количество сторонних скриптов;</li><li>слабый хостинг;</li><li>избыточные анимации.</li></ul><p>Медленный сайт влияет не только на удобство пользователя, но и на:</p><ul><li>конверсию;</li><li>стоимость рекламы;</li><li>SEO-показатели;</li><li>глубину просмотра;</li><li>поведенческие факторы.</li></ul><p><strong>Чем медленнее работает сайт, тем выше потери рекламного бюджета.</strong></p></section><section><h3 id="error-5">Мобильная версия неудобна</h3><figure><img src="https://baranov.guru/assets/posts/website-no-leads-mistakes/05.webp" alt="Мобильная версия неудобна"></figure><p>Во многих нишах мобильный трафик уже <strong>превышает 70%</strong>.</p><p>При этом часть сайтов по-прежнему проектируется преимущественно под десктоп.</p><p>В результате на смартфоне:</p><ul><li>текст слишком мелкий;</li><li>кнопки неудобны для нажатия;</li><li>формы работают некорректно;</li><li>элементы интерфейса ломаются;</li><li>страницы долго загружаются.</li></ul><p>Пользователь <strong>не будет адаптироваться</strong> под неудобный интерфейс. Он просто <strong>уйдёт к конкурентам</strong>.</p><p>Проверить качество мобильной версии можно очень просто: открыть сайт с телефона и попробовать оставить заявку одной рукой.</p><p>Большинство проблем становятся очевидны сразу.</p></section></div><aside><h3>Сайт не приносит заявки?</h3><p>Устраним технические проблемы, которые мешают сайту приносить деньги: медленная загрузка, плохая мобильная версия, непонятный оффер и проблемы с SEO.</p></aside><div><section><h3 id="error-6">Сайт не вызывает доверия</h3><figure><img src="https://baranov.guru/assets/posts/website-no-leads-mistakes/06.webp" alt="Сайт не вызывает доверия"></figure><p>Пользователь редко оставляет заявку компании, о которой невозможно получить базовую информацию.</p><p>Особенно в сегментах с высоким чеком.</p><p>Если на сайте отсутствуют:</p><ul><li>кейсы;</li><li>отзывы;</li><li>примеры работ;</li><li>информация о компании;</li><li>реальные фотографии;</li><li>юридические данные;</li><li>понятные контакты —</li></ul><p>доверие к бизнесу снижается.</p><p>Даже если компания работает давно и качественно.</p><p>Сильнее всего доверие повышают:</p><ul><li><strong>реальные цифры;</strong></li><li><strong>конкретные результаты;</strong></li><li><strong>фотографии проектов;</strong></li><li><strong>видео;</strong></li><li><strong>разборы кейсов;</strong></li><li><strong>упоминания клиентов и партнёров.</strong></li></ul><p><strong>Блоки доверия</strong> — это не второстепенный контент, а один из <strong>ключевых факторов</strong> конверсии.</p></section><section><h3 id="error-7">Слишком сложные формы заявки</h3><figure><img src="https://baranov.guru/assets/posts/website-no-leads-mistakes/07.webp" alt="Слишком сложные формы заявки"></figure><p>Чем <strong>больше полей</strong> содержит форма — тем <strong>ниже вероятность</strong>, что пользователь её заполнит.</p><p>Типичная ошибка — попытка собрать максимум информации на первом контакте:</p><ul><li>имя;</li><li>телефон;</li><li>email;</li><li>компания;</li><li>бюджет;</li><li>комментарий;</li><li>количество сотрудников;</li><li>удобное время звонка.</li></ul><p>Для большинства сайтов это <strong>избыточно</strong>.</p><p>Основная задача первой формы — не собрать подробную анкету, а <strong>начать коммуникацию</strong>.</p><p>Во многих случаях достаточно:</p><ul><li>имени;</li><li>телефона или Telegram.</li></ul><p>Остальные детали можно уточнить позже.</p></section><section><h3 id="error-8">Нет понятного призыва к действию</h3><figure><img src="https://baranov.guru/assets/posts/website-no-leads-mistakes/08.webp" alt="Нет понятного призыва к действию"></figure><p>Иногда пользователь готов обратиться, но сайт не даёт <strong>понятного</strong> следующего шага.</p><p>Типовые проблемы:</p><ul><li>кнопки плохо заметны;</li><li>форма находится слишком низко;</li><li>CTA не объясняет выгоду;</li><li>непонятно, что произойдёт после отправки заявки.</li></ul><p>Слабые CTA:</p><blockquote><p>Отправить</p></blockquote><blockquote><p>Подробнее</p></blockquote><p>Более эффективные варианты:</p><ul><li>«Получить расчёт стоимости»</li><li>«Обсудить проект»</li><li>«Получить аудит сайта»</li><li>«Узнать сроки запуска»</li></ul><p>Хороший призыв к действию всегда:</p><ul><li><strong>конкретен;</strong></li><li><strong>понятен;</strong></li><li><strong>связан с ценностью для клиента.</strong></li></ul></section><section><h3 id="error-9">Сайт создаётся «для себя», а не для клиента</h3><figure><img src="https://baranov.guru/assets/posts/website-no-leads-mistakes/09.webp" alt="Сайт создаётся «для себя», а не для клиента"></figure><p>Многие компании пытаются <a href="https://baranov.guru/services/website/">сделать сайт</a> максимально необычным:</p><ul><li>сложный дизайн;</li><li>нестандартную навигацию;</li><li>большое количество анимаций;</li><li>креативные интерфейсные решения.</li></ul><p>Но задача пользователя — не оценивать дизайнерские эксперименты.</p><p>Он хочет быстро решить свою задачу.</p><p>Если интерфейс мешает получить информацию или оставить заявку, сайт начинает снижать конверсию.</p><p>На практике <strong>самые эффективные</strong> коммерческие сайты часто выглядят достаточно <strong>сдержанно</strong>.</p><p>Потому что их <strong>основная цель</strong> — не производить впечатление, а <strong>приводить клиентов</strong>.</p></section><section><h3 id="error-10">Нет аналитики и данных</h3><figure><img src="https://baranov.guru/assets/posts/website-no-leads-mistakes/10.webp" alt="Нет аналитики и данных"></figure><p>Без <a href="https://baranov.guru/services/web-analytics/">аналитики</a> <strong>невозможно понять</strong>, почему сайт не приносит заявки.</p><p>Если не настроено отслеживание действий пользователей, бизнес не видит:</p><ul><li>где теряется трафик;</li><li>какие страницы работают хуже;</li><li>какие источники приводят клиентов;</li><li>сколько стоит лид;</li><li>какие каналы окупаются.</li></ul><p>В результате любые изменения превращаются <strong>в предположения</strong>.</p><p>Базовый набор аналитики должен включать:</p><ul><li>Яндекс Метрику;</li><li>Google Analytics;</li><li>настройку целей;</li><li>отслеживание форм;</li><li>коллтрекинг при необходимости;</li><li>аналитику рекламных кампаний.</li></ul><p>Только <strong>данные позволяют системно повышать конверсию сайта</strong>.</p></section></div><h2>Вывод</h2><p>Проблема большинства сайтов не в дизайне. Чаще всего сайт просто <strong>не помогает</strong> пользователю <strong>принять решение и сделать следующий шаг</strong>.</p><p>На конверсию влияют:</p><ul><li>понятность предложения;</li><li>скорость работы;</li><li>мобильная версия;</li><li>доверие;</li><li>структура страницы;</li><li>качество оффера;</li><li>сценарий взаимодействия.</li></ul><p><strong>Эффективный сайт</strong> — это не набор визуальных эффектов, а <strong>инструмент продаж</strong>.</p><p>И <strong>чем проще</strong> пользователю:</p><ul><li>понять предложение;</li><li>оценить выгоду;</li><li>убедиться в надёжности компании;</li><li>оставить заявку —</li></ul><p><strong>тем выше</strong> вероятность <strong>конверсии</strong>.</p>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/website-no-leads-mistakes/og.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Настройка MAX-бота для работы в Yandex Cloud Functions]]></title>
            <link>https://baranov.guru/posts/yandex-cloud-functions-max-bot/</link>
            <guid>https://baranov.guru/posts/yandex-cloud-functions-max-bot</guid>
            <pubDate>Sat, 14 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по настройке и хостингу простейшего MAX-бота в serverless-окружении Yandex Cloud Functions.]]></description>
            <content:encoded><![CDATA[<div><h2 id="why">Зачем нужна эта статья? </h2><p>MAX-боты умееют получать события от платформы несколькими способами:</p><ul><li>В режиме <strong>ожидания запросов</strong> от сервера (long-polling);</li><li>В режиме получения событий через <strong>webhook</strong>;</li></ul><p>В <a href="https://dev.max.ru/docs/chatbots/bots-coding/prepare">документации</a> к официальной <a href="https://dev.max.ru/docs/chatbots/bots-coding/library/js">typescript библиотеке</a> для создания MAX-ботов есть примеры запуска ботов в режиме ожидания запросов. В котором бот подключается к серверам MAX и ждёт события обновления. Как только сервер присылает обновление, бот обрабатывает его.</p><p>К сожалению такой режим работы не подходит для <strong>serverless</strong> окружений, а примеров запуска бота через получение событий по вебхуку, на момент написания этой статьи, в документации не нашлось.</p><p>В этой статье я постараемся исправить этот недочёт.</p><h2 id="solution">Решение для тех кто торопится </h2><p>Для тех кто спешит и умеет настраивать бота для работы с webhook, сразу публикуем базовое готовое решение:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { Update } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> '@maxhub/max-bot-api/dist/core/network/api'</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { Bot</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> Context } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> '@maxhub/max-bot-api'</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> handler</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(event</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexCloudFunctionEvent</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#1976D2;--shiki-dark:#79B8FF">event</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.body) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">typeof</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> event</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.body </span><span style="color:#D32F2F;--shiki-dark:#F97583">===</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'string'</span><span style="color:#D32F2F;--shiki-dark:#F97583"> &#x26;&#x26;</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> event</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">body</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">length</span><span style="color:#D32F2F;--shiki-dark:#F97583"> ></span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">      try</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">        // Парсим тело запроса</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">        const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> update</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Update</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> JSON</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.parse</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">event</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.body) </span><span style="color:#D32F2F;--shiki-dark:#F97583">as</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Update</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">        // Создаём бота</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">        const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> bot</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> createBot</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">        // Ниже то, для чего нужна эта статья</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">        const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> ctx</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Context</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(update</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> bot</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.api</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> bot</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.botInfo);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">        await</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> bot</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.middleware</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()(ctx</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> Promise</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.resolve</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">        return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { statusCode</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 200</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> body</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'ok'</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> };</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      } </span><span style="color:#D32F2F;--shiki-dark:#F97583">catch</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (error) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">        return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { statusCode</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 500</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> body</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'error'</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> };</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { statusCode</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 200</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> body</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'ok'</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> };</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> createBot</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // В данном примере токен для бота хранится в переменной окружения</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> token</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">env</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">MAX_BOT_TOKEN</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">!</span><span style="color:#24292EFF;--shiki-dark:#B392F0">token) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    throw</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Error</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">'MAX_BOT_TOKEN не найден'</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> bot</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Bot</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(token);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // Тут описываете вашу логику работы бота</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> bot;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre></div><aside><h3>Нужен MAX-бот под ваш сценарий?</h3><p>Сделаем MAX-бота “под ключ”: архитектура, хостинг, webhook, логика, тестирование и поддержка.</p></aside><div><h2 id="explanation">Разбор решения </h2><p>Не буду долго и подробно расписывать как мы его нашли, но тем не менее отмечу пару важных моментов:</p><p>Мы искали что-то похожее на</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">bot</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.handleUpdate</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(update)</span></span></code></pre><p>из <a href="https://grammy.dev/">grammY</a>, библиотеки для Telegram-ботов.</p><p>И действительно, такой метод у класса Bot есть, но он приватный и не доступен для вызова.</p><p>Тут наверное можно было бы кастануть Bot к нужному мне типу с не приватным <strong>handleUpdate</strong>, но мы решили чуть-чуть покопать и изучить код библиотеки.</p><p>В итоге поняли что можно вызвать <strong>middleware()</strong>, предварительно добавив событие Update в контекст бота.</p><h2 id="webhook">Подписка функции на получение событий </h2><p>Для того чтобы подписать облачную функцию на обработку событий нужно:</p><ul><li><strong>Знать публичный url для вызова функции</strong> (можно посмотреть в настройках функции в консоли управления Yandex Cloud);</li><li><strong>Отправить</strong> <a href="https://dev.max.ru/docs-api/methods/POST/subscriptions"><strong>API запрос</strong></a></li></ul><p>На этом всё. <strong>Спасибо за внимание!</strong></p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/yandex-cloud-functions-max-bot/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Автоматизируем публикацию npm-пакета с помощью Github Actions]]></title>
            <link>https://baranov.guru/posts/github-actions-npm-publish/</link>
            <guid>https://baranov.guru/posts/github-actions-npm-publish</guid>
            <pubDate>Fri, 25 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по подготовке npm-пакета к автоматизированному релизу с помощью Github Actions.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ В ноябре 2025 года, npm изменили свою политику использования <strong>access tokens</strong>. <a href="https://docs.npmjs.com/about-access-tokens">Подробнее</a>.</p><p>Поэтому часть статьи про <a href="#token">получение токена</a> устарела.</p></aside><p>Не так давно мы <a href="https://baranov.guru/posts/react-telegram-widgets">выпустили npm-пакет @baranov-guru/react-telegram-widgets</a>. В этой статье решили поделиться своим опытом по автоматизации сборки и публикации обновлений npm-пакетов.</p><p>В начале статьи дана небольшая справка, о том что такое <strong>NPM</strong> и <strong>Github Actions</strong>.</p><aside class="post-disclaimer" role="note"><p>💡 Если вам интересно сразу готовое решение, <a href="#result">жмите сюда</a>.</p></aside><h2 id="npm">Что такое NPM? </h2><p><strong>NPM</strong> (Node Package Manager) — это менеджер пакетов для <strong>JavaScript</strong>. Проще говоря - это программа, которая помогает устанавливать и управлять чужими библиотеками (частями кода), и использовать их в своём JavaScript-проекте.</p><p>Она идёт в комплекте с <strong>Node.js</strong> и позволяет:</p><ul><li>быстро <strong>подключать нужные инструменты</strong> и функции,</li><li><strong>обновлять их</strong> до новых версий,</li><li><strong>удалять</strong>, если больше не нужны.</li></ul><p>Также через <strong>NPM</strong> можно найти тысячи готовых решений, которые другие разработчики выложили в общий доступ.</p><h2 id="github-actions">Что такое Github Actions? </h2><p><strong>GitHub Actions</strong> — это инструмент внутри <a href="https://github.com">GitHub</a>, который предоставляет инструменты для непрерывной интеграции и непрерывной поставки (<strong>CI/CD</strong>). Или если проще, то помогает автоматически выполнять задачи с вашим кодом:</p><ul><li><strong>собирать</strong> проект,</li><li>проверять, работает ли код (<strong>прогонять тесты</strong>),</li><li><strong>выкладывать</strong> его на сервер или в облако.</li></ul><p>Всё это происходит автоматически, по <strong>заранее созданным правилам</strong>, описанным в <strong>workflow</strong> файлах. Это удобно, потому что не нужно делать всё вручную.</p><h2 id="workflow">Собираем свой Github workflow </h2><p>Углубляться во все подробности написания workflow файлов, так же известных как "рабочие процессы", в этой статье не будем, оставим лишь <a href="https://docs.github.com/ru/actions/how-tos/writing-workflows">ссылку на официальную документацию</a>.</p><p>Нас интересует два момента:</p><ul><li><a href="#trigger"><strong>Когда запускать</strong> workflow?</a>;</li><li><a href="#publish"><strong>Как публиковать</strong> в npm</a>;</li></ul><h2 id="trigger">Когда запускать workflow? </h2><p>Для себя мы выбрали следующую схему работы:</p><ul><li>Все изменения вливаются в <strong>main ветку</strong> проекта;</li><li>Публикация же в npm происходит после ручного создания <a href="https://docs.github.com/ru/repositories/releasing-projects-on-github/about-releases">Github Release</a>.</li></ul><p>Для этого надо добавить следующий код в workflow:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-yaml"><span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">on</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  release</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    types</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#2B5581;--shiki-dark:#9DB1C5">published</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span></span></code></pre><p>Если же вы хотите публиковаться каждый раз при <strong>добавлении коммита в main</strong> ветку, то надо прописать следующее:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-yaml"><span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">on</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  push</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    branches</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#2B5581;--shiki-dark:#9DB1C5">main</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span></span></code></pre><h2 id="publish">Как публиковать в npm? </h2><p>Тут всё просто, необходимо использовать команду:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> publish</span></span></code></pre><p>Шаг в workflow может выглядеть следующим образом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-yaml"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">- </span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> Publish to npm</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  run</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> npm publish</span></span></code></pre><p>Однако, если запустить такой workflow, то он <strong>упадёт с ошибкой авторизации</strong>, при публикации пакета.</p><p>Причина банальна - <strong>нельзя</strong> опубликовать пакет <strong>анонимно</strong>, нужно представиться.</p><p>Для этого необходимо <strong>добавить токен</strong>.</p><h3 id="token">Получение токена </h3><p>Получить токен можно в личном кабинете <a href="https://www.npmjs.com/">NPM</a>.</p><p>Для этого надо:</p><ul><li><strong>перейти на страницу</strong> с токенами;</li></ul><p><img src="https://baranov.guru/assets/posts/github-actions-npm-publish/access-tokens.webp" alt="Переходим на страницу с токенами в личном кабинете" title="Переходим на страницу с токенами в личном кабинете"></p><ul><li>затем <strong>создать токен</strong>, нажав на кнопку "<strong>Generate New Token</strong>";</li></ul><p>Для простых целей, нам хватит <strong>классического legacy access token</strong>. Его и выберем:</p><p><img src="https://baranov.guru/assets/posts/github-actions-npm-publish/generate-new-classic-token-button.webp" alt="Кнопка Generate New Token" title="Кнопка Generate New Token"></p><ul><li><strong>заполнить форму</strong> и <strong>скопировать</strong> полученный токен;</li></ul><p>Так как мы собираемся публиковаться через <strong>Github Actions</strong>, то выбираем тип токена "<strong>Automation</strong>".</p><p><img src="https://baranov.guru/assets/posts/github-actions-npm-publish/create-classic-token.webp" alt="Создание токена" title="Создание токена"></p><p>Полученный токен необходимо добавить в <strong>repository secrets</strong> в репозитории проекта на github.</p><h3 id="secrets">Добавление токена в Secrets на Github </h3><p>Нам необходимо создать секрет <strong>NPM_TOKEN</strong>. Для этого необходимо:</p><ul><li>перейти в <strong>настройки репозитория</strong>;</li><li>развернуть вкладку <strong>Secrets And Variables</strong>;</li><li>выбрать <strong>Actions</strong>;</li></ul><p>На открывшейся странице нажать кнопку <strong>New Repository Secret</strong> и вставить в модальном окне полученный ранее токен</p><p><img src="https://baranov.guru/assets/posts/github-actions-npm-publish/github-secrets.webp" alt="Добавление токена в Secrets на Github" title="Добавление токена в Secrets на Github"></p><p>Теперь осталось только указать токен в переменной окружения <strong>NODE_AUTH_TOKEN</strong> в нашем workflow:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-yaml"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">- </span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> Publish to npm</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  run</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> npm publish</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  env</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    NODE_AUTH_TOKEN</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> ${{ secrets.NPM_TOKEN }}</span></span></code></pre><h3>Особенности публикации @scoped пакетов</h3><p>Если у вас, как и у нас, <a href="https://docs.npmjs.com/about-scopes">scoped package</a>, то <strong>по умолчанию</strong> пакет <strong>не будет</strong> публично доступен.</p><p>Для решиния этой проблемы необходимо добавить параметр <strong>access</strong> со значением <strong>public</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> publish</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> --access</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> public</span></span></code></pre><p>Как не трудно догадаться это сделает пакет <strong>публичным</strong> и предоставит к нему <strong>открытый доступ</strong>.</p><h3 id="provenance">Происхождение публикуемых пакетов (Provenance) </h3><p>Так же, <strong>Github Actions</strong> из коробки позволяют создавать <strong>сведения о происхождении</strong> публикуемых пакетов (<strong>Provenance</strong>).</p><p>Provenance содержит подписанную информацию о сборке и публикации проекта, тем самым <strong>повышает безопасность</strong> цепочки поставок пакетов и просто классно смотрится. 😉</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> publish</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> --provenance</span></span></code></pre><p>Однако, если запустить такой workflow, то мы увидим следующую ошибку:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> error</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> code</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> EUSAGE</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> error</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> Provenance</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> generation</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> in</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> GitHub</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> Actions</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> requires</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "write"</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> access</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> to</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> the</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "id-token"</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> permission</span></span></code></pre><p>Как и написано в ошибке, необходимо добавить в workflow пермиссию <strong>id-token</strong> со значением <strong>write</strong>. Выглядит она следующим образом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-yaml"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">permissions</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  id-token</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> write</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  contents</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> read</span></span></code></pre><p>Теперь, если вы всё сделали правильно, в опубликоманном пакете можно будет увидеть следующую плашку:</p><p><img src="https://baranov.guru/assets/posts/github-actions-npm-publish/provenance.webp" alt="Provenance" title="Плашка с provenance в описании пакета"></p><h2 id="result">Итоговый workflow </h2><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-yaml"><span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C"># Название workflow</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> Publish Package</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C"># Когда выполнять</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">on</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  release</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    types</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#2B5581;--shiki-dark:#9DB1C5">published</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C"># Работа с разрешениями</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">permissions</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  id-token</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> write</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  contents</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> read</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C"># Описание job</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">jobs</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  build-and-publish</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    runs-on</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> ubuntu-latest</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    steps</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">      # Получение актуального кода проекта</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      - </span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> Checkout code</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">        uses</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> actions/checkout@v4</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">      # Настройка окружения</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      - </span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> Setup Node.js</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">        uses</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> actions/setup-node@v4</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">        with</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">          node-version</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "18"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">          registry-url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://registry.npmjs.org"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">          cache</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "npm"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">      # Опционально: Установка зависимостей, линтинг, прогон тестов</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">      # ...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">      # Сборка пакета</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      - </span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> Build package</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">        run</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> npm run build</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">      # Публикация пакета</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      - </span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> Publish to npm</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">        run</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> npm publish --provenance --access public</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">        env</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">          NODE_AUTH_TOKEN</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> ${{ secrets.NPM_TOKEN }}</span></span></code></pre></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/github-actions-npm-publish/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем Telegram Widgets в React-приложение]]></title>
            <link>https://baranov.guru/posts/react-telegram-widgets/</link>
            <guid>https://baranov.guru/posts/react-telegram-widgets</guid>
            <pubDate>Tue, 15 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению Telegram Widgets в React-приложение с помощью npm-пакета @baranov-guru/react-telegram-widgets: посты, комментарии, кнопка «Поделиться» и вход через Telegram.]]></description>
            <content:encoded><![CDATA[<div><p>В этой статье расскажем что такое <strong>Telegram Widgets</strong>, зачем они нужны и как максимально просто добавить их в своё <strong>React</strong> приложение.</p><h2 id="what">Что такое Telegram Widgets? </h2><p><a href="https://core.telegram.org/widgets">Telegram Widgets</a> - <strong>API от Telegram</strong>, которое позволяет быстро добавить на сайт <strong>кнопку "Поделиться"</strong>, встроить <strong>посты из публичных каналов или групп</strong>. С его помощью пользователи также могут <strong>авторизоваться через Telegram</strong> или <strong>просматривать обсуждения</strong> прямо на сайте.</p><h2 id="problem">Проблема с использованием в React приложениях </h2><p>Дело в том что <strong>Telegram Widget</strong> - это <strong>script-тег</strong> вида:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">script</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  async</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  src</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://telegram.org/js/telegram-widget.js?22"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  data-telegram-post</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"baranov_guru/61"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  data-width</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"100%"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">>&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">script</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><p>Он инициализируется и добавляет в родительский контейнер iframe, который рендерит контент с нужными параметрами.</p><p>Из-за этого с виджетами очень неудобно работать в декларативном стиле, присущем React-компонентам. Поэтому нам пришлось написать небольшую обёртку над скриптами, позволяющую:</p><ul><li><strong>Подписываться на события загрузки</strong>;</li><li><strong>Отслеживать ошибки</strong>;</li><li>В удобном декларативном стиле <strong>пробрасывать необходимые свойства виджетов</strong>;</li><li>Корректно <strong>обрабатывать изменения свойств и перерендеры</strong> компонентов;</li></ul><p>А так как официального React-пакета для виджетов нет и не предвиделось, поэтому мы решили оформить её в виде npm-пакета <a href="https://www.npmjs.com/package/@baranov-guru/react-telegram-widgets">@baranov-guru/react-telegram-widgets</a>.</p><h2 id="package">Что делает пакет @baranov-guru/react-telegram-widgets? </h2><p>С версии <strong>1.1.0</strong> пакет поддерживает:</p><ul><li><strong>Комментарии</strong> (<code>TelegramDiscussionWidget</code>) — обсуждение постов прямо на сайте.</li><li><strong>Посты из ТГ-каналов</strong> (<code>TelegramPostWidget</code>) — отдельный пост из публичного Telegram-канала или группы.</li><li><strong>Кнопку «Поделиться»</strong> (<code>TelegramShareWidget</code>) — шаринг ссылки в Telegram.</li><li><strong>Вход через Telegram</strong> (<code>TelegramLoginWidget</code> и хук <code>useTelegramLogin</code>) — текущий OpenID Connect-логин.</li><li><strong>Legacy Login Widget</strong> (<code>TelegramLegacyLoginWidget</code>) — старый iframe-виджет с <code>hash</code>; для новых интеграций лучше OIDC.</li></ul><h2>Установка</h2><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> install</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> @baranov-guru/react-telegram-widgets</span></span></code></pre><h2 id="examples">Примеры использования </h2><h3 id="discussion-widget">Виджет комментариев (TelegramDiscussionWidget) </h3><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { TelegramDiscussionWidget } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@baranov-guru/react-telegram-widgets"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> App</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">div</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">h1</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>Discussion&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">h1</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">TelegramDiscussionWidget</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">        discussion</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"baranov_guru"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">        commentsLimit</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">10</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">        height</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">400</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">        color</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"#ff0000"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">        colorful</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">true</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">        dark</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">true</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">        onLoad</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{() </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Comments loaded!"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">        onError</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{(error) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.error</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Failed to load comments:"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> error)}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">div</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Этот компонент можно встроить под статьёй или любым другим контентом. Все комментарии будут сохраняться в Telegram и синхронизироваться между пользователями.</p><p>Комментарии к посту доступны в Telegram.</p><h3 id="post-widget">Встраивание поста (TelegramPostWidget) </h3><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { TelegramPostWidget } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@baranov-guru/react-telegram-widgets"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> App</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">TelegramPostWidget</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    post</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"baranov_guru/61"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    userpic</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    onLoad</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{() </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Post loaded!"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    onError</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{(error) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.error</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Failed to load post:"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> error)}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    className</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"w-full flex p-2"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span></code></pre><p>Просто указываете <strong>название канала и ID поста</strong> — и он появляется на странице.</p><p><a href="https://t.me/baranov_guru/62">Открыть пост в Telegram</a></p><h3 id="share-widget">Кнопка «Поделиться» (TelegramShareWidget) </h3><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { TelegramShareWidget } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@baranov-guru/react-telegram-widgets"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ShareSection</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">TelegramShareWidget</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      url</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://example.com/article"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      comment</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Check out this article!"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      size</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"large"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Нужны абсолютный <strong>url</strong> и при желании <strong>comment</strong> и <strong>size</strong> (<code>large</code> | <code>medium</code> | <code>small</code>).</p><h3 id="login-widget">Вход через Telegram (TelegramLoginWidget) </h3><p>Актуальный способ — <a href="https://core.telegram.org/widgets/login">OpenID Connect login</a>. Зарегистрируйте origin сайта в @BotFather (Login Widget → Allowed URLs) и используйте числовой <strong>Client ID</strong>.</p><p><code>telegram-login.js</code> открывает попап. Если сайт отдаёт заголовок <code>Cross-Origin-Opener-Policy: same-origin</code>, логин не сработает — нужен <code>same-origin-allow-popups</code> (или без этого заголовка).</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  TelegramLoginWidget</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  TelegramLoginSuccess</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">} </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@baranov-guru/react-telegram-widgets"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> LoginPage</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> handleAuth</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (result</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TelegramLoginSuccess</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">    // Отправьте id_token на бэкенд и проверьте его там.</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> fetch</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"/api/auth/telegram"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      method</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "POST"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      headers</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { </span><span style="color:#22863A;--shiki-dark:#FFAB70">"Content-Type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "application/json"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      body</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> JSON</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.stringify</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({ id_token</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> result</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.id_token })</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    });</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">TelegramLoginWidget</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      clientId</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">123456789</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      scope</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{[</span><span style="color:#22863A;--shiki-dark:#FFAB70">"profile"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "write"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      onAuth</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{handleAuth}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      onError</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{(error) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.error</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Login failed:"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> error)}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Без готовой кнопки — хук <code>useTelegramLogin</code>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { useTelegramLogin } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@baranov-guru/react-telegram-widgets"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> CustomLoginButton</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { </span><span style="color:#1976D2;--shiki-dark:#79B8FF">login</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> isReady</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> isLoading</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> error</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> } </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> useTelegramLogin</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    clientId</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 123456789</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    scope</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#22863A;--shiki-dark:#FFAB70">"profile"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    onAuth</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (result) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">result</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.id_token</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> result</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.user)</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">button</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> type</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"button"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> disabled</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#D32F2F;--shiki-dark:#F97583">!</span><span style="color:#24292EFF;--shiki-dark:#B392F0">isReady </span><span style="color:#D32F2F;--shiki-dark:#F97583">||</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> isLoading} </span><span style="color:#6F42C1;--shiki-dark:#B392F0">onClick</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{() </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#6F42C1;--shiki-dark:#B392F0"> login</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()}></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      Continue with Telegram</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">button</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p><code>id_token</code> всегда проверяйте на сервере. Хелперов для верификации в пакете нет — примеры проверки есть в <a href="https://www.npmjs.com/package/@baranov-guru/react-telegram-widgets">README на npm</a> и в <a href="https://github.com/baranov-guru/react-telegram-widgets">репозитории</a>.</p><h3 id="legacy-login-widget">Legacy Login Widget (TelegramLegacyLoginWidget) </h3><p>Старый <a href="https://core.telegram.org/widgets/login-legacy">iframe Login Widget</a> с полем <code>hash</code>. Для новых интеграций лучше <code>TelegramLoginWidget</code>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  TelegramLegacyLoginWidget</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  TelegramLegacyLoginData</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">} </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@baranov-guru/react-telegram-widgets"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> LegacyLoginPage</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> handleAuth</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (user</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TelegramLegacyLoginData</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> fetch</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"/api/auth/telegram-legacy"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      method</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "POST"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      headers</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { </span><span style="color:#22863A;--shiki-dark:#FFAB70">"Content-Type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "application/json"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      body</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> JSON</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.stringify</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(user)</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    });</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">TelegramLegacyLoginWidget</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      botName</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"SampleBot"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      size</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"large"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      radius</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">8</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      requestAccess</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">true</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      onAuth</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{handleAuth}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      onError</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{(error) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.error</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Login widget error:"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> error)}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Вместо (или вместе с) <code>onAuth</code> можно задать <code>authUrl</code> для редиректа после входа.</p><h2 id="doc">Документация </h2><p>Все доступные пропсы и примеры есть в <a href="https://www.npmjs.com/package/@baranov-guru/react-telegram-widgets">npm-профиле пакета</a>. Там всё кратко и по делу.</p><h2>Поддержите проект ⭐️</h2><p>Если вам пригодился этот пакет — будем рады, если поставите ему звёздочку на <a href="https://github.com/baranov-guru/react-telegram-widgets">GitHub</a>. Это поможет другим разработчикам найти его.</p><div><p><img src="https://baranov.guru/assets/posts/react-telegram-widgets/cover.webp" alt="@baranov-guru/react-telegram-widgets"></p><p><a href="https://github.com/baranov-guru/react-telegram-widgets">@baranov-guru/react-telegram-widgets</a> — React components for embedding Telegram posts, comments, login, and share widgets in your web applications.</p></div></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/react-telegram-widgets/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Новые требования по работе с персональными данными]]></title>
            <link>https://baranov.guru/posts/personal-data-law-changes/</link>
            <guid>https://baranov.guru/posts/personal-data-law-changes</guid>
            <pubDate>Fri, 11 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Обзор новых требований к обработке персональных данных для владельцев сайтов и чат-ботов и чек-лист для самопроверки.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note">⚠️ Информация, представленная в статье, носит исключительно информационный характер и не является юридической консультацией. Автор не несёт ответственности за точность, актуальность или полноту предоставленных данных. Для получения профессиональной юридической помощи рекомендуется обратиться к квалифицированному юристу.</aside><p>С <strong>30 мая 2025 года</strong> правила работы с персональными данными в России стали строже. Для владельцев сайтов, интернет-магазинов, Telegram-ботов и небольших онлайн-сервисов это не формальность «где-то для крупных компаний», а вполне практическая история: собираете имя, телефон, email, cookie или заявки — значит, уже работаете с персональными данными.</p><p>В этой статье разберём, что изменилось, кто считается оператором персональных данных и что стоит проверить у себя на сайте или в боте, чтобы не попасть на штрафы из-за базовых ошибок.</p><hr><h2 id="review">Обзор новых законов и штрафов </h2><p>Поправки к законам № 420‑ФЗ и № 421‑ФЗ усилили ответственность за нарушения при обработке персональных данных. Если коротко, государство стало внимательнее смотреть не только на утечки, но и на то, как бизнес получает согласия, хранит данные и уведомляет пользователей.</p><p>Вот на что стоит обратить внимание в первую очередь:</p><ul><li><strong>Штрафы от 100 000 до 300 000 ₽</strong> — за отсутствие регистрации в Роскомнадзоре как оператора персональных данных.</li><li><strong>Оборотные штрафы от 1 до 3 млн ₽</strong> — за утечку данных, даже если технически ошибся подрядчик.</li><li><strong>Уголовная ответственность</strong> — при умышленных или массовых нарушениях, например при продаже баз.</li><li><strong>Штрафы для юрлиц и ИП</strong> — до 5 млн ₽ за отсутствие согласия, нарушение правил хранения или несоблюдение сроков обработки.</li></ul><p>Главный вывод простой: если раньше многие относились к политике конфиденциальности и чекбоксам как к «бумажке для галочки», теперь лучше привести всё в порядок заранее.</p><hr><h2 id="who">Кто считается оператором персональных данных? </h2><p>Оператор персональных данных — это не обязательно большая компания с юридическим отделом. В большинстве случаев оператором становится тот, кто сам решает, какие данные собирать, зачем они нужны и как потом будут использоваться.</p><p>Например, вы попадаете в эту категорию, если:</p><ul><li>принимаете заявки через форму на сайте;</li><li>собираете комментарии, отзывы или регистрации;</li><li>принимаете оплату онлайн;</li><li>ведёте email- или мессенджер-рассылку;</li><li>подключили CRM, чат-бота или сервис аналитики.</li></ul><p>То есть под требования попадают не только интернет-магазины и сервисы, но и ИП, самозанятые, эксперты, авторы ботов, владельцы лендингов и небольших корпоративных сайтов.</p><p>Даже если вы собираете «только email для связи», это всё равно персональные данные. А значит, нужны понятные правила: где пользователь дал согласие, на что именно он согласился и как вы можете подтвердить этот факт.</p><hr><h2 id="site-actions">Что нужно сделать владельцам сайтов? </h2><h3>1. Уведомить Роскомнадзор</h3><p>Если сайт собирает имя, телефон, email, cookies или другие данные пользователя, обычно нужно <a href="https://pd.rkn.gov.ru/operators-registry/notification/form/">уведомить Роскомнадзор и зарегистрироваться как оператор ПДн</a>.</p><p>Это не тот пункт, который стоит откладывать «до первого серьёзного проекта». Даже простая форма обратной связи может считаться обработкой персональных данных.</p><h3>2. Разместить политику конфиденциальности</h3><p>На сайте должна быть понятная и доступная <strong>политика обработки персональных данных</strong>. Лучше не прятать её в глубине сайта: ссылка обычно размещается в футере, рядом с формами и в местах, где пользователь оставляет данные.</p><p>В политике стоит описать:</p><ul><li>кто является оператором и как с ним связаться;</li><li>какие данные собираются;</li><li>зачем они нужны;</li><li>как и где они обрабатываются;</li><li>сколько времени хранятся;</li><li>какие права есть у пользователя;</li><li>как отозвать согласие.</li></ul><p>Хорошая политика — это не набор сложных юридических фраз, а документ, по которому обычный человек понимает, что происходит с его данными.</p><h3>3. Получить согласие до отправки формы</h3><p>Согласие нужно получать до того, как пользователь отправил данные. То есть сначала человек должен увидеть текст согласия или ссылку на него, а уже потом нажать кнопку отправки.</p><p>На практике это обычно выглядит так:</p><ul><li>рядом с формой есть чекбокс или кнопка подтверждения;</li><li>без согласия форма не отправляется;</li><li>фиксируется дата, IP и версия текста, с которым согласился пользователь.</li></ul><p>Важно, чтобы согласие было <strong>свободным, конкретным, информированным и осознанным</strong>. Проще говоря: пользователь должен понимать, кому, какие данные и для чего он передаёт.</p><h3>4. Настроить уведомление о cookie и сторонних скриптах</h3><p>Если на сайте стоит Яндекс.Метрика, Google Analytics, пиксели рекламных систем или другие сторонние скрипты, это тоже зона внимания.</p><p>Пользователя нужно уведомить, что сайт использует cookies и похожие технологии, а также дать ему возможность принять или отклонить такие файлы.</p><p>Минимальный набор:</p><ul><li>баннер при первом посещении сайта;</li><li>понятный текст о целях использования cookies;</li><li>кнопки выбора, а не только «ОК, закрыть»;</li><li>ссылка на политику или отдельное описание cookie.</li></ul><hr><h2 id="bot-actions">Как собирать данные в чат-ботах </h2><p>С чат-ботами ситуация часто выглядит неочевидно. Кажется, что пользователь сам написал в Telegram или WhatsApp, значит, всё нормально. Но если бот просит имя, телефон, email, город, должность или принимает заявку — это тоже обработка персональных данных.</p><p>Если используете бота для заявок, консультаций, рассылок или поддержки, проверьте несколько вещей.</p><h3>1. Зарегистрируйтесь как оператор ПДн</h3><p>Если бот собирает данные пользователей, одного факта «это просто Telegram» недостаточно. Владелец бота всё равно определяет цели обработки и отвечает за то, как данные используются дальше.</p><h3>2. Получите явное согласие</h3><p>Перед тем как просить телефон, имя или другие данные, дайте пользователю понятное действие для согласия. Это может быть кнопка «Согласен», команда вроде <code>/agree</code> или отдельный шаг в сценарии бота.</p><p>Главное — не собирать данные молча и не прятать согласие в длинный текст, который пользователь не видит.</p><h3>3. Дайте ссылку на политику</h3><p>Политика конфиденциальности должна быть доступна прямо из бота: через кнопку, команду, автоответ или сообщение перед сбором данных.</p><p>Пользователь должен иметь возможность открыть документ до того, как передаст информацию.</p><h3>4. Фиксируйте факт согласия</h3><p>Согласие лучше сохранять так, чтобы его можно было подтвердить позже. Обычно фиксируют:</p><ul><li>дату и время;</li><li>ID пользователя;</li><li>текст согласия или его версию;</li><li>действие, которым пользователь подтвердил согласие.</li></ul><p>Это особенно важно, если бот связан с CRM, рассылкой, оплатой или передаёт данные в сторонние сервисы.</p><hr><h2 id="security">Общие требования по безопасности </h2><p>Юридические документы важны, но одних документов недостаточно. Нужно ещё организовать нормальную защиту данных на практике.</p><p>Базовые правила такие:</p><ul><li><strong>Собирайте только то, что действительно нужно.</strong> Если для заявки достаточно имени и телефона, не стоит спрашивать паспортные данные или дату рождения.</li><li><strong>Храните данные безопасно.</strong> Используйте шифрование, сложные пароли, двухфакторную аутентификацию и защищённые каналы доступа.</li><li><strong>Ограничьте доступ.</strong> Данные должны видеть только те сотрудники или подрядчики, которым они реально нужны для работы.</li><li><strong>Удаляйте лишнее и устаревшее.</strong> Если срок обработки прошёл или данные больше не нужны, их не стоит хранить «на всякий случай».</li><li><strong>Оформляйте отношения с подрядчиками.</strong> Если подрядчик получает доступ к данным пользователей, это должно быть закреплено в договоре.</li></ul><p>Отдельно стоит проверить, куда физически уходят данные: в российскую CRM, зарубежное облако, Telegram, Google Таблицы или другой сервис. От этого могут зависеть дополнительные уведомления и ограничения.</p><hr><h2 id="checklist">Чек-лист для владельцев сайтов и чат-ботов </h2><table><thead><tr><th>Действие</th><th>Обязательно?</th><th>Комментарий</th></tr></thead><tbody><tr><td><a href="https://pd.rkn.gov.ru/operators-registry/notification/form/">Регистрация в Роскомнадзоре</a></td><td>✅ Да</td><td>Даже если собираете только email</td></tr><tr><td>Политика конфиденциальности</td><td>✅ Да</td><td>На сайте, в футере, доступна по ссылке из бота</td></tr><tr><td>Согласие пользователя</td><td>✅ Да</td><td>До отправки формы или сообщения</td></tr><tr><td>Уведомление о cookie</td><td>✅ Да</td><td>При первом посещении сайта</td></tr><tr><td>Минимизация данных</td><td>✅ Да</td><td>Не запрашивайте лишнего</td></tr><tr><td>Хранение и защита</td><td>✅ Да</td><td>Пароли, шифрование, логирование доступа</td></tr><tr><td>Обработка через подрядчиков</td><td>⚠️ С осторожностью</td><td>Только по договору, с передачей ответственности</td></tr><tr><td>Трансграничная передача (например, Telegram)</td><td>⚠️ Доп. уведомление в РКН</td><td>Особенно, если сервера за пределами РФ</td></tr></tbody></table><hr><h2 id="summary">Выводы </h2><p>Если упростить всё до практических шагов, владельцу сайта или бота нужно ответить на несколько вопросов:</p><ol><li>Какие данные я собираю?</li><li>Зачем они мне нужны?</li><li>Где пользователь даёт согласие?</li><li>Где это согласие хранится?</li><li>Кто имеет доступ к данным?</li><li>Куда данные передаются дальше?</li></ol><p>После этого уже проще навести порядок: зарегистрироваться как оператор, обновить политику, добавить согласия к формам, настроить cookie-баннер и проверить подрядчиков.</p><p>Персональные данные — это не только про юристов и крупный бизнес. Это часть нормальной цифровой гигиены любого проекта, который принимает заявки, ведёт рассылки, подключает аналитику или использует чат-ботов.</p><p>Лучше один раз спокойно настроить сайт и бота, чем потом разбираться с претензиями, утечками и штрафами.</p><p>На этом всё. Спасибо за внимание!</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/personal-data-law-changes/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Брендированные типы (branded types) в TypeScript]]></title>
            <link>https://baranov.guru/posts/typescript-branded-types/</link>
            <guid>https://baranov.guru/posts/typescript-branded-types</guid>
            <pubDate>Tue, 08 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Что такое branded types в Typescript и как с их помощью улучшить type safety в рантайме.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2025 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><h2 id="what">Что такое брендированные типы в TypeScript? </h2><p><strong>Брендированные типы</strong> — это паттерн в TypeScript, позволяющий повысить безопасность типов, добавляя <em>уникальные метки</em> к существующим типам (например, <code>string</code> или <code>number</code>).</p><p>Это особенно полезно, когда несколько значений имеют одинаковый базовый тип, но несут разную семантическую нагрузку — например, <code>UserId</code>, <code>PostId</code>, <code>OrderId</code>.</p><p>Несмотря на то что на уровне выполнения это всё те же строки или числа, компилятор TypeScript воспринимает их как разные типы. Это предотвращает случайную подстановку одного значения вместо другого.</p><h2>Зачем нужны брендированные типы?</h2><p>TypeScript обеспечивает <em>структурную типизацию</em> — тип считается корректным, если у него есть нужные поля, независимо от его имени. Это может привести к ошибкам, когда два значения совместимы по структуре, но не по смыслу. Например:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> User</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { id</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Post</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { id</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ownerId</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getPostByUser</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(userId</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> postId</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) { </span><span style="color:#D32F2F;--shiki-dark:#F97583">...</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">// вызов</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">getPostByUser</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.id</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> user</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.id) </span><span style="color:#C2C3C5;--shiki-dark:#6B737C">// ❌ ошибка смысла, но TypeScript не ругается</span></span></code></pre><p>С брендированными типами TypeScript «поймёт» ошибку на этапе компиляции:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> UserId</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> &#x26;</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { __brand</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'UserId'</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> PostId</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> &#x26;</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { __brand</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'PostId'</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getPostByUser</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(userId</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> UserId</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> postId</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> PostId</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) { </span><span style="color:#D32F2F;--shiki-dark:#F97583">...</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">getPostByUser</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.id</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> user</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.id) </span><span style="color:#C2C3C5;--shiki-dark:#6B737C">// ❌ ошибка типов: PostId ≠ UserId</span></span></code></pre><h2 id="how">Как создать брендированный тип? </h2><p>Существует несколько подходов. Простейший — через пересечение типов с объектом-брендом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Brand</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">K</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> T</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> K</span><span style="color:#D32F2F;--shiki-dark:#F97583"> &#x26;</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { __brand</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> T</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> UserId</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Brand</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "UserId"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ProductId</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Brand</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ProductId"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>;</span></span></code></pre><p>Чтобы избежать дублирования имён и скрыть бренд от автодополнения в редакторе, можно использовать <code>unique symbol</code>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">declare</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> __brand</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> unique</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> symbol</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Brand</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> B</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> T</span><span style="color:#D32F2F;--shiki-dark:#F97583"> &#x26;</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { [__brand]</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> B</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> UserId</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Brand</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "UserId"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>;</span></span></code></pre><h2 id="pros">Преимущества брендированных типов </h2><ul><li>✅ <strong>Повышенная безопасность типов (type safety)</strong>: предотвращают подстановку несовместимых значений.</li><li>📖 <strong>Ясность кода</strong>: код становится самодокументируемым — <code>UserId</code>, <code>OrderId</code> говорят сами за себя, упрощают понимание, поддержку и рефакторинг кода</li><li>🧪 <strong>Простота тестирования и валидации</strong>: можно создавать отдельные функции-валидаторы, возвращающие брендированный тип только при успешной проверке.</li></ul><h2 id="cons">Ограничения </h2><ul><li>🧱 <strong>Бренды — это compile-time метки</strong>: во время выполнения они не существуют.</li><li>💡 <strong>Можно случайно обойти типизацию</strong>, используя <code>as</code> без валидации — будьте осторожны.</li><li>🧠 <strong>Требуется внимание разработчиков</strong>: особенно в больших командах важно соблюдать договорённости при создании брендированных типов.</li></ul><h2 id="use-cases">Применение на практике </h2><h3>1. Разделение ID-шников</h3><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> UserId</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Brand</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'UserId'</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> PostId</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Brand</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'PostId'</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getComments</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(postId</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> PostId</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> authorId</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> UserId</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) { </span><span style="color:#D32F2F;--shiki-dark:#F97583">...</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">getComments</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">user</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.id</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.id) </span><span style="color:#C2C3C5;--shiki-dark:#6B737C">// ❌ TypeScript выдаст ошибку</span></span></code></pre><h3>2. Валидация пользовательского ввода</h3><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> EmailAddress</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Brand</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "EmailAddress"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> validateEmail</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(input</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> EmailAddress</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // Не валидируйте так email! Этот код написан для примера.</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">!</span><span style="color:#1976D2;--shiki-dark:#79B8FF">input</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.includes</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"@"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)) </span><span style="color:#D32F2F;--shiki-dark:#F97583">throw</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Error</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Invalid email"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> input </span><span style="color:#D32F2F;--shiki-dark:#F97583">as</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> EmailAddress</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h3>3. Работа с числовыми значениями, например, возрастом</h3><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Age</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Brand</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">number</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Age"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> createAge</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(age</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Age</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (age </span><span style="color:#D32F2F;--shiki-dark:#F97583">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#D32F2F;--shiki-dark:#F97583"> ||</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> age </span><span style="color:#D32F2F;--shiki-dark:#F97583">></span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 125</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    throw</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Error</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Возраст вне допустимого диапазона"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> age </span><span style="color:#D32F2F;--shiki-dark:#F97583">as</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Age</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getBirthYear</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(age</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Age</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.getFullYear</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() </span><span style="color:#D32F2F;--shiki-dark:#F97583">-</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> age;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> myAge</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> createAge</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">30</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> birthYear</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getBirthYear</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(myAge);</span></span></code></pre><h2>Заключение</h2><p><strong>Branded Types</strong> — это мощный приём TypeScript, позволяющий повысить читаемость, надёжность и безопасность кода. Несмотря на то, что они не влияют на выполнение, их сила в compile-time проверках, помогающих предотвратить трудноуловимые ошибки.</p><p>Используйте <strong>брендированные типы</strong> там, где одинаковые по структуре значения имеют разное семантическое значение. Это особенно важно в больших приложениях, API-интеграциях и при валидации пользовательского ввода.</p><p>На этом всё. Спасибо за внимание!</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/typescript-branded-types/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем Not Found (404) страницу в Next.js приложение]]></title>
            <link>https://baranov.guru/posts/nextjs-not-found/</link>
            <guid>https://baranov.guru/posts/nextjs-not-found</guid>
            <pubDate>Wed, 26 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению Not Found (404) страницы в Next.js приложение.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2025 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Важным элементом <strong>SEO-оптимизации</strong> является правильная работа с ошибками. В этой статье вы узнаете, что такое <strong>ошибка 404</strong>, и как правильно работать с ней в приложении на <strong>Next.js</strong>.</p><h2 id="what">Что такое Not Found или 404 страница? </h2><p>Ошибка <strong>404</strong>, также известная как «<strong>Not Found</strong>» или «<strong>Не найдено</strong>», является стандартным <strong>HTTP</strong>-статусным кодом, который указывает на отсутствие запрашиваемого ресурса на сервере.</p><h2 id="file">Файл not-found.js | not-found.ts </h2><p>В <strong>Next.js</strong>, начиная с <strong>13</strong> версии, для работы с отображением <strong>404</strong> ошибки используется файл <strong>not-found.js</strong>.</p><p>Вот так может выглядеть код файла:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> Link </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next/link"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> NotFound</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">div</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">h2</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>Не найдено&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">h2</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">p</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>Запрашиваемый ресурс не доступен&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">p</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Link</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> href</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>Вернуться на главную&#x3C;/</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Link</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">div</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Для отображения <strong>404</strong> ошибки необходимо поместить данный файл в корень директории <strong>app/</strong>,</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>app/not-found.tsx</span></span></code></pre><p>страница будет отображаться для <strong>всех</strong> ненайденных <strong>URL</strong> приложения.</p><p>По умолчанию, <strong>NotFound</strong> - серверный компонент, и если необходимо получить какие-то данные перед отрисовкой, то необходимо сделать его асинхронным (<em>async</em>).</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> Link </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'next/link'</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { headers } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> 'next/headers'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> NotFound</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> authorization</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> headers</span><span style="color:#24292EFF;--shiki-dark:#B392F0">())</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.get</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">'authorization'</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> res</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> fetch</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">'...'</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    headers</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { authorization }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  })</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> user</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> res</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.json</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">     &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">div</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">h2</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>Ничего не найдено, {</span><span style="color:#1976D2;--shiki-dark:#79B8FF">user</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.name}.&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">h2</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">p</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>Но ты можешь попробовать снова, вдруг это просто ошибка?&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">p</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">div</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  )</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Если же необходимо получить какие-то данные уже на клиенте, то можно вынести часть содержимого страницы в <strong>клиентский компонент</strong>, и запросить данные из него.</p><p>Подробную информацию можно найти в <a href="https://nextjs.org/docs/app/api-reference/file-conventions/not-found">официальной документации по странице</a>.</p><h2 id="function">Функция notFound() </h2><p>Так же отрисовку файла not-found.js можно вызвать принудительно с помощью функции <strong>notFound()</strong>.</p><p>Примерно вот так:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { notFound } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next/navigation"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({ params }</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Params</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getPostBySlug</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">params</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">!</span><span style="color:#24292EFF;--shiki-dark:#B392F0">post) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> notFound</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  ...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Эта функция позволяет кинуть ошибку <strong>NEXT_NOT_FOUND</strong>, а затем вызвать рендер страницы <strong>not-found.js</strong> в пределах сегмента пути и отдать <strong>404 код ошибки HTTP</strong>.</p><p>В дополнение к этому, функция вставит на страницу <strong>мета-тег robots</strong>, при её использовании в серверном компоненте.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">meta</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"robots"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"noindex"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>Функцию <strong>notFound()</strong> можно использовать в серверных компонентах (<strong>Server Components</strong>), обработчиках пути (<strong>Route Handlers</strong>) и серверных действиях (<strong>Server Actions</strong>).</p><p>Подробную информацию можно найти в <a href="https://nextjs.org/docs/app/api-reference/functions/not-found">официальной документации по функции</a>.</p><p>На этом всё! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/nextjs-not-found/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем sitemap.xml в Next.js приложение]]></title>
            <link>https://baranov.guru/posts/nextjs-sitemap/</link>
            <guid>https://baranov.guru/posts/nextjs-sitemap</guid>
            <pubDate>Fri, 14 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению sitemap.xml в Next.js приложение.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2025 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Важным элементом <strong>SEO-оптимизации</strong> является наличие файла <strong>sitemap.xml</strong>. В этой статье вы узнаете, что такое <strong>sitemap.xml</strong> и как добавить его в свое приложение на <strong>Next.js</strong>.</p><p>Если вы разбираетесь не с реализацией в коде, а с тем, <strong>зачем sitemap.xml нужен бизнесу</strong>, какие страницы стоит включать в карту сайта и какие ошибки мешают индексации, начните с общей статьи <a href="https://baranov.guru/posts/sitemap-xml/">Sitemap.xml для сайта: что это, зачем нужен и как не допустить ошибок</a>. А ниже — уже практическая инструкция именно для проектов на <strong>Next.js</strong>.</p><h2 id="what">Что такое sitemap.xml? </h2><p><strong>Sitemap.xml</strong> - это файл формата <a href="https://www.sitemaps.org/protocol.html">Sitemaps XML format</a>, который сообщает поисковым системам (например <strong>Google</strong> или <strong>Yandex</strong>), какие страницы есть на вашем сайте и как часто они обновляются.</p><p>Этот файл должен находиться в корневой папке вашего сайта, и обычно доступен по адресу:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>http(s)://домен.зона/sitemap.xml</span></span></code></pre><p>Например, <strong>sitemap.xml</strong> этого блога лежит по адресу <a href="https://baranov.guru/sitemap.xml">https://baranov.guru/sitemap.xml</a> и выглядит следующим образом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-xml"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> xmlns</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"http://www.sitemaps.org/schemas/sitemap/0.9"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://baranov.guru/&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>2025-03-13T12:07:00.337Z&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>daily&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>0.7&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ...</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><p>Более подробную информацию о том как как поисковые системы обрабатывают файлы <strong>sitemap.xml</strong> можно найти в <a href="https://yandex.ru/support/webmaster/controlling-robot/sitemap.html">справке от Яндекса</a> или <a href="https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview">документации Google</a>.</p><h2 id="static">Первый способ: Добавляем статический файл </h2><p>Первый способ - очень простой. <strong>Next.js</strong> из коробки умеет отдавать статические файлы, например изображения, добавленные в папку <strong>public</strong> в корне проекта.</p><p>Этот же подход можно использовать для добавления файла <strong>sitemap.xml</strong>. Всё что необходимо сделать - добавить файл в папку <strong>public</strong>.</p><p>Подробнее про работу со статикой и папку <strong>public</strong> можно почитать в <a href="https://nextjs.org/docs/app/building-your-application/optimizing/static-assets">официальной документации Next.js</a>.</p><p>Как видите, способ действительно очень простой, но подойдёт он вам только в случае, если у вас небольшой сайт, с редко меняющимся содержимым. В противном случае лучше прибегнуть к <a href="#dynamic">динамической генерации sitemap.xml</a>.</p><h3 id="alternate">Альтернативный способ добавления статического файла </h3><p>Начиная с <a href="https://github.com/vercel/next.js/releases/tag/v13.3.0">версии 13.3</a> <strong>Next.js</strong> умеет корректно обрабатывать статический файл <strong>sitemap.xml</strong> лежащий в корне папки <strong>app</strong>.</p><p>Подробнее в <a href="https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap">документации</a>.</p><h2 id="dynamic">Второй способ: Динамическая генерация sitemap.xml </h2><p>Для того чтобы динамически создавать файл <strong>sitemap.xml</strong> необходимо в папку <strong>app</strong> добавить файл <strong>robots.js</strong> или <strong>robots.ts</strong>, который будет возвращать объект типа <a href="#type"><strong>MetadataRoute.Sitemap</strong></a>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { MetadataRoute } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { URL_BASE } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@/lib/constants"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MetadataRoute</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      lastModified</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      changeFrequency</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "daily"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      priority</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.7</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/about/`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      lastModified</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      changeFrequency</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "daily"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      priority</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.7</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/posts/`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      lastModified</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      changeFrequency</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "daily"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      priority</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.7</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Данный код сгенерирует файл со следующим содержимым:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-xml"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> xmlns</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"http://www.sitemaps.org/schemas/sitemap/0.9"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://baranov.guru/&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>2025-03-13T12:07:00.337Z&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>daily&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>0.7&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://baranov.guru/about/&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>2025-03-13T12:07:00.337Z&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>daily&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>0.7&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://baranov.guru/posts/&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>2025-03-13T12:07:00.337Z&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>daily&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>0.7&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><h3 id="image">Генерация Image Sitemaps </h3><p>Так же, начиная с <strong>15</strong> версии в <strong>Next.js</strong> есть встроенная возможность генерировать <a href="https://developers.google.com/search/docs/crawling-indexing/sitemaps/image-sitemaps"><strong>Image Sitemaps</strong></a>. Для этого надо всего лишь добавить свойство <strong>images</strong> (см. <a href="#type">спецификацию</a>) к странице.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { MetadataRoute } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { URL_BASE } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@/lib/constants"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MetadataRoute</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      lastModified</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      changeFrequency</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "daily"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      priority</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.7</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      images</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://baranov.guru/assets/authors/avatar.webp"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>В результате получим следующий файл:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-xml"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  xmlns</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"http://www.sitemaps.org/schemas/sitemap/0.9"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  xmlns</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0">image</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"http://www.google.com/schemas/sitemap-image/1.1"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://baranov.guru/&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">image</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">image</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">image</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://baranov.guru/assets/authors/avatar.webp&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">image</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">image</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">image</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>2025-03-13T12:07:00.337Z&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>daily&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>0.7&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><h3 id="video">Генерация Video Sitemaps </h3><p>Так же, начиная с <strong>15</strong> версии в <strong>Next.js</strong> есть встроенная возможность генерировать <a href="https://developers.google.com/search/docs/crawling-indexing/sitemaps/video-sitemaps"><strong>Video Sitemaps</strong></a>. Для этого надо всего лишь добавить свойство <strong>videos</strong> (см. <a href="#type">спецификацию</a>) к странице.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { MetadataRoute } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { URL_BASE } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@/lib/constants"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MetadataRoute</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/posts/yandex-gpt-intro/`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      lastModified</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      changeFrequency</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "daily"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      priority</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.7</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      videos</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Название видео"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          thumbnail_loc</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">            "https://baranov.guru/assets/posts/yandex-gpt-intro/cover.webp"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Это описание видео"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      ]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>В результате получим следующий файл:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-xml"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  xmlns</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"http://www.sitemaps.org/schemas/sitemap/0.9"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  xmlns</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0">video</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"http://www.google.com/schemas/sitemap-video/1.1"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://baranov.guru/posts/yandex-gpt-intro/&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">video</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">video</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">video</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">title</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>Название видео&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">video</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">title</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">video</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">thumbnail_loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://baranov.guru/assets/posts/yandex-gpt-intro/cover.webp&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">video</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">thumbnail_loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">video</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">description</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>Это описание видео&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">video</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">description</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">video</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">video</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>2025-03-13T12:07:00.337Z&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>daily&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>0.7&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><h3 id="locale">Генерация локализованных Sitemaps </h3><p>В <strong>Next.js</strong> есть встроенная возможность добавлять ссылки на версии страниц на другом языке при генерации <strong>sitemap.xml</strong>. Для этого надо всего лишь добавить свойство <strong>alternates</strong> (см. <a href="#type">спецификацию</a>) к странице.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { MetadataRoute } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { URL_BASE } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@/lib/constants"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MetadataRoute</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      lastModified</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      changeFrequency</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "daily"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      priority</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.7</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      alternates</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        languages</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          en</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/en/`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          fr</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/fr/`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>В результате получим следующий файл:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-xml"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  xmlns</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"http://www.sitemaps.org/schemas/sitemap/0.9"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  xmlns</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0">xhtml</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"http://www.w3.org/1999/xhtml"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>https://baranov.guru/&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">loc</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">xhtml</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">link</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      rel</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"alternate"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      hreflang</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"en"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      href</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://baranov.guru/en/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">/></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">xhtml</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70">link</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      rel</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"alternate"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      hreflang</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"fr"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      href</span><span style="color:#24292EFF;--shiki-dark:#B392F0">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://baranov.guru/fr/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">/></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>2025-03-13T12:07:00.337Z&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">lastmod</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>daily&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">changefreq</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>0.7&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">priority</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">url</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">urlset</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><h3 id="type">Спецификация MetadataRoute.Sitemap </h3><p>Вот полная спецификация MetadataRoute.Sitemap, взятая из <a href="https://github.com/vercel/next.js">официального репозитория на Github</a>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Sitemap</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Array</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;{</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  lastModified</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  changeFrequency</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "always"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "hourly"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "daily"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "weekly"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "monthly"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "yearly"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "never"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  priority</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  alternates</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        languages</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Languages</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">|</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  images</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[] </span><span style="color:#D32F2F;--shiki-dark:#F97583">|</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  videos</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Videos</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[] </span><span style="color:#D32F2F;--shiki-dark:#F97583">|</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}>;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Videos</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  thumbnail_loc</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  content_loc</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  player_loc</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  duration</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  expiration_date</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  rating</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  view_count</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  publication_date</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  family_friendly</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "yes"</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "no"</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  restriction</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Restriction</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  platform</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Restriction</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  requires_subscription</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "yes"</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "no"</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  uploader</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        info</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        content</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  live</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "yes"</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "no"</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  tag</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> undefined</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><h2 id="packages">Альтернативные решения </h2><p>Так же есть несколько альтернативных решений для генерации <strong>sitemap.xml</strong> в виде npm-пакетов. Например пакет <a href="https://www.npmjs.com/package/next-sitemap">next-sitemap</a>.</p><h2 id="validation">Бонус: Валидация sitemap.xml </h2><p>Для того чтобы удостовериться что содержимое <strong>sitemap.xml</strong> валидно и будет работать как и задумано, можно воспользоваться несколькими <strong>бесплатными</strong> инструментами:</p><div><p><img src="https://baranov.guru/assets/tools/sitemap-validator/cover.webp" alt="Валидатор Sitemap.xml"></p><p><a href="https://baranov.guru/tools/sitemap-validator/">Валидатор Sitemap.xml</a> — Проверка структуры XML sitemap: urlset/sitemapindex, loc/lastmod/changefreq/priority, типовые ошибки и замечания — онлайн.</p></div><ul><li><a href="https://search.google.com/search-console/sitemaps">Валидатор от Google</a>;</li><li><a href="https://webmaster.yandex.ru/tools/sitemap/">Валидатор от Yandex</a>;</li></ul><p>На этом всё! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/nextjs-sitemap/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем robots.txt в Next.js приложение]]></title>
            <link>https://baranov.guru/posts/nextjs-robots/</link>
            <guid>https://baranov.guru/posts/nextjs-robots</guid>
            <pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению robots.txt в Next.js приложение.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2025 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Важным элементом <strong>SEO-оптимизации</strong> является наличие файла <strong>robots.txt</strong>. В этой статье вы узнаете, что такое <strong>robots.txt</strong> и как добавить его в свое приложение на <strong>Next.js</strong>.</p><h2 id="what">Что такое robots.txt? </h2><p><strong>Robots.txt</strong> - это текстовый файл, основанный на спецификации <a href="https://www.rfc-editor.org/rfc/rfc9309.html">Robots Exclusion Protocol</a>, который сообщает поисковым роботам (например <strong>Googlebot</strong> или <strong>Yandex</strong>), какие страницы они могут сканировать, а какие нет.</p><p>А так же указывает на файл <strong>sitemap.xml</strong>. О том что это такое и как добавить файл <strong>sitemap.xml</strong> читайте в статье «<a href="https://baranov.guru/posts/nextjs-sitemap/">Добавляем sitemap.xml в Next.js приложение</a>».</p><p><strong>Robots.txt</strong> должен находиться в корневой папке вашего сайта, и обычно доступен по адресу:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>домен.зона/robots.txt</span></span></code></pre><p>Например, у этого блога он лежит по адресу <a href="https://baranov.guru/robots.txt">https://baranov.guru/robots.txt</a> и выглядит следующим образом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span># *</span></span>
<span class="line"><span>User-agent: *</span></span>
<span class="line"><span>Allow: /</span></span>
<span class="line"><span></span></span>
<span class="line"><span># Host</span></span>
<span class="line"><span>Host: https://baranov.guru/</span></span>
<span class="line"><span></span></span>
<span class="line"><span># Sitemap</span></span>
<span class="line"><span>Sitemap: https://baranov.guru/sitemap.xml</span></span></code></pre><p>Более подробную информацию о том как как поисковые системы обрабатывают файлы <strong>robots.txt</strong> можно найти в <a href="https://yandex.ru/support/webmaster/controlling-robot/robots-txt.html">справке от Яндекса</a> или <a href="https://developers.google.com/search/docs/crawling-indexing/robots/robots_txt">документации Google</a>.</p><h2 id="static">Первый способ: Добавляем статический файл </h2><p>Первый способ - очень простой. <strong>Next.js</strong> из коробки умеет отдавать статические файлы, например изображения, добавленные в папку <strong>public</strong> в корне проекта.</p><p>Этот же подход можно использовать для добавления файла <strong>robots.txt</strong>. Всё что необходимо сделать - добавить файл в папку <strong>public</strong>.</p><p>Подробнее про работу со статикой и папку <strong>public</strong> можно почитать в <a href="https://nextjs.org/docs/app/building-your-application/optimizing/static-assets">официальной документации Next.js</a>.</p><h3 id="alternate">Альтернативный способ добавления статического файла </h3><p>Начиная с <a href="https://github.com/vercel/next.js/releases/tag/v13.3.0">версии 13.3</a> <strong>Next.js</strong> умеет корректно обрабатывать статический файл <strong>robots.txt</strong> лежащий в корне папки <strong>app</strong>.</p><p>Подробнее в <a href="https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots#static-robotstxt">документации</a>.</p><h2 id="dynamic">Второй способ: Динамическая генерация robots.txt </h2><p>Для того чтобы динамически создавать файл <strong>robots.txt</strong> необходимо в папку <strong>app</strong> добавить файл <strong>robots.js</strong> или <strong>robots.ts</strong>, который будет возвращать объект типа <a href="#type"><strong>MetadataRoute.Robots</strong></a>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#D32F2F;--shiki-dark:#F97583"> type</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { MetadataRoute } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> robots</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MetadataRoute</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Robots</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    rules</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      userAgent</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "*"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      allow</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "/"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      disallow</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "/private/"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    sitemap</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/sitemap.xml"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    host</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Данный код сгенерирует файл со следующим содержимым:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>User-Agent: *</span></span>
<span class="line"><span>Allow: /</span></span>
<span class="line"><span>Disallow: /private/</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Host: https://baranov.guru/</span></span>
<span class="line"><span>Sitemap: https://baranov.guru/sitemap.xml</span></span></code></pre><h3 id="custom">Кастомизация настроек в зависимости от user-agent </h3><p>Так же есть возможность добавлять правила для различных <strong>user-agent</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#D32F2F;--shiki-dark:#F97583"> type</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { MetadataRoute } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> robots</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MetadataRoute</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Robots</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    rules</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        userAgent</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Googlebot"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        allow</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#22863A;--shiki-dark:#FFAB70">"/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        disallow</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "/private/"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        userAgent</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Yandex"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Bingbot"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        disallow</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#22863A;--shiki-dark:#FFAB70">"/"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    ]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    sitemap</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/sitemap.xml"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>На выходе получим следующий файл:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>User-Agent: Googlebot</span></span>
<span class="line"><span>Allow: /</span></span>
<span class="line"><span>Disallow: /private/</span></span>
<span class="line"><span></span></span>
<span class="line"><span>User-Agent: Yandex</span></span>
<span class="line"><span>User-Agent: Bingbot</span></span>
<span class="line"><span>Disallow: /</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Sitemap: https://baranov.guru/sitemap.xml</span></span></code></pre><h3 id="type">Спецификация MetadataRoute.Robots </h3><p>Вот полная спецификация MetadataRoute.Robots, взятая из <a href="https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots#robots-object">официальной документации</a>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Robots</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  rules</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        userAgent</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        allow</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        disallow</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        crawlDelay</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Array</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;{</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        userAgent</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        allow</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        disallow</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        crawlDelay</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }>;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  sitemap</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  host</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><h3 id="dynamic-cons">Минусы динамической генерации и директива Clean-Param </h3><p>К сожалению, динамическая генерация <strong>не поддерживает добавление кастомных директив</strong> в правила. Самым ярким примером кастомной директивы можно считать <a href="https://yandex.ru/support/webmaster/robot-workings/clean-param.html">директиву Clean-Param</a>, поддерживаемую <strong>Яндексом</strong>.</p><p>Для того чтобы её добавить придётся воспользовать либо <a href="#static">первым способом (статический файл)</a> либо каким-нибудь сторонним решением, например <strong>npm-пакетом</strong> <a href="https://www.npmjs.com/package/next-sitemap">next-sitemap</a>.</p><h2 id="validation">Бонус: Валидация robots.txt </h2><p>Для того чтобы удостовериться что содержимое <strong>robots.txt</strong> валидно и будет работать как и задумано, можно воспользоваться несколькими <strong>бесплатными</strong> инструментами:</p><div><p><img src="https://baranov.guru/assets/tools/robots-txt-validator/cover.webp" alt="Валидатор Robots.txt"></p><p><a href="https://baranov.guru/tools/robots-txt-validator/">Валидатор Robots.txt</a> — Проверка синтаксиса и структуры robots.txt: User-agent, Allow, Disallow, Sitemap, директивы для поисковых систем — онлайн.</p></div><ul><li><a href="https://www.google.com/webmasters/tools/robots-testing-tool">Валидатор от Google</a>;</li><li><a href="https://webmaster.yandex.ru/tools/robotstxt/">Валидатор от Yandex</a>;</li></ul><p>На этом всё! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/nextjs-robots/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем кнопку для сбора пожертвований (донатов) к блогу]]></title>
            <link>https://baranov.guru/posts/blog-donation-button/</link>
            <guid>https://baranov.guru/posts/blog-donation-button</guid>
            <pubDate>Wed, 03 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению кнопки для сбора пожертвований (донатов) к блогу.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Сегодня расскажем как добавить <strong>кнопку для сбора пожертвований (донатов)</strong> к блогу или сайту.</p><h2 id="why">Зачем блогеру нужна кнопка "Поддержать" </h2><p>Ведение блога — это не только творчество, но и регулярные усилия: подбор тем, написание, редактирование, поддержка сайта. Монетизация через рекламу не всегда подходит, а донаты позволяют читателю напрямую выразить благодарность. Даже небольшие суммы помогают автору продолжать развивать проект.</p><aside class="post-disclaimer" role="note"><p>ℹ️ По данным Patreon, регулярная поддержка позволяет авторам увеличивать доход в среднем на 25–40 % ежемесячно.</p></aside><h2 id="service">Выбор сервиса для сбора пожертвований </h2><p>Существует множество <strong>сервисов</strong>, которые позволяют собрать <strong>пожертвования (донаты)</strong> с пользователей:</p><p>Быстрый поиск в интернете выдал следующие результаты:</p><ul><li><strong>BuyMeACoffee</strong> - на первый взгляд - то что нужно, но находится за рубежом и не обслуживает клиентов из РФ;</li><li><strong>Ko-fi</strong> - аналогично <strong>BuyMeACoffee</strong>, но ещё говорят и кидает на деньги если ты из РФ;</li><li><a href="https://coindrop.to/">Coindrop</a> - интересный <strong>open-source</strong> сервис, но как мне показалось больше подходит для <strong>крипты</strong>;</li><li><a href="https://yookassa.ru/">ЮKassa</a> - всё хорошо, но не работает с физ. лицами;</li><li><a href="https://yoomoney.ru/">ЮMoney</a> - та же <strong>ЮKassa</strong>, но вид сбоку;</li><li><a href="https://cloudtips.ru/">CloudTips</a> - сервис для чаевых и донатов от <strong>Т-банка</strong>, предназначен именно для физиков и безвозмездных пожертвований.</li></ul><table><thead><tr><th>Сервис</th><th>Комиссия</th><th>Платежные методы</th><th>Особенности</th></tr></thead><tbody><tr><td>BuyMeACoffee</td><td>~5%</td><td>Карты, Apple Pay, PayPal</td><td>Простая настройка, англ. интерфейс</td></tr><tr><td>Ko-fi</td><td>0% (в free), 5% (Gold)</td><td>PayPal</td><td>Простой виджет, поддержка целей</td></tr><tr><td>CloudTips</td><td>5%</td><td>Карты РФ</td><td><strong>Подходит для российских блогеров</strong></td></tr><tr><td>Donorbox</td><td>0–1.5% + Stripe/PayPal</td><td>Stripe, PayPal</td><td>Много кастомизации, подписки</td></tr><tr><td>ЮMoney/ЮKassa</td><td>3–6%</td><td>Карты, СБП</td><td><strong>Легальная интеграция в РФ</strong></td></tr></tbody></table><aside class="post-disclaimer" role="note"><p>⚠️ Перед выбором уточните юридические нюансы и условия вывода средств.</p></aside><p>В итоге я решил создать себе <a href="https://pay.cloudtips.ru/p/7d0e9b9f">страницу на CloudTips</a>.</p><h2 id="button">Как выглядит "правильная" кнопка доната (требования к кнопке) </h2><h3>Советы по оформлению:</h3><ul><li>Цвет — <strong>контрастный</strong>, выделяющийся на фоне сайта</li><li>Надпись — короткая и мотивирующая: <code>Поддержать</code>, <code>Купить кофе</code>, <code>Помочь блогу</code></li><li>Размер — не слишком маленький, кнопка должна быть заметной</li><li>Положение — в шапке, футере и внутри постов</li></ul><h3>Пример кнопки:</h3><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">a</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  href</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://www.buymeacoffee.com/yourname"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  target</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"_blank"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  rel</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"noopener noreferrer"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  class</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"donate-button"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ☕ Поддержать</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">a</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><p>Стилизуем через CSS:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-css"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">.donate-button</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  background-color</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> #ffdd00</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  color</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> #000</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  padding</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.6</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">em</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 1</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">em</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  font-weight</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> bold</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  border-radius</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 8</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">px</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  text-decoration</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> none</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  box-shadow</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0 2</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">px</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 4</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">px</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rgba</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.1)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  transition</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> transform 0.2</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">s</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">.donate-button:hover</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  transform</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> scale</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(1.05)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h2 id="where">Где размещать кнопку </h2><ul><li><strong>В шапке сайта</strong> — рядом с навигацией;</li><li><strong>В конце каждого поста</strong> — после текста;</li><li><strong>В сайдбаре</strong> (если есть);</li><li><strong>В футере</strong> — для постоянного доступа;</li><li><strong>Плавающая кнопка</strong> (например, снизу справа);</li></ul><h2 id="tricks">Продвинутые приёмы </h2><ul><li><strong>QR-код для доната</strong> — удобно если вас читают с десктопа, а платить будут с мобильного;</li><li><strong>Inline‑форма</strong> — некоторые сервисы позволяют вставлять форму прямо в пост;</li><li><strong>Цели/прогресс</strong> — показывайте цель сбора и текущий прогресс (мотивация!);</li></ul><h2 id="social">Интеграция в соцсетях </h2><ul><li><strong>Telegram</strong>: закрепите ссылку в описании канала</li><li><strong>YouTube</strong>: добавьте кнопку в описание профиля</li></ul><h2 id="faq">Частые вопросы </h2><p><strong>Нужно ли платить налоги?</strong></p><p><strong>Всё сложно</strong>, но на практике часто <strong>проще заплатить налог</strong>, чем потом объяснять и доказывать банку и налоговой что ты не верблюд.</p><p><strong>Какой сервис выбрать?</strong></p><p>Для международной аудитории — <strong>BuyMeACoffee</strong> или <strong>Donorbox</strong>. Для РФ — <strong>CloudTips</strong> или <strong>ЮMoney</strong>.</p><p><strong>Люди не донатят, что делать?</strong></p><p>Добавьте <strong>личный призыв</strong>, расскажите, <strong>зачем</strong> поддержка важна, дайте <strong>стимул</strong>: «донаты помогут выпустить книгу».</p><p>Кнопка доната — это не про навязчивость, а про возможность поддержать полезный контент. Сделайте её <strong>видимой, дружелюбной и понятной</strong> — и она начнёт работать.</p><h2 id="react">P.S. Моя реализации кнопки для донатов на react </h2><h3>Компонент <strong>DonateButton</strong></h3><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> classNames </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "classnames"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> React </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { FaHeart } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react-icons/fa"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> styles </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "./DonateButton.module.css"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> DONATION_LINK</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://pay.cloudtips.ru/p/7d0e9b9f"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> DonateButton</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">a</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      href</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">DONATION_LINK</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      rel</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"noreferrer"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      target</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"_blank"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      className</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#6F42C1;--shiki-dark:#B392F0">classNames</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">        "bg-red-600 rounded-md p-2 text-white flex flex-row gap-2 justify-center items-center text-xl"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">        styles</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.wrap</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      )}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    ></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">FaHeart</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> size</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">20</span><span style="color:#24292EFF;--shiki-dark:#B392F0">} </span><span style="color:#6F42C1;--shiki-dark:#B392F0">className</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">styles</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.heart} /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">span</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>Отправить донат&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">span</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">a</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> DonateButton;</span></span></code></pre><h3>Стили для компонента</h3><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-css"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">.wrap</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  animation</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> glow 800</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">ms</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> ease-out</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> infinite</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> alternate</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  border-color</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rgb</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(153</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 51</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 51)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  box-shadow</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#F8F8F8">    0 0 5</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">px</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rgba</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(255</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.2)</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    inset</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0 0 5</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">px</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rgba</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(255</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.1)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  color</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rgb</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(255</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 238</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 238)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  outline</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> none</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">@keyframes</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> glow {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  0% {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    border-color</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rgb</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(153</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 51</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 51)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    box-shadow</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#F8F8F8">      0 0 5</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">px</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rgba</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(255</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.2)</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">      inset</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0 0 5</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">px</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rgba</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(255</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.1)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  100% {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    border-color</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rgb</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(255</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 102</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 102)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    box-shadow</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#F8F8F8">      0 0 20</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">px</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rgba</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(255</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.6)</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">      inset</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0 0 10</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">px</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rgba</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(255</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.4)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">.heart</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  animation</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> heartbeat 0.9</span><span style="color:#D32F2F;--shiki-dark:#F8F8F8">s</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> linear</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> infinite</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">@keyframes</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> heartbeat {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  0% {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    transform</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> scale</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(1.05)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  50% {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    transform</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> scale</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(1)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  100% {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    transform</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> scale</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">(0.9)</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>На этом всё! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/blog-donation-button/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем поддержку MDX в Next.js приложение]]></title>
            <link>https://baranov.guru/posts/nextjs-mdx/</link>
            <guid>https://baranov.guru/posts/nextjs-mdx</guid>
            <pubDate>Sun, 23 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению поддержки MDX в Next.js приложение.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Сегодня расскажем как можно добавить базовую поддержку <strong>MDX</strong> в <strong>Next.js</strong> приложение и использовать <strong>.mdx</strong> файлы в качестве страниц.</p><h2 id="what">Что такое MDX? </h2><p><strong>MDX</strong> - это синтаксис, который позволяет использовать <strong>JSX</strong>-синтаксис в <strong>markdown</strong> контенте, ну или наоборот. Подробнее можно почитать <a href="https://mdxjs.com/docs/what-is-mdx/">тут</a>.</p><p>Вот простой пример <strong>MDX</strong> синтаксиса:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-jsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ComplexComponent </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@/app/_components/ComplexComponent"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ImageWrap </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@/app/_components/ImageWrap"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0"># Тут заголовок</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">обычный текст</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">и React</span><span style="color:#D32F2F;--shiki-dark:#F97583">-</span><span style="color:#24292EFF;--shiki-dark:#B392F0">компоненты</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">-</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> например</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> для картинок</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">ImageWrap</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> src</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#22863A;--shiki-dark:#FFAB70">"путь к картинке"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}/></span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">-</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> или что</span><span style="color:#D32F2F;--shiki-dark:#F97583">-</span><span style="color:#24292EFF;--shiki-dark:#B392F0">нибудь сложное</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">ComplexComponent</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><h2>Описание процесса</h2><p>Итак, для того чтобы добавить поддержку <strong>MDX</strong> в <strong>Next.js</strong> приложение необходимо:</p><ul><li><a href="#packages">Установить необходимые пакеты</a>;</li><li><a href="#file">Создать файл mdx-components.tsx</a>;</li><li><a href="#config">Включить обработку .mdx файлов в next.config.js</a>;</li><li><a href="#extension">(Опционально) Настроить IDE</a>;</li></ul><h2 id="packages">Установка необходимых пакетов </h2><p>Нам понадобятся пакеты:</p><ul><li><a href="https://www.npmjs.com/package/@next/mdx">@next/mdx</a> - основной пакет, который дружит <strong>Next.js</strong> и <strong>MDX</strong>;</li><li><a href="https://www.npmjs.com/package/@mdx-js/loader">@mdx-js/loader</a> - <strong>webpack loader</strong> для <strong>MDX</strong> файлов;</li><li><a href="https://www.npmjs.com/package/@types/mdx">@types/mdx</a> - необходим для нормальной работы с <strong>typescript</strong>;</li></ul><p>Установим их следующими командами:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> i</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> -S</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> @next/mdx</span></span></code></pre><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> install</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> -D</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> @mdx-js/loader</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> @types/mdx</span></span></code></pre><h2 id="file">Создание файла mdx-components.tsx </h2><p>Теперь необходимо создать файл <strong>mdx-components.tsx</strong> и положить его в <strong>src/</strong>.</p><p>Файл <strong>mdx-components.tsx</strong> необходим, для того чтобы подружить <strong>MDX</strong> и <strong>Next.js</strong> приложение использующее <strong>App Router</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#D32F2F;--shiki-dark:#F97583"> type</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { MDXComponents } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "mdx/types"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> useMDXComponents</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(components</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MDXComponents</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MDXComponents</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    ...</span><span style="color:#24292EFF;--shiki-dark:#B392F0">components</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h2 id="config">Включение обработки .mdx файлов в next.config.js </h2><p>Осталось только подправить <strong>конфиг</strong> (файл <strong>next.config.js</strong>) нашего приложения.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> withMDX</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"@next/mdx"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">/** </span><span style="color:#D32F2F;--shiki-dark:#F97583">@type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> {import('next').NextConfig}</span><span style="color:#C2C3C5;--shiki-dark:#6B737C"> */</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> nextConfig</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // Добавляем `mdx` в pageExtensions</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  pageExtensions</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#22863A;--shiki-dark:#FFAB70">"js"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "jsx"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "mdx"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ts"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "tsx"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">module</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">exports</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> withMDX</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(nextConfig);</span></span></code></pre><p>Теперь вместо <strong>page.tsx</strong> для страниц можно использовать <strong>page.mdx</strong>! 🎉</p><h2 id="extension">Расширение для VSCode </h2><p>Бонус для тех кто использует <strong>VSCode</strong>, есть неплохое расширение для поддержки MDX синтаксиса - <a href="https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx">MDX for Visual Studio Code</a>;</p><p>На этом всё! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/nextjs-mdx/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Как посчитать время чтения текста (и добавить индикатор на сайт)]]></title>
            <link>https://baranov.guru/posts/blog-post-read-time/</link>
            <guid>https://baranov.guru/posts/blog-post-read-time</guid>
            <pubDate>Sat, 22 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению индикатора времени чтения текста на сайт.]]></description>
            <content:encoded><![CDATA[<div><p>Индикатор времени чтения — простая деталь, которая <strong>улучшает пользовательский опыт</strong>: читатель сразу понимает, сколько времени займёт статья.</p><p>В этой статье разберём:</p><ul><li>как считается время чтения;</li><li>какую скорость брать за основу;</li><li>как реализовать расчёт на JavaScript / TypeScript.</li></ul><h2 id="quick">Как быстро посчитать время чтения текста </h2><aside class="post-disclaimer" role="note"><p>Если не хочется считать вручную, проще проверить в готовом инструменте — например, в нашем <a href="https://baranov.guru/tools/text-calculator/">текстовом калькуляторе</a>.</p></aside><p>Базовая логика очень простая:</p><ul><li>считаем <strong>количество слов</strong> в тексте;</li><li>делим на <strong>среднюю скорость</strong> чтения.</li></ul><h3 id="formula">Формула расчёта </h3><p>Формула выглядит так:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>время чтения (мин) = количество слов / скорость чтения</span></span></code></pre><p>Чтобы избежать значения 0 минут, результат обычно округляют вверх.</p><h3 id="calculation-example">Пример расчёта </h3><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>700 слов / 140 слов в минуту ≈ 5 минут</span></span></code></pre><h2 id="speed">Какая скорость чтения считается нормальной </h2><p>Единого стандарта нет, но обычно используют такие значения:</p><ul><li><strong>100–120</strong> слов/мин — сложный или технический текст</li><li><strong>130–160</strong> слов/мин — обычные статьи</li><li><strong>180+</strong> слов/мин — лёгкое чтение</li></ul><p>Для блога с техническим контентом разумно брать около <strong>140</strong> слов в минуту — это даёт более реалистичную оценку.</p><h2 id="count-words">Подсчёт количества слов </h2><p>Начнём с функции подсчёта слов. Один из самых простых вариантов — использовать <strong>регулярное выражение</strong>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> countWords</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (str</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> words</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> str</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.match</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">/[ЁёА-я\w\d\’\'-]</span><span style="color:#D32F2F;--shiki-dark:#F97583">+</span><span style="color:#22863A;--shiki-dark:#FFAB70">/</span><span style="color:#D32F2F;--shiki-dark:#F97583">gi</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> words </span><span style="color:#D32F2F;--shiki-dark:#F97583">?</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> words</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">length</span><span style="color:#D32F2F;--shiki-dark:#F97583"> :</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><h3 id="cyrillic-words">Важный момент </h3><p>Обратите внимание на часть:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">ЁёА</span><span style="color:#D32F2F;--shiki-dark:#F97583">-</span><span style="color:#24292EFF;--shiki-dark:#B392F0">я</span></span></code></pre><p>Без неё кириллические слова не будут учитываться, и результат окажется некорректным (например, будет считаться только код или латиница).</p><h3 id="count-words-tests">Тесты для функции подсчёта слов </h3><p>После этого я написал несколько тестов для функции <strong>countWords</strong>, вот один из них для примера:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">it</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"should return correct value for cyrillic string"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // arrange</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> expectedResult</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 1</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> inputValue</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "тест"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // act</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> result</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> countWords</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(inputValue);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // assert</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  expect</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(result)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.toBe</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(expectedResult);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">});</span></span></code></pre><h2 id="read-time">Расчёт скорости чтения </h2><p>Теперь подсчитаем количество слов:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> AVERAGE_READ_SPEED</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 140</span><span style="color:#24292EFF;--shiki-dark:#B392F0">; </span><span style="color:#C2C3C5;--shiki-dark:#6B737C">//Слов в минуту</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getReadTime</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (content</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> Math</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.ceil</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">countWords</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(content) </span><span style="color:#D32F2F;--shiki-dark:#F97583">/</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> AVERAGE_READ_SPEED</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><h3 id="ceil">Не забудьте про округление </h3><p>Обратите внимание на вызов:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">Math</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.ceil</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span></span></code></pre><p>Для того чтобы не получать <strong>0 минут</strong>, решили <strong>округлять результат вверх</strong> до ближайшего целого числа.</p><h3 id="read-time-tests">Тесты для функции подсчёта времени чтения </h3><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">it</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"should return 0 for an empty string"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // arrange</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> expectedResult</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> inputValue</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#22863A;--shiki-dark:#FFAB70"> ""</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // act</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> result</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getReadTime</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(inputValue);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // assert</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  expect</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(result)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.toBe</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(expectedResult);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">it</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"should return 1 for not empty string"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // arrange</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> expectedResult</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 1</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> inputValue</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "тест"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // act</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> result</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getReadTime</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(inputValue);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  // assert</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  expect</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(result)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.toBe</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(expectedResult);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">});</span></span></code></pre><h2 id="disclaimer">Ограничения подхода </h2><p>Важно понимать, что это приближённая оценка:</p><ul><li>не учитывается сложность текста;</li><li>не учитываются изображения, код и форматирование;</li><li>скорость чтения у разных людей отличается.</li></ul><p>Тем не менее, для большинства блогов такой расчёт даёт достаточно точный результат.</p><h2>Где это может пригодиться</h2><p>Индикатор времени чтения полезен не только разработчикам, но и:</p><ul><li>авторам блогов;</li><li>копирайтерам;</li><li>SEO-специалистам;</li><li>редакторам;</li><li>студентам;</li></ul><p>Это простой способ сделать контент более предсказуемым для читателя.</p><h2 id="text-calculator">Как проверить результат </h2><p>Если вы хотите быстро проверить любой текст без кода, можно воспользоваться <a href="https://baranov.guru/tools/text-calculator/">текстовым калькулятором</a>: вставить текст и сразу получить количество слов и примерное время чтения.</p><div><p><img src="https://baranov.guru/assets/tools/text-calculator/cover.webp" alt="Текстовый калькулятор"></p><p><a href="https://baranov.guru/tools/text-calculator/">Текстовый калькулятор</a> — Проверьте ваш текст за минуту: найдёт длину текста, количество слов, абзацев и примерное время чтения.</p></div><p>На этом всё! <strong>Спасибо</strong> за внимание!</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/blog-post-read-time/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем подсветку кода (синтаксиса) в статический блог на Next.js]]></title>
            <link>https://baranov.guru/posts/nextjs-prismjs/</link>
            <guid>https://baranov.guru/posts/nextjs-prismjs</guid>
            <pubDate>Sat, 15 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению подсветки кода (синтаксиса) в статическом блоге на Next.js.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Продолжаем работать над улучшением <a href="https://baranov.guru">блога</a>. В этой статье расскажем как добавляли <strong>подсветку кода (синтаксиса)</strong> в постах.</p><p>Есть множество способов сделать это, мы же решили пойти самым простым путём и остановились на использовании пакета <a href="https://www.npmjs.com/package/prismjs">Prism</a>.</p><p>Процесс можно разделить на несколько этапов:</p><ul><li><a href="#install">Установка необходимых пакетов</a></li><li><a href="#generate">Статическая генерация подсвеченного кода</a></li><li><a href="#languages">Добавление подсветки необходимых языков</a></li><li><a href="#plugin">Работа с плагинами Prism</a></li><li><a href="#theme">Подключение темы Prism</a></li></ul><h2 id="install">Установка необходимых пакетов </h2><p>Сначала установим необходимые пакеты:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> i</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> prismjs</span></span></code></pre><p>Так как мы статически генерируем <strong>html</strong> блога из <strong>.md</strong> файлов, то нам нужен способ запускать <strong>Prism</strong> во время сборки приложения. В этом нам поможет пакет <a href="https://www.npmjs.com/package/rehype-prism">rehype-prism</a>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> i</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> rehype-prism</span></span></code></pre><h2 id="generate">Статическая генерация подсвеченного кода </h2><p>В подробностях тут описывать особенно нечего, просто импортируем необходимые пакеты и включаем их в процесс генерации <strong>html</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "prismjs"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> rehypePrism </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "rehype-prism"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> rehypeStringify </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "rehype-stringify"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> remarkCustomHeaderId </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "remark-custom-header-id"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> remarkParse </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "remark-parse"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> remarkRehype </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "remark-rehype"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { unified } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "unified"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> markdownToHtml</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(markdown</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> result</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> unified</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .use</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(remarkParse</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { sanitize</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> false</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> })</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .use</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(remarkCustomHeaderId)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .use</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(remarkRehype)</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">    // Внимание на следующую строку</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .use</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(rehypePrism)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .use</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(rehypeStringify)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(markdown);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">await</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> result)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.toString</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h2 id="languages">Добавление подсветки необходимых языков </h2><p><strong>Prism</strong> поддерживает множество языков, но для того чтобы они заработали, необходимо подключить их.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "prismjs/components/prism-bash"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "prismjs/components/prism-json"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "prismjs/components/prism-typescript"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span></code></pre><h2 id="plugin">Работа с плагинами Prism </h2><p>Так же <strong>Prism</strong> поддерживает множество плагинов. Мы выбрали <strong>"line-numbers"</strong>, <strong>"toolbar"</strong>, <strong>"copy-to-clipboard"</strong> плагины.</p><p>Для того чтобы их активировать необходимо:</p><ul><li><a href="#import-plugins">Подключить код и стили этих плагинов</a>;</li><li><a href="#use-plugins">Указать пакету <strong>rehype-prism</strong> что их необходимо использовать</a>.</li></ul><h3 id="import-plugins">Подключение кода и стилей этих плагинов </h3><p>Добавим импорт стилей для плагинов в компонент <strong>Post</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "prismjs/plugins/line-numbers/prism-line-numbers.css"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "prismjs/plugins/toolbar/prism-toolbar.css"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span></code></pre><h3 id="use-plugins">Использование плагинов </h3><p>Пакет <strong>rehype-prism</strong> поддерживает нужные нам плагины из коробки, необходимо только указать что их следует использовать.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> markdownToHtml</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(markdown</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> result</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> unified</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .use</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(remarkParse</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { sanitize</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> false</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> })</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .use</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(remarkCustomHeaderId)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .use</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(remarkRehype)</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">    // Внимание на следующую строку</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .use</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(rehypePrism</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      plugins</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#22863A;--shiki-dark:#FFAB70">"line-numbers"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "toolbar"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "copy-to-clipboard"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    })</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .use</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(rehypeStringify)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(markdown);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">await</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> result)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.toString</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h2 id="theme">Подключение цветовой темы Prism </h2><p><strong>Prism</strong> поддерживает множество цветовых тем. Для того чтобы использовать выбранную тему, необходимо импортировать <strong>.css</strong> файл нужной темы.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "prismjs/themes/prism.css"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span></code></pre><p>На этом всё! 🎉</p><p>Результат можно посмотреть в <a href="https://baranov.guru/posts/">любом из постов блога</a>.</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/nextjs-prismjs/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем пагинацию в статический блог на Next.js]]></title>
            <link>https://baranov.guru/posts/blog-pagination/</link>
            <guid>https://baranov.guru/posts/blog-pagination</guid>
            <pubDate>Mon, 03 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению пагинации постов в статическом блоге на Next.js.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Продолжаем работать над улучшением <a href="https://baranov.guru">блога</a> и одновременно разбираться в <strong>Next.js</strong>. В этой статье расскажем как добавить пагинацию к статическим страницам блога.</p><p>Процесс можно разделить на несколько этапов:</p><ul><li><a href="#divide">Разделение логики страницы на динамическую и статическую</a></li><li><a href="#links">Добавление навигационного компонента</a></li><li><a href="#seo">Работа над поисковой выдачей</a></li></ul><p>Результат можно посмотреть в самом низу <a href="https://baranov.guru/posts">страницы с постами</a>.</p><h2 id="divide">Разделение логики страницы на динамическую и статическую </h2><p>Нам не хотелось менять структуру страниц сайта, поэтому мы решили что страницу будем прокидывать через <strong>query string</strong>.</p><p>Статические страницы в <strong>Next.js</strong> имеют ряд ограничений при работе с <strong>query string</strong>. По сути, единственный доступный в <strong>моём случае</strong> способ получить доступ к параметрам из <strong>URL</strong> - воспользоваться хуком <a href="https://nextjs.org/docs/app/api-reference/functions/use-search-params">useSearchParams</a> из <strong>клиентского компонента</strong>.</p><p>Для этого необходимо <strong>вынести получение данных</strong> о постах в код <strong>статической</strong> страницы, и передать их в <strong>клиентский компонент</strong>, который сможет <strong>отфильтровать</strong> их нужным нам способом и отрисовать нужный нам список постов.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { Suspense } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> PostsPage</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> posts</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllPostsMeta</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> tags</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllTags</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      // ...</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Suspense</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> fallback</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">StaticPosts</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> posts</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{posts} </span><span style="color:#6F42C1;--shiki-dark:#B392F0">tags</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{tags} />}></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">DynamicPosts</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> posts</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{posts} </span><span style="color:#6F42C1;--shiki-dark:#B392F0">tags</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{tags} /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;/</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Suspense</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      // ...</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Заметьте что компонент <strong>DynamicPosts</strong> обёрнут в <strong>Suspense</strong>. Фоллбек будет отрендерен в <strong>build-time</strong>. А компонент <strong>DynamicPosts</strong> будет отрендерен уже <strong>во время исполнения кода</strong> страницы на клиенте.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">"use client"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">// ...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> DynamicPage</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Props</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ({ posts</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> tags }) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> searchParams</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> useSearchParams</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> page</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Number</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">searchParams</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.get</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"page"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)) </span><span style="color:#D32F2F;--shiki-dark:#F97583">||</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 1</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> maxPage</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> Math</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.ceil</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">posts</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">length</span><span style="color:#D32F2F;--shiki-dark:#F97583"> /</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> POSTS_PER_PAGE</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> pageOffset</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (page </span><span style="color:#D32F2F;--shiki-dark:#F97583">-</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 1</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">*</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> POSTS_PER_PAGE</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> pagePosts</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> posts</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.slice</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(pageOffset</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> pageOffset </span><span style="color:#D32F2F;--shiki-dark:#F97583">+</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> POSTS_PER_PAGE</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">!</span><span style="color:#1976D2;--shiki-dark:#79B8FF">pagePosts</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">length</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> notFound</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Posts</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> posts</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{pagePosts} </span><span style="color:#6F42C1;--shiki-dark:#B392F0">title</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Посты"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">} </span><span style="color:#6F42C1;--shiki-dark:#B392F0">withFilter</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> tags</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{tags} /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">PageSelector</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> page</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{page} </span><span style="color:#6F42C1;--shiki-dark:#B392F0">maxPage</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{maxPage} /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><h2 id="links">Добавление навигационного компонента </h2><p>Для навигационного компонента, будем использовать компонент <strong>Link</strong> из встроенного пакета <strong>next/link</strong> и прокидывать в параметр <strong>href</strong> необходимую нам страницу.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Link</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> href</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#22863A;--shiki-dark:#FFAB70">`./?page=</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">page</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}>{page}&#x3C;/</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Link</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><p>Сильно углубляться в подробности реализации не будем, скажем лишь что в результате получилось вот так:</p><p><img src="https://baranov.guru/assets/posts/blog-pagination/page-selector.webp" alt="Навигационный компонент" title="Навигационный компонент"></p><h2 id="seo">Работа над поисковой выдачей </h2><p>Для того чтобы не получить дублирующиеся страницы в поиске, и не усложнить себе жизнь долгим процессом удалением дублей, необходимо сделать следующее:</p><ul><li><a href="#canonical">Добавить ссылку на каноническую страницу</a>;</li><li><a href="#robots">Добавить директиву Clean-Param в robots.txt</a>;</li></ul><h3 id="canonical">Добавление ссылки на каноническую страницу </h3><p>Для того чтобы правильно пройти через процесс <a href="https://developers.google.com/search/docs/crawling-indexing/canonicalization?hl=ru">нормализации</a>, необходимо добавить <strong>ссылку на каноническую страницу</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">link</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rel</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"canonical"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> href</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://baranov.guru/posts"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span></code></pre><p>В <strong>Next.js</strong> есть специальное <a href="https://nextjs.org/docs/app/building-your-application/optimizing/metadata">Metadata API</a>, которое можно использовать как раз для этих целей.</p><p>Воспользуемся им:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> metadata</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Metadata</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Посты | Алексей Баранов. Блог"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Страница со всеми опубликованными постами"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  alternates</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    canonical</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `/posts`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><h3 id="robots">Добавление директивы Clean-Param в robots.txt </h3><p><strong>Яндекс</strong> поддерживает специальную <a href="https://yandex.ru/support/webmaster/robot-workings/clean-param.html">директиву Clean-param</a> в файлах <strong>robots.txt</strong>.</p><p>Давайте добавим её:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>User-agent: Yandex</span></span>
<span class="line"><span>Clean-param: page</span></span>
<span class="line"><span>Allow: /</span></span></code></pre><p>На этом всё! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/blog-pagination/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем поиск по тегам в статический блог на Next.js]]></title>
            <link>https://baranov.guru/posts/blog-tags/</link>
            <guid>https://baranov.guru/posts/blog-tags</guid>
            <pubDate>Sun, 02 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению простейшего поиска по тегам в статическом Next.js блоге.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Количество постов в <a href="https://baranov.guru">блоге</a> понемногу <strong>прибавляется</strong>, а находить их становится <strong>сложнее</strong>, поэтому мы решили добавить <strong>поиск постов по тегам</strong>.</p><p>Вот так он выглядит на момент написания этой статьи:</p><p><img src="https://baranov.guru/assets/posts/blog-tags/result.webp" alt="Результат" title="Результат"></p><p>Процесс можно разделить на несколько этапов:</p><ul><li><a href="#add-tags">Добавление тегов к статьям</a></li><li><a href="#page-generation">Генерация страниц для каждого тега</a></li><li><a href="#links">Добавление ссылок на страницы</a></li><li><a href="#seo">Работа над поисковой выдачей</a></li></ul><h2 id="add-tags">Добавление тегов к постам </h2><p>Тут всё просто, как и <a href="https://baranov.guru/posts/post-sharing-and-recommendations#recomendations">в случае с рекомендациями</a>, мы решили захардкодить в <strong>frontmatter</strong> каждой статьи массив с тегами.</p><p>Например, для <a href="https://baranov.guru/posts/blog-tags">этой статьи</a> массив выглядит следующим образом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">tags: [</span><span style="color:#22863A;--shiki-dark:#FFAB70">"улучшения блога"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "nextjs"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span></span></code></pre><p>Если вы пойдёте этим же путём, то <strong>обратите внимание</strong> на то, что в названии тегов лучше не использовать <strong>кириллицу и спец. символы</strong>. О том как всё же использовать в тегах кириллицу читайте <a href="#interesting">далее в статье</a>.</p><h2 id="page-generation">Генерация страниц для каждого тега </h2><p>Генерация страниц для тегов очень похожа на генерацию страниц постов.</p><p>Алгоритм очень простой и выглядит следующим образом:</p><ul><li><a href="#collect-tags">Проходим по всем постам и собираем уникальные теги</a></li><li><a href="#generate">Для каждого тега генерируем страницу с постами</a></li></ul><h3 id="collect-tags">Сбор уникальных тегов </h3><p>О том как получить все посты и их метаданные мы писали <a href="https://baranov.guru/posts/nextjs-rss#frontmatter">в статье о добавлении RSS фида</a>.</p><p>Там же можно найти и полную реализацию метода <strong>getAllPosts</strong>.</p><p>А нам же остаётся только добавить все теги в массив и удалить оттуда <strong>дубли</strong>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllTags</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[] {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> notUniqueTags</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllPosts</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.flatMap</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((p) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    p</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.tags </span><span style="color:#D32F2F;--shiki-dark:#F97583">?</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#D32F2F;--shiki-dark:#F97583">...</span><span style="color:#1976D2;--shiki-dark:#79B8FF">p</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.tags] </span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> []</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> Array</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.from</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#D32F2F;--shiki-dark:#F97583">new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Set</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(notUniqueTags));</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h3 id="generate">Генерация страниц </h3><p>Для того чтобы сгенерировать страницы, нам нужен не только список тегов, но и список постов по тегу:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllPostsByTag</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(tag</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">)</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[] {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllPosts</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.filter</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((p) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> p</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">tags</span><span style="color:#6F42C1;--shiki-dark:#B392F0">?.includes</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(tag));</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Далее добавляем <strong>компонент для отображения</strong> страницы:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Params</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  params</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    tag</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">// Компонент для отображения страницы</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TagPage</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({ params }</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Params</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> decodedTag</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getTagFromURI</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">params</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.tag);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> posts</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllPostsByTag</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(decodedTag);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">!</span><span style="color:#1976D2;--shiki-dark:#79B8FF">posts</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">length</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> notFound</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      // Отображаем тут наши посты</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>И генерируем <strong>статические</strong> страницы:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">// Генерируем статические страницы в build-time</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> generateStaticParams</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> tags</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllTags</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> tags</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.map</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((t) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    tag</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getTagURI</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(t)</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }));</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h3 id="interesting">Неочевидные моменты </h3><p>Вы наверняка заметили что мы используем функции <strong>getTagFromURI</strong> и <strong>getTagURI</strong> при работе с тегами.</p><p>Дело в том что на данный момент в <strong>Next.js</strong> <a href="https://github.com/vercel/next.js/issues/10084">существуют сложности с кириллическими путями</a>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getTagURI</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (tag</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">env</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">NODE_ENV</span><span style="color:#D32F2F;--shiki-dark:#F97583"> ===</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "development"</span><span style="color:#D32F2F;--shiki-dark:#F97583"> ?</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> encodeURIComponent</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(tag) </span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> tag;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getTagFromURI</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (uri</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> decodeURIComponent</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(uri);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><h2 id="links">Добавление ссылок на страницы </h2><p>Тут сказать особо нечего, единственный момент, который следует учитывать - не забывайте оборачивать теги в <strong>encodeURIComponent</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TagLinks</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> tags</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllTags</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">div</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      {</span><span style="color:#1976D2;--shiki-dark:#79B8FF">tags</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.map</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((item</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> i) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Link</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">          href</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#22863A;--shiki-dark:#FFAB70">`/tags/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#6F42C1;--shiki-dark:#B392F0">getTagURI</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(item)</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">          key</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{i}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        ></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          {</span><span style="color:#22863A;--shiki-dark:#FFAB70">`#</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">item</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        &#x3C;/</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Link</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      ))}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">div</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><h2 id="seo">Работа над поисковой выдачей </h2><aside class="post-disclaimer" role="note"><p>ℹ️ Подробнее про работу с <strong>sitemap</strong> можно прочитать в статье <a href="https://baranov.guru/posts/nextjs-sitemap/">Добавляем sitemap.xml в Next.js приложение</a>;</p></aside><p>Для улучшения индексирования сгенерированные страницы можно добавить в файл <strong>sitemap</strong> сайта.</p><p>Для этого, в файл <strong>sitemap.ts</strong>, лежащий в корне сайта добавляем следующий код:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { MetadataRoute } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { getAllTags</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> getTagURI } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@/lib/api"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MetadataRoute</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Sitemap</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> tags</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllTags</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">    // тут остальные страницы сайта</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    ...</span><span style="color:#1976D2;--shiki-dark:#79B8FF">tags</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.map</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((tag) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `https://baranov.guru/tags/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#6F42C1;--shiki-dark:#B392F0">getTagURI</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(tag)</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      lastModified</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      priority</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 0.7</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }))</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>На этом всё! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/blog-tags/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем рекомендации постов и блок "Поделиться в соц. сетях"]]></title>
            <link>https://baranov.guru/posts/post-sharing-and-recommendations/</link>
            <guid>https://baranov.guru/posts/post-sharing-and-recommendations</guid>
            <pubDate>Sun, 26 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению механизма рекомендаций постов и блока "Поделиться в соц. сетях".]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Изучали различные блоги, аналогичные <a href="https://baranov.guru">нашему</a> и решили добавить <a href="#social">блок "Поделиться в соц. сетях"</a>, а так же простейший <a href="#recomendations">механизм рекомендаций</a> статей. Вот что из этого вышло...</p><p><img src="https://baranov.guru/assets/posts/post-sharing-and-recommendations/result.webp" alt="Результат" title="Результат"></p><h2 id="recomendations">Добавляем рекомендации постов </h2><p>Тут всё просто, мы решили захардкодить в frontmatter каждой статьи массив со ссылками на другие статьи, схожие по тематике.</p><p>Только вместо ссылок мы использовали <strong>slug</strong> постов.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">recommendations: [</span><span style="color:#22863A;--shiki-dark:#FFAB70">"collabic"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "vk-feed"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span></span></code></pre><p>У нас уже был компонент <strong>PostPreview</strong> для отображения описания поста. Он используется на главной странице. Поэтому мы просто переиспользовали его.</p><p>В компоненте поста, мы получаем список данных о рекомендованных постах:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> recommendations</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">recomendations</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.map</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((s) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getPostBySlug</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(s));</span></span></code></pre><p>И передаём их в компонент <strong>PostPreview</strong>, который рендерим после тела статьи:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">recommendations</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.map</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((r) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">PostPreview</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    key</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">r</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    title</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">r</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    coverImage</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">r</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.coverImage}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    date</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">r</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">r</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    excerpt</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">r</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.excerpt}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">))}</span></span></code></pre><p>В результате блок рекомендаций выглядит следующим образом:</p><p><img src="https://baranov.guru/assets/posts/post-sharing-and-recommendations/recommendations.webp" alt="Блок рекомендаций" title="Блок рекомендаций"></p><h2 id="social">Добавляем блок "Поделиться в соц. сетях" </h2><p>Для кнопок соц. сетей мы использовали готовое решение - пакет <a href="https://www.npmjs.com/package/react-share">react-share</a>.</p><p>Установим его:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> i</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> react-share</span></span></code></pre><p>Далее всё просто, импортим оттуда кнопки и иконки:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">"use client"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { TelegramIcon</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> TelegramShareButton } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react-share"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span></code></pre><p>И передаём в них базовые данные о посте:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> ICON_SIZE</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 40</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">TelegramShareButton</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> url</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{url} </span><span style="color:#6F42C1;--shiki-dark:#B392F0">title</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title}></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">TelegramIcon</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    size</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">ICON_SIZE</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    className</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"rounded-3xl grayscale hover:grayscale-0"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/</span><span style="color:#1976D2;--shiki-dark:#79B8FF">TelegramShareButton</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><p>С кнопками <strong>соц. сетей</strong> всё просто. Но нам хотелось ещё добавить возможность использовать <strong>нативный механизм "поделиться"</strong> на мобильных устройствах и просто удобно копировать ссылку на статью на десктопах.</p><p>Поэтому мы решил сделать свою кнопку.</p><p>С разметкой там всё просто, поэтому остановлюсь только на интересном, а именно на использовании <strong>navigator.share API</strong>.</p><p>Сначала подготовим данные:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> shareData</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  text</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.description</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.url</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><p>Затем вызовем <strong>navigator.share</strong>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> handler</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  try</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    await</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> navigator</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.share</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(shareData);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  } </span><span style="color:#D32F2F;--shiki-dark:#F97583">catch</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (e) {</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">    // Тут нужен фоллбек на копирование ссылки</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    writeToClipboard</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.url);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><p>Нужно учитывать что <strong>navigator.share</strong> может быть недоступен по множеству причин, поэтому я добавил фоллбек на запись ссылки в <strong>clipboard</strong>.</p><p>Но и запись в <strong>clipboard</strong> может быть недоступен по множеству причин, поэтому и это надо учитывать.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> writeToClipboard</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (text</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  try</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    await</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> navigator</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">clipboard</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.writeText</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(text);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  } </span><span style="color:#D32F2F;--shiki-dark:#F97583">catch</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (error) {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.error</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((error </span><span style="color:#D32F2F;--shiki-dark:#F97583">as</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Error</span><span style="color:#24292EFF;--shiki-dark:#B392F0">).message);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><p><img src="https://baranov.guru/assets/posts/post-sharing-and-recommendations/social.webp" alt="Результат" title="Результат"></p><p>А вот так этот блок выглядит при включенном <strong>Ad Block</strong>:</p><p><img src="https://baranov.guru/assets/posts/post-sharing-and-recommendations/blocked.webp" alt="Заблокированный результат" title="Заблокированный результат"></p><p>Не очень конечно, думаем что с блокировщиками рекламы тоже предстоит разобраться со временем.</p><p>На этом всё! 🎉</p><p>В живую результат статьи можно увидеть ниже 🙂</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/post-sharing-and-recommendations/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем Google Analytics в Next.js приложение]]></title>
            <link>https://baranov.guru/posts/nextjs-google-analytics/</link>
            <guid>https://baranov.guru/posts/nextjs-google-analytics</guid>
            <pubDate>Fri, 24 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению Google Analytics в Next.js приложение.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Решили добавить Google Analytics на страницы <a href="https://baranov.guru">блога</a> для базового отслеживания переходов по страницам.</p><p>Если коротко, то есть очень много способов сделать это, в этой статье мы остановимся на двух из них:</p><ul><li><a href="#old">Старый способ</a>, который мы использовали раньше;</li><li><a href="#new">Новый способ</a>, появившийся не так давно;</li></ul><h2 id="old">Старый способ </h2><p>Ранее мы добавляли <strong>аналитику от гугла</strong> следующим образом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> Script </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next/script"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> React </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> GA_MEASUREMENT_ID</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "G-1234567890"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">; </span><span style="color:#C2C3C5;--shiki-dark:#6B737C">//Не настоящий</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Props</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  enabled</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> GtagContainer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Props</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ({ enabled }) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">!</span><span style="color:#24292EFF;--shiki-dark:#B392F0">enabled) </span><span style="color:#D32F2F;--shiki-dark:#F97583">return</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> null</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Script</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">        src</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#22863A;--shiki-dark:#FFAB70">`https://www.googletagmanager.com/gtag/js?id=</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">GA_MEASUREMENT_ID</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Script</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"google-analytics"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        {</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">          window.dataLayer = window.dataLayer || [];</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">          function gtag(){dataLayer.push(arguments);}</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">          gtag('js', new Date());</span></span>
<span class="line"></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">          gtag('config', '</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">GA_MEASUREMENT_ID</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">');</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">        `</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;/</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Script</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> GtagContainer;</span></span></code></pre><h2 id="new">Новый способ </h2><p>Теперь же <strong>Vercel</strong> выпустили пока ещё экспериментальный пакет <a href="https://www.npmjs.com/package/@next/third-parties">@next/third-parties</a>, с которым добавить аналитику становится чуть удобнее.</p><p>Установим его.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> i</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> @next/third-parties@latest</span></span></code></pre><p>Далее добавим компонент <strong>GoogleAnalytics</strong> в <strong>RootLayout</strong></p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { GoogleAnalytics } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> '@next/third-parties/google'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> GA_MEASUREMENT_ID</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "G-1234567890"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">; </span><span style="color:#C2C3C5;--shiki-dark:#6B737C">//Не настоящий</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> RootLayout</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  children</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  children</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ReactNode</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">html</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> lang</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"en"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">body</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>{children}&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">body</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">GoogleAnalytics</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> gaId</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">GA_MEASUREMENT_ID</span><span style="color:#24292EFF;--shiki-dark:#B392F0">} /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">html</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  )</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Теперь всё должно работать.</p><p>На этом всё! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/nextjs-google-analytics/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Автопостинг в VK через RSS feed]]></title>
            <link>https://baranov.guru/posts/vk-feed/</link>
            <guid>https://baranov.guru/posts/vk-feed</guid>
            <pubDate>Tue, 21 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Заметили в настройках сообщества в VK интересную настройку, связанную с фидами. И понеслось.]]></description>
            <content:encoded><![CDATA[<div><p>Заметили в настройках сообщества в <strong>VK</strong> интересную настройку <strong>Импорт RSS</strong>.</p><p>А так как мы недавно <a href="https://baranov.guru/posts/nextjs-rss">добавили RSS фид</a>, то мы сразу же решили её опробовать.</p><p>То как <strong>VK</strong> обрабатывает фид - тайна за семью печатями. В документации мы <strong>не нашли</strong> никаких <strong>подробностей</strong>. Там даже <strong>описания</strong> этой функции нет.</p><p>Настройка просто есть, <strong>кому надо - тот разберётся</strong>.</p><p>Нам надо, мы начали разбираться 🙂</p><h2 id="test">Тестируем настройку </h2><p>Итак, выставляем настройку следующим образом:</p><p><img src="https://baranov.guru/assets/posts/vk-feed/setting-no-article.webp" alt="Настройка импорта RSS" title="Настройка импорта RSS"></p><p>Теперь выкладываем фид и ждём.</p><p>Через несколько минут получаем результат!</p><p><img src="https://baranov.guru/assets/posts/vk-feed/no-article-result.webp" alt="Первый результат" title="Первый результат"></p><p>На этом можно было бы и остановиться, но...</p><p>Что там было про <strong>"Публиковать в виде статьи"</strong>?</p><h2 id="article">Тестируем настройку "Публиковать в виде статьи" </h2><p>Меняем настройки:</p><p><img src="https://baranov.guru/assets/posts/vk-feed/setting.webp" alt="&#x22;Настройка \&#x22;Публиковать в виде статьи\&#x22;&#x22;" title="Настройка &#x22;Публиковать в виде статьи&#x22;"></p><ul><li><a href="https://baranov.guru/posts/nextjs-rss">Генерим фид</a>;</li><li>Загружаем его в бакет;</li><li><a href="https://youtu.be/dQw4w9WgXcQ">Ждём</a>;</li></ul><p>И получаем следующий результат:</p><p><img src="https://baranov.guru/assets/posts/vk-feed/article.webp" alt="Первый результат со статьёй" title="Первый результат со статьёй"></p><p>При этом если кликнуть на статью, то она <strong>пустая внутри</strong>:</p><p><img src="https://baranov.guru/assets/posts/vk-feed/empty-article.webp" alt="Пустая статья" title="Пустая статья"></p><h2 id="content">Добавляем содержимое статьи </h2><p>Статья пустая внутри, потому что я не пишу в <strong>RSS</strong> фид содержимое статьи.</p><p>Это легко исправить. Добавляем в код для генерации фида содержимое:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">allPosts</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.forEach</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((post) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`Adding rss item for post </span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  feed</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.addItem</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">    // ...</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    content</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.content</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  });</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">});</span></span></code></pre><p>Снова проверяем результат:</p><p><img src="https://baranov.guru/assets/posts/vk-feed/test.webp" alt="Результат со статьёй" title="Результат со статьёй"></p><p>Кажется что-то пошло не так. А что с содержимым статьи?</p><p><img src="https://baranov.guru/assets/posts/vk-feed/markdown-test.webp" alt="Содержимое статьи в формате markdown" title="Содержимое статьи в формате markdown"></p><p>Становится очевидно что причина в том, что <strong>markdown</strong> не поддерживается</p><p>Значит попробуем добавлять в фид <strong>HTML</strong> разметку.</p><p>Снова меняем код генерации фида:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">for</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#D32F2F;--shiki-dark:#F97583"> of</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> allPosts) {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`Adding VK item for post </span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> markdownToHtml</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.content);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  feed</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.addItem</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">    //..</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    content</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> content</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  });</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Проверяем результат:</p><p><img src="https://baranov.guru/assets/posts/vk-feed/test2.webp" alt="Результат со статьёй в формате HTML" title="Результат со статьёй в формате HTML"></p><p>А что с содержимым статьи?</p><p><img src="https://baranov.guru/assets/posts/vk-feed/html-test.webp" alt="Содержимое статьи в формате HTML" title="Содержимое статьи в формате HTML"></p><p>Видимо <strong>VK</strong> не поддерживает <strong>якорные ссылки</strong>...</p><p>А что ещё он не поддерживает?</p><p>Добавляем ещё больше <strong>разного</strong> содержимого в статью:</p><ul><li>Изображения с <strong>относительным</strong> путём;</li><li>Изображения с <strong>абсолютным</strong> путём;</li><li>Блоки кода;</li></ul><p>Результат:</p><p><img src="https://baranov.guru/assets/posts/vk-feed/test3.webp" alt="Содержимое статьи c картинками и кодом" title="Содержимое статьи c картинками и кодом"></p><h2 id="results">Выводы </h2><p>Выводы:</p><ul><li>Изображения с относительным путём - <strong>работают</strong>;</li><li>Изображения с абсолютным путём - <strong>работают</strong>;</li><li>Блоки кода - <strong>не работают</strong>;</li></ul><p>С этим можно жить 🙂</p><p>На этом всё, примеры автопостинга, вы всегда можете посмотреть в нашем <a href="https://vk.com/baranov_guru">сообществе во Вконтакте</a>! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/vk-feed/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Борьба с грамматическими ошибками в markdown файлах]]></title>
            <link>https://baranov.guru/posts/markdown-spellcheck/</link>
            <guid>https://baranov.guru/posts/markdown-spellcheck</guid>
            <pubDate>Mon, 20 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по использованию LTеX для борьбы с грамматическими ошибками в .md файлах.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Заметили большое количество грамматических, орфографических и пунктуационных ошибок в текстах статей <a href="https://baranov.guru">нашего блога</a>.</p><p>Поняли что с этим надо бороться и начали искать варианты.</p><p>Посты мы пишем в markdown файлах, используя <strong>VS Code</strong>, поэтому начали искать <strong>линтер</strong> с возможностью проверки грамматики, орфографии и пунктуации.</p><h2 id="cspell">Расширение code-spell-checker </h2><p>Ранее мы уже использовали расширение <a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker">code-spell-checker</a> поэтому решили попробовать его и в этот раз.</p><p>Но сразу столкнулись с парой проблем:</p><ul><li>отсутствие поддержки русского языка из коробки;</li><li>отсутствие проверки функций проверки грамматики и пунктуации;</li></ul><p><strong>Первая проблема легко решается</strong> установкой расширения с русским словарём <a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker-russian">russian-code-spell-checker</a>.</p><p>А вот вторая проблема <strong>не решается</strong> в рамках данного решения.</p><h2 id="ltex">LTеX </h2><p>Довольно быстро нашли другое решение - <a href="https://valentjn.github.io/ltex/index.html">LTеX</a>.</p><p>Основные особенности <strong>LTeX</strong>:</p><ul><li>Поддержка <strong>Markdown</strong>, <strong>BibTEX</strong>, <strong>ConTEXt</strong>, <strong>LATEX</strong>, <strong>Org</strong>, <strong>reStructuredText</strong>, <strong>R Sweave</strong>, <strong>XHTML</strong>;</li><li>Работает полностью локально, не делает запросов в интернет;</li><li>Поддержка более <strong>20</strong> языков, в том числе <strong>русского</strong>;</li><li>Поддержка <strong>Quick fix</strong> и подсветка ошибок;</li><li>Поддержка пользовательских словарей;</li><li>Нормальная документация;</li></ul><h2 id="install">Настройка расширения для VS code </h2><p>Для того чтобы включить и использовать LTeX для русского языка в <strong>VS Code</strong> надо:</p><ul><li>Установить <a href="https://marketplace.visualstudio.com/items?itemName=valentjn.vscode-ltex">расширение</a>;</li><li>В настройках расширения, либо в файле <strong>settings.json</strong> установить следующие настройки:</li></ul><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  "ltex.language"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">: </span><span style="color:#22863A;--shiki-dark:#FFAB70">"ru-RU"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  "ltex.additionalRules.motherTongue"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">: </span><span style="color:#22863A;--shiki-dark:#FFAB70">"ru-RU"</span></span></code></pre><h2 id="examples">Примеры использования </h2><p>Вот так выглядит подсветка ошибок в файле:</p><p><img src="https://baranov.guru/assets/posts/markdown-spellcheck/ltex_warnings.webp" alt="Подсветка ошибок в файле" title="Подсветка ошибок в файле"></p><p>Вот так ошибки выглядят при наведении на них:</p><p><img src="https://baranov.guru/assets/posts/markdown-spellcheck/ltex_hover.webp" alt="Ошибки при наведении" title="Ошибки при наведении"></p><p>Вот так выглядит меню <strong>Quick fix</strong>:</p><p><img src="https://baranov.guru/assets/posts/markdown-spellcheck/ltex_quick_fix.webp" alt="Меню Quick fix" title="Меню Quick fix"></p><p>Так же можно добавлять слова и правила в исключения:</p><p><img src="https://baranov.guru/assets/posts/markdown-spellcheck/ltex_exclude.webp" alt="Исключения" title="Исключения"></p><p>При этом генерируются .txt файлы с добавленными словами и исключениями для каждого языка.</p><p>Пример <strong>ltex.dictionary.ru-RU.txt</strong>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-text"><span class="line"><span>бакет</span></span>
<span class="line"><span>захостить</span></span></code></pre><p>На этом всё! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/markdown-spellcheck/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем JSON-LD разметку к блогу на Next.js]]></title>
            <link>https://baranov.guru/posts/nextjs-json-ld/</link>
            <guid>https://baranov.guru/posts/nextjs-json-ld</guid>
            <pubDate>Sat, 18 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению JSON-LD разметки к блогу на Next.js.]]></description>
            <content:encoded><![CDATA[<div><p>В <a href="https://baranov.guru/posts/nextjs-yandex-turbo">прошлом посте</a>, мы рассказывали о том как сделать так, чтобы в <strong>выдаче Яндекса</strong> отображалась красивая галерея статей.</p><p>Пришло время сделать выдачу ещё лучше, а также поработать над выдачей в других поисковиках. Сделать это можно добавив на страницы разметку <a href="https://json-ld.org/">JSON-LD</a>.</p><p>Эта статья показывает, как внедрить JSON-LD именно в блог на <strong>Next.js</strong>. Если вы только разбираетесь, зачем бизнесу структурированные данные и какие типы schema.org выбирать для сайта, сначала прочитайте обзорную статью <a href="https://baranov.guru/posts/json-ld/">JSON-LD для сайта</a>, а затем возвращайтесь к этому техническому примеру.</p><div><p><img src="https://baranov.guru/assets/posts/json-ld/cover.webp" alt="JSON-LD для сайта: как разметка schema.org помогает SEO, сниппетам и продажам"></p><p><a href="https://baranov.guru/posts/json-ld/">JSON-LD для сайта: как разметка schema.org помогает SEO, сниппетам и продажам</a> — Большое руководство для владельцев бизнеса и SEO-специалистов: зачем нужна JSON-LD-разметка, какие типы schema.org внедрять и как не допустить типичных ошибок.</p></div><h2 id="what">Что такое JSON-LD? </h2><p><strong>JSON-LD (JSON Lightweight Linked Data format)</strong> — это формат метаданных для поисковых систем о типе контента на каждой странице. В теории, наличие подобной разметки на сайте приводит к более высоким результатам в поисковой выдаче.</p><p><strong>JSON-LD</strong> выглядит так:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@context"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "BlogPosting"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "headline"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Добавляем JSON-LD разметку к блогу на Next.js"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "description"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Инструкция по добавлению JSON-LD разметки к блогу на Next.js"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "datePublished"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "2024-05-18"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "genre"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Technology"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "author"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "@type"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Person"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "name"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Алексей Баранов"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "url"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "image"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/assets/posts/nextjs-json-ld/cover.webp"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h2 id="why">Для чего нужен JSON-LD? </h2><p>В поисковой выдаче <strong>Яндекса</strong> есть, как минимум, 2 элемента, которые напрямую зависят от <strong>JSON-LD</strong>:</p><ul><li><strong>Навигационные цепочки</strong> - они же хлебные крошки; <img src="https://baranov.guru/assets/posts/nextjs-json-ld/navigation.webp" alt="Навигационные цепочки" title="Навигационные цепочки"></li><li>Сниппет <strong>Вопрос-ответ</strong>; <img src="https://baranov.guru/assets/posts/nextjs-json-ld/faq.webp" alt="Вопрос-ответ" title="Вопрос-ответ"></li></ul><p>С поисковой выдачей <strong>Google</strong> всё ещё интереснее. Они называют такую разметку <strong>Структурированными данными</strong>.</p><p>В документации для разработчиков есть целый <a href="https://developers.google.com/search/docs/appearance/structured-data/search-gallery?hl=ru">раздел посвящённый <strong>Структурированным данным</strong> и <strong>JSON-LD</strong></a>.</p><p>От <strong>Структурированных данных</strong> зависят такие функции поисковой выдачи как:</p><ul><li><strong>Статья</strong> - название говорит само за себя;</li><li><strong>Строка навигации</strong> - аналог <strong>Навигационных цепочек</strong> Яндекса;</li><li><strong>Карусель</strong> - аналог <a href="https://baranov.guru/posts/nextjs-yandex-turbo">Турбо карусели</a> Яндекса;</li><li><strong>Часто задаваемые вопросы</strong> - аналог <strong>Вопрос-ответ</strong> от Яндекса;</li><li><strong>Товары</strong> - описание товаров и их характеристик;</li><li><strong>Видео</strong> - описание и основные моменты видео;</li><li><strong>Организация</strong> - название, часы работы, телефоны и прочее;</li><li><strong>Мероприятие</strong> - где и когда состоится;</li></ul><p>и многие другие (я насчитал <strong>36 штук</strong>).</p><p>Для начала мне хватит просто <strong>Навигационных цепочек</strong> и теоретического улучшения позиций в выдаче 🙂</p><p>Итак, для добавления <strong>JSON-LD</strong> нам нужно:</p><ul><li><a href="#generate">Сформировать описание в формате <strong>JSON-LD</strong></a>;</li><li><a href="#add">Добавить его на страницу</a>;</li><li><a href="#test">Протестировать что всё работает</a>;</li></ul><p><a href="#result">Ссылка на конечный результат</a>.</p><h2 id="generate">Формирование JSON-LD </h2><p>Как не трудно догадаться из названия, <strong>JSON-LD</strong> - это <strong>JSON</strong> объект, составленный по определённой схеме. Для валидации схемы уже есть npm пакет <a href="https://www.npmjs.com/package/schema-dts">schema-dts</a>.</p><p>Если нужен быстрый черновик без кода — воспользуйтесь нашим <a href="https://baranov.guru/tools/json-ld-generator/">генератором JSON-LD</a>: выберите тип <strong>schema.org</strong> (<strong>BlogPosting</strong>, <strong>BreadcrumbList</strong>, <strong>FAQPage</strong> и др.), заполните поля и скопируйте готовый <strong>JSON</strong> или тег <strong>script</strong>.</p><div><p><img src="https://baranov.guru/assets/tools/json-ld-generator/cover.webp" alt="Генератор JSON-LD"></p><p><a href="https://baranov.guru/tools/json-ld-generator/">Генератор JSON-LD</a> — Соберите JSON-LD для страницы: BlogPosting, FAQPage, BreadcrumbList, Organization и другие типы schema.org — с копированием и тегом script.</p></div><p>Установим <strong>schema-dts</strong>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> i</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> schema-dts</span></span></code></pre><p>Далее создадим объект для поста <a href="https://baranov.guru">этого блога</a>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { BlogPosting</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> WithContext } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "schema-dts"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> HOME_OG_IMAGE_URL</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru/logo.webp"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> blogPosting</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> WithContext</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">BlogPosting</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  "@context"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "BlogPosting"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  headline</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.description</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  datePublished</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.toISOString</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  genre</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Technology"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  author</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Person"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">author</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.name</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/about`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  publisher</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Organization"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Алексей Баранов. Блог"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    logo</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ImageObject"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> HOME_OG_IMAGE_URL</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  image</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">ogImage</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  mainEntityOfPage</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "WebPage"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    "@id"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/posts/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  inLanguage</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ru-RU"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><p>Теперь необходимо сформировать <strong>BreadcrumbList</strong> для <strong>Навигационных цепочек</strong>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { BreadcrumbList</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> WithContext } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "schema-dts"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> breadcrumbList</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> WithContext</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">BreadcrumbList</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  "@context"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "BreadcrumbList"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  itemListElement</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ListItem"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      position</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 1</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Посты"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      item</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/posts`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ListItem"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      position</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 2</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      item</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/posts/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><p>Ну и на сладкое мы можем добавить информацию о видео содержащихся на странице:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { VideoObject</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> WithContext } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "schema-dts"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> videoStructuredData</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> WithContext</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">VideoObject</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  "@context"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "VideoObject"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.description</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  thumbnailUrl</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `https://i.ytimg.com/vi/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.youtubeId</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/hqdefault.jpg`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#C2C3C5;--shiki-dark:#6B737C"> // Миниатюра видео</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  uploadDate</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.toISOString</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  contentUrl</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `https://www.youtube.com/watch?v=</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.youtubeId</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#C2C3C5;--shiki-dark:#6B737C"> // Ссылка на видео на Youtube</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  embedUrl</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `https://www.youtube.com/embed/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.youtubeId</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#C2C3C5;--shiki-dark:#6B737C"> // Ссылка на встроенное видео с Youtube</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><p>Объединяем всё это вместе в один объект:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> jsonLd</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [blogPosting</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> breadcrumbList</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> videoStructuredData];</span></span></code></pre><h2 id="add">Добавление на страницу </h2><p>Для того чтобы добавить разметку на страницу, создадим компонент <strong>JsonLd</strong>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> React </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Props</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  data</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> unknown</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> JsonLd</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Props</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ({ data }) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">script</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    type</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"application/ld+json"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    dangerouslySetInnerHTML</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{{ __html</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> JSON</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.stringify</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(data) }}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> JsonLd;</span></span></code></pre><p>Далее просто добавляем этот компонент на страницу с постом и передаём в него данные:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> jsonLd</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [blogPosting</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> breadcrumbList</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> videoStructuredData];</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">JsonLd</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> data</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{jsonLd} /></span></span></code></pre><p>Теперь необходимо проверить что у нас всё получилось как надо.</p><h2 id="test">Тестирование </h2><p>Заходим на страницу и смотрим разметку:</p><p><img src="https://baranov.guru/assets/posts/nextjs-json-ld/result.webp" alt="Разметка страницы" title="Разметка страницы"></p><p>Итак, разметка появилась на странице.</p><p>Теперь необходимо проверить что разметка валидная. Для этого можно воспользоваться нашим инструментом:</p><div><p><img src="https://baranov.guru/assets/tools/json-ld-validator/cover.webp" alt="Валидатор JSON-LD"></p><p><a href="https://baranov.guru/tools/json-ld-validator/">Валидатор JSON-LD</a> — Проверка JSON-LD по URL страницы: извлечение блоков разметки, типы schema.org и замечания по обязательным полям.</p></div><p>Так же у Google есть <a href="https://search.google.com/test/rich-results">специальный инструмент для проверки</a> — вставьте URL страницы или HTML с уже сгенерированной разметкой (в том числе из <a href="https://baranov.guru/tools/json-ld-generator/">генератора</a>).</p><p><img src="https://baranov.guru/assets/posts/nextjs-json-ld/testing-tool.webp" alt="Инструмент для проверки" title="Инструмент для проверки"></p><p>Вбиваем в него <strong>адрес нашей страницы</strong>, если она уже где-то хостится, либо <strong>HTML-разметку</strong> страницы.</p><p><img src="https://baranov.guru/assets/posts/nextjs-json-ld/success.webp" alt="Результат проверки" title="Результат проверки"></p><p>Отлично, на этом всё! 🎉</p><p>Теперь выкладываем страницу и ждём индексации.</p><h2 id="result">Готовое решение </h2><p>Прикладываю полный код решения.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">// types.ts</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Author</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  picture</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Post</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  date</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  coverImage</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  author</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Author</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  excerpt</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ogImage</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  keywords</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  content</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  preview</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  youtubeId</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">// src/lib/jsonLd.ts</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  BlogPosting</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  BreadcrumbList</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  VideoObject</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  WithContext</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">} </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "schema-dts"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { Post } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@/interfaces/post"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { HOME_OG_IMAGE_URL</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> URL_BASE } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "./constants"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getPostJsonLd</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (post</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> blogPosting</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> WithContext</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">BlogPosting</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    "@context"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "BlogPosting"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    headline</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.description</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    datePublished</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.toISOString</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    genre</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Technology"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    author</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Person"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">author</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.name</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/about`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    publisher</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Organization"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Алексей Баранов. Блог"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      logo</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">        "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ImageObject"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> HOME_OG_IMAGE_URL</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    image</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">ogImage</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    mainEntityOfPage</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "WebPage"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "@id"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/posts/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    inLanguage</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ru-RU"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> breadcrumbList</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> WithContext</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">BreadcrumbList</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    "@context"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "BreadcrumbList"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    itemListElement</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">        "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ListItem"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        position</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 1</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Посты"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        item</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">URL_BASE</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/posts`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">        "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ListItem"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        position</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 2</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    ]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.youtubeId) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> videoStructuredData</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> WithContext</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">VideoObject</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "@context"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://schema.org"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "@type"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "VideoObject"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.description</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      thumbnailUrl</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `https://i.ytimg.com/vi/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.youtubeId</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/hqdefault.jpg`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#C2C3C5;--shiki-dark:#6B737C"> // this is the thumbnail for the video straight from youtube</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      uploadDate</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.toISOString</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      contentUrl</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `https://www.youtube.com/watch?v=</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.youtubeId</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#C2C3C5;--shiki-dark:#6B737C"> // this is the URL for the video on youtube</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      embedUrl</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `https://www.youtube.com/embed/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.youtubeId</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#C2C3C5;--shiki-dark:#6B737C"> // this is the URL for the video embed on youtube</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    };</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [blogPosting</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> breadcrumbList</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> videoStructuredData];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [blogPosting</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> breadcrumbList];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">// src/app/posts/[slug]/page.ts</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({ params }</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Params</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> jsonLd</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getPostJsonLd</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(post);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">JsonLd</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> data</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{jsonLd} /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      ...</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;/></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>На этом всё. Спасибо за внимание!</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/nextjs-json-ld/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем Яндекс Турбо-страницы к блогу на Next.js]]></title>
            <link>https://baranov.guru/posts/nextjs-yandex-turbo/</link>
            <guid>https://baranov.guru/posts/nextjs-yandex-turbo</guid>
            <pubDate>Fri, 17 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Для того чтобы красиво отображаться в поисковой выдаче Яндекса решили добавить Яндекс Турбо-страницы к блогу. Вот что из этого вышло.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ 7 февраля 2025 года <strong>Yandex</strong> анонсировал <a href="https://webmaster.yandex.ru/blog/yandex-stops-supporting-turbo-technology">прекращение поддержки Turbo страниц</a>.</p><p>Поэтому содержимое этой статьи <strong>устарело</strong> и оставлено здесь просто для истории.</p></aside><p>Для того чтобы красиво отображаться в поисковой выдаче <strong>Яндекса</strong> решил добавить <strong>Яндекс Турбо-страницы</strong> к <a href="https://baranov.guru">этому блогу</a>.</p><p>Вот так это выглядит:</p><p><img src="https://baranov.guru/assets/posts/nextjs-yandex-turbo/result.webp" alt="Турбо-страницы в поиске" title="Турбо-страницы в поиске"></p><p>Процесс этот простой и проходит в 2 основных шага:</p><ul><li><a href="#generate">Сгенерировать фид</a>;</li><li><a href="#add">Добавить информацию о нём в Яндекс Вебмастер</a>;</li></ul><h2 id="generate">Генерация фида </h2><p>Процесс генерации фида почти ничем не отличается от <a href="https://baranov.guru/posts/nextjs-rss">генерации RSS фида</a>.</p><p>Для того чтобы сгенерировать фид, необходимо:</p><ul><li><a href="#frontmatter">получить список всех постов и их метаданные</a>,</li><li><a href="#turbo">сформировать Tурбофид в специальном формате</a></li><li><a href="#build">делать так при каждой сборке</a></li></ul><h3 id="frontmatter">Получение постов и их метаданных </h3><p>Так как посты у меня хранятся в репозитории проекта в формате <strong>.md</strong>, то для получения всех постов мне просто необходимо:</p><ul><li>получить список файлов в директории с постами,</li><li>пройти по каждому посту и получить метаданные</li></ul><p>Для получения метаданных я воспользовался пакетом <a href="https://www.npmjs.com/package/gray-matter">gray-matter</a></p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> i</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> gray-matter</span></span></code></pre><p>В результате получился такой код:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> fs </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "fs"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { join } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "path"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> matter </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "gray-matter"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { Post } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@/interfaces/post"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> postsDirectory</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> join</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">process</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.cwd</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "_posts"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getPostBySlug</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> realSlug</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> slug</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.replace</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">/\.md</span><span style="color:#D32F2F;--shiki-dark:#F97583">$</span><span style="color:#22863A;--shiki-dark:#FFAB70">/</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> ""</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> fullPath</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> join</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(postsDirectory</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">realSlug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">.md`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> fileContents</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> fs</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.readFileSync</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(fullPath</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "utf8"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { </span><span style="color:#1976D2;--shiki-dark:#79B8FF">data</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> content</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> } </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> matter</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(fileContents);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { </span><span style="color:#D32F2F;--shiki-dark:#F97583">...</span><span style="color:#24292EFF;--shiki-dark:#B392F0">data</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> realSlug</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> content } </span><span style="color:#D32F2F;--shiki-dark:#F97583">as</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllPosts</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[] {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> slugs</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> fs</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.readdirSync</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(postsDirectory);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> posts</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> slugs</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .map</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((slug) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getPostBySlug</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(slug))</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">    // сортируем посты по дате в порядке убывания</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .sort</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((a</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> b) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#1976D2;--shiki-dark:#79B8FF">a</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date </span><span style="color:#D32F2F;--shiki-dark:#F97583">></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> b</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date </span><span style="color:#D32F2F;--shiki-dark:#F97583">?</span><span style="color:#D32F2F;--shiki-dark:#F97583"> -</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">1</span><span style="color:#D32F2F;--shiki-dark:#F97583"> :</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 1</span><span style="color:#24292EFF;--shiki-dark:#B392F0">));</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> posts;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h3 id="turbo">Формирование Турбо фида </h3><p>Для того чтобы добавить <strong>RSS фид</strong> я воспользовался пакетом <a href="https://www.npmjs.com/package/turbo-rss">turbo-rss</a>.</p><p>Устанавливаем его командой:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> i</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> turbo-rss</span></span></code></pre><p>Алгоритм следующий:</p><ul><li>сформировать описание фида,</li><li>пройти по списку постов, и сформировать feedItem для каждого поста,</li><li>записать всё в .xml файл</li></ul><p>У меня получился вот такой скрипт:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> fs </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "fs"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> path </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "node:path"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { getAllPosts } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "../api"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { markdownToTurboHtml } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "../markdownToHtml"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">// eslint-disable-next-line @typescript-eslint/no-var-requires</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> TR</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"turbo-rss"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> generateTurboFeed</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Generating Turbo feed..."</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> allPosts</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllPosts</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`Found </span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">allPosts</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">length</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70"> posts.`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> feedOptions</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    language</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ru"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Baranov.Guru | RSS feed"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "Личный блог. Пишу о разработке софта, предпринимательстве и просто об интересных мне вещах"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    id</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> site_url</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    link</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> site_url</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    image</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/assets/authors/avatar.webp`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    favicon</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/favicon/favicon.ico`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    copyright</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `All rights reserved </span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#D32F2F;--shiki-dark:#F97583">new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.getFullYear</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">, Baranov.Guru`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    generator</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Feed for Node.js"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    feedLinks</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      rss2</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/rss.xml`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> feed</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TR</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(feedOptions);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  for</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#D32F2F;--shiki-dark:#F97583"> of</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> allPosts) {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`Adding turbo item for post </span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> content</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> markdownToTurboHtml</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.content);</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    feed</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.item</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      id</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/posts/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.description</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      image_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.coverImage</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/posts/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      author</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">author</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.name</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      date</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date)</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      content</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> content</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      menu</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          link</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "http://alexeybaranov.dev"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          text</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Главная страница"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      ]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    });</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> feedPath</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> path</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.resolve</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"./out/turbo.xml"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  fs</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.writeFileSync</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(feedPath</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> feed</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.xml</span><span style="color:#24292EFF;--shiki-dark:#B392F0">());</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`Rss feed is written to </span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">feedPath</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">.`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h3 id="build">Автоматизация сборки фида </h3><p>Добавляем в package.json скрипт <strong>postbuild</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "scripts"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "build"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next build"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "postbuild"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "npx tsx ./src/lib/feeds/generate.ts"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Он запустится сразу же после <strong>успешной сборки</strong> проекта и запишет фид в папку с результатами сборки.</p><h2 id="add">Добавление информации о фиде в Яндекс Вебмастер </h2><p>Теперь осталось только добавить информацию о фиде в поисковую машину <strong>Яндекса</strong>.</p><p>Заходим в <a href="https://webmaster.yandex.ru">Яндекс Вебмастер</a>.</p><p>Переходим на вкладку <strong>Турбо-страницы для контентных сайтов</strong> --> <strong>Источники</strong>.</p><p><img src="https://baranov.guru/assets/posts/nextjs-yandex-turbo/webmaster.webp" alt="Добавление через Вебмастер" title="Добавление через Вебмастер"></p><p>Добавляем фид удобным нам способом:</p><ul><li>Руками, через форму;</li><li>Через <strong>API</strong>;</li></ul><p>Добавить фид надо всего 1 раз, поэтому я не стал заморачиваться с API и просто добавил ссылку на фид руками.</p><p>Всё готово! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/nextjs-yandex-turbo/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Подключение счётчика Яндекс Метрики к Next.js приложению]]></title>
            <link>https://baranov.guru/posts/nextjs-yandex-metrika/</link>
            <guid>https://baranov.guru/posts/nextjs-yandex-metrika</guid>
            <pubDate>Wed, 15 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению счётчика Яндекс Метрики к Next.js блогу.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Одним из важнейших аспектов поддержки любого сайта является работа с аналитикой.</p><p>Аналитические <strong>счётчики</strong> для сайтов играют ключевую роль в понимании поведения посетителей и оптимизации веб-ресурсов. Они позволяют владельцам сайтов собирать ценные <strong>данные о посетителях</strong>, <strong>источниках переходов</strong>, <strong>поведении пользователей</strong> на страницах сайта и многом другом.</p><p>В этой статье мы расскажем о нескольких способах подключения счётчика <strong>Яндекс Метрики</strong> к <strong>Next.js</strong> приложению.</p><p>Однако, все эти способы <strong>очень похожи</strong> и для того чтобы лучше их понять и грамотнее их использовать, начнём с написания <strong>собственного решения</strong>.</p><h2 id="own-solution">Написание собственного решения </h2><p>Итак, нам нужно:</p><ul><li><a href="#own-solution-create-component">создать компонент, который будет загружать счётчик на страницу</a>,</li><li><a href="#own-solution-add-to-layout">добавить этот компонент в Root Layout</a>,</li><li><a href="#own-solution-change-route">научиться обрабатывать события перехода между страницами</a>,</li><li><a href="#own-solution-handle-events">а также научиться прокидывать любые другие события в счётчик</a></li></ul><h3 id="own-solution-create-component">Создание компонента </h3><p>Итак, наш компонент должен:</p><ul><li>Загрузить скрипт <strong>Яндекс Метрики</strong> с указанными настройками;</li><li>Предоставить методы для работы с <strong>API Яндекс Метрики</strong>;</li></ul><h4>Загрузка скрипта</h4><p>Добавим компонент <strong>YandexMetrikaInitializer</strong>.</p><p>Он будет принимать <strong>id</strong> счётчика и <a href="#own-solution-init-params">набор параметров счётчика</a> и рендерить <strong>Script</strong>-компонент, с кодом загрузки счётчика метрики с указанными параметрами.</p><p>Это тот самый код, который можно получить в <a href="https://metrika.yandex.ru/settings">личном кабинете Яндекс Метрики</a> при создании счётчика.</p><p>В итоге должно получиться нечто похожее:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">"use client"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> Script </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next/script"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> React </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { YandexMetrikaInitParameters } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "./types"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Props</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  id</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  initParameters</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaInitParameters</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaInitializer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Props</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ({ id</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> initParameters }) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">  /* eslint-disable @next/next/no-img-element */</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Script</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> type</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"text/javascript"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#22863A;--shiki-dark:#FFAB70">`ym_</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">id</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        {</span><span style="color:#22863A;--shiki-dark:#FFAB70">`(function (m, e, t, r, i, k, a) {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  m[i] =</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    m[i] ||</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    function () {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      (m[i].a = m[i].a || []).push(arguments);</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    };</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  m[i].l = 1 * new Date();</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  for (var j = 0; j &#x3C; document.scripts.length; j++) {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    if (document.scripts[j].src === r) {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      return;</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    }</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  }</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">  (k = e.createElement(t)),</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    (a = e.getElementsByTagName(t)[0]),</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    (k.async = 1),</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    (k.src = r),</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">    a.parentNode.insertBefore(k, a);</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">})(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");</span></span>
<span class="line"></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">ym(</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">id</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">, "init", </span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">JSON</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.stringify</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(initParameters)</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">);`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;/</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Script</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">noscript</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">div</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">img</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">            src</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#22863A;--shiki-dark:#FFAB70">`https://mc.yandex.ru/watch/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">id</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">            style</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{{ position</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "absolute"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> left</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "-9999px;"</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">            alt</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">""</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">          /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">div</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">noscript</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> MyYandexMetrikaInitializer;</span></span></code></pre><h4 id="own-solution-init-params">Набор параметров счётчика </h4><p>Вот список параметров счётчика, взятый из <a href="https://yandex.ru/support/metrica/code/counter-initialize.html?lang=ru">официальной доки</a>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaInitParameters</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  accurateTrackBounce</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  childIframe</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  clickmap</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  defer</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ecommerce</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  params</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> unknown</span><span style="color:#D32F2F;--shiki-dark:#F97583"> |</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  userParams</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> unknown</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  trackHash</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  trackLinks</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  trustedDomains</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[];</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  type</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  webvisor</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  triggerEvent</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  sendTitle</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><h3 id="own-solution-add-to-layout">Добавление компонента в Root Layout </h3><p>Теперь нам надо добавить <a href="#own-solution-create-component">наш компонент</a> в <strong>Root Layout</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> RootLayout</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  children</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Readonly</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;{</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  children</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ReactNode</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}>) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">html</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">head</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        ...</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YandexMetrikaInitializer</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">          id</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{id}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">          initParameters</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{{ webvisor</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> defer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">head</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">html</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Обратите внимание, что инициализировать компонент лучше внутри тега <strong>head</strong>.</p><h3 id="own-solution-change-route">Обработка перехода между страницами </h3><p>При переходе между страницами <strong>Next.js</strong> не всегда перерендеривает весь <strong>Layout</strong>.</p><p>Это одна из встроенных в фреймворк <strong>оптимизаций</strong>, она позволяет <strong>снизить время перехода</strong> между страницами приложения.</p><p>Однако нам сейчас это поведение <strong>вредит и не позволяет</strong> полноценно отслеживать переходы посетителя между страницами.</p><p>Как это победить? Надо просто подписаться на <strong>события изменения URL</strong>. Сделать это можно используя хуки <strong>usePathname</strong> и <strong>useSearchParams</strong> встроенного пакета <strong>next/navigation</strong>, а также воспользовавшись <strong>react-хуком useEffect</strong>.</p><p>Обернём наш компонент для инициализации в контейнер. Назовём его <strong>YandexMetrikaContainer</strong> и подпишемся на изменения <strong>URL</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">"use client"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { usePathname</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> useSearchParams } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next/navigation"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> React</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { useEffect } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { YM_COUNTER_ID } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "./constants"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> YandexMetrikaInitializer </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "./YandexMetrikaInitializer"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Props</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  enabled</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaContainer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Props</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ({ enabled }) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> pathname</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> usePathname</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> search</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> useSearchParams</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  useEffect</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(() </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">pathname</span><span style="color:#D32F2F;--shiki-dark:#F97583">}${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">search</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.size </span><span style="color:#D32F2F;--shiki-dark:#F97583">?</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `?</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">search</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#D32F2F;--shiki-dark:#F97583"> :</span><span style="color:#22863A;--shiki-dark:#FFAB70"> ""</span><span style="color:#D32F2F;--shiki-dark:#F97583">}${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">window</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">location</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.hash</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [hit</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> pathname</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> search]);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">!</span><span style="color:#24292EFF;--shiki-dark:#B392F0">enabled) </span><span style="color:#D32F2F;--shiki-dark:#F97583">return</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> null</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YandexMetrikaInitializer</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      id</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YM_COUNTER_ID</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      initParameters</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{{ webvisor</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> defer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> YandexMetrikaContainer;</span></span></code></pre><p>Отлично, теперь мы можем видеть в консоли сообщение о том что <strong>адрес страницы изменился</strong>. Но как передать эту информацию в счётчик?</p><p>Для этого нужно воспользоваться одним из встроенных методов <strong>Яндекс Метрики</strong> под названием <a href="https://yandex.ru/support/metrica/objects/hit.html"><strong>hit</strong></a>.</p><p>Для удобства, давайте обернём вызов этого метода в хук и назовём его <strong>useYandexMetrika</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { YandexMetrikaHitOptions</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> YandexMetrikaMethod } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "./types"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">declare</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ym</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  id</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  method</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaMethod</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  ...</span><span style="color:#24292EFF;--shiki-dark:#B392F0">params</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> unknown</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[]</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> void</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> enabled</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> !!</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">env</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">NODE_ENV</span><span style="color:#D32F2F;--shiki-dark:#F97583"> ===</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "production"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> useYandexMetrika</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (id</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> hit</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (url</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> options</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaHitOptions</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (enabled) {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      ym</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(id</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "hit"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> url</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> options);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    } </span><span style="color:#D32F2F;--shiki-dark:#F97583">else</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">      console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`%c[YandexMetrika](hit)`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `color: orange`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> url);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { hit };</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> useYandexMetrika;</span></span></code></pre><p>Вот так выглядит описание типов <strong>YandexMetrikaHitOptions</strong> и <strong>YandexMetrikaHitParams</strong>:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaHitOptions</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  callback</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> void</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ctx</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> unknown</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  params</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaHitParams</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  referer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaHitParams</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  order_price</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  currency</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><p>Теперь используем наш хук при переходе между страницами, должно получиться примерно так:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">"use client"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { usePathname</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> useSearchParams } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next/navigation"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> React</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { useEffect } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { YM_COUNTER_ID } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "./constants"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> useYandexMetrika </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "./useYandexMetrika"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> YandexMetrikaInitializer </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "./YandexMetrikaInitializer"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Props</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  enabled</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaContainer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Props</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ({ enabled }) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> pathname</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> usePathname</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> search</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> useSearchParams</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { </span><span style="color:#1976D2;--shiki-dark:#79B8FF">hit</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> } </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> useYandexMetrika</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YM_COUNTER_ID</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  useEffect</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(() </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    hit</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">pathname</span><span style="color:#D32F2F;--shiki-dark:#F97583">}${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">search</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.size </span><span style="color:#D32F2F;--shiki-dark:#F97583">?</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `?</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">search</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#D32F2F;--shiki-dark:#F97583"> :</span><span style="color:#22863A;--shiki-dark:#FFAB70"> ""</span><span style="color:#D32F2F;--shiki-dark:#F97583">}${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">window</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">location</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.hash</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [hit</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> pathname</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> search]);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">!</span><span style="color:#24292EFF;--shiki-dark:#B392F0">enabled) </span><span style="color:#D32F2F;--shiki-dark:#F97583">return</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> null</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YandexMetrikaInitializer</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      id</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YM_COUNTER_ID</span><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      initParameters</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{{ webvisor</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> defer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> }}</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> YandexMetrikaContainer;</span></span></code></pre><p><strong>Важно!</strong> Если вы используете серверный рендеринг, не забудьте обернуть <strong>YandexMetrikaContainer</strong> в <strong>Suspense-тег</strong> для того чтобы не сломать его.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { Suspense } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">...</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Suspense</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YandexMetrikaContainer</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> enabled</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/</span><span style="color:#1976D2;--shiki-dark:#79B8FF">Suspense</span><span style="color:#24292EFF;--shiki-dark:#B392F0">>;</span></span></code></pre><h3 id="own-solution-handle-events">Отправка событий в счётчик </h3><p>Отправить любое другое событие в счётчик можно похожим образом.</p><p>Просто расширим хук <strong>useYandexMetrika</strong> новыми методами, например для использования события <a href="https://yandex.ru/support/metrica/objects/reachgoal.html"><strong>reachGoal</strong></a>, можно добавить следующий метод:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> reachGoal</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  target</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  params</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> unknown</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  callback</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> void</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ctx</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> unknown</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (enabled) {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    ym</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(id</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "reachGoal"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> target</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> params</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> callback</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ctx);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  } </span><span style="color:#D32F2F;--shiki-dark:#F97583">else</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`%c[YandexMetrika](reachGoal)`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `color: orange`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> target);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><p>В итоге код нашего хука будет выглядеть следующим образом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { YandexMetrikaHitOptions</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> YandexMetrikaMethod } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "./types"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">declare</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ym</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  id</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  method</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaMethod</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  ...</span><span style="color:#24292EFF;--shiki-dark:#B392F0">params</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> unknown</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[]</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> void</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> enabled</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> !!</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">env</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">NODE_ENV</span><span style="color:#D32F2F;--shiki-dark:#F97583"> ===</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "production"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> useYandexMetrika</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (id</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> number</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> hit</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (url</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> options</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaHitOptions</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (enabled) {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      ym</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(id</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "hit"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> url</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> options);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    } </span><span style="color:#D32F2F;--shiki-dark:#F97583">else</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">      console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`%c[YandexMetrika](hit)`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `color: orange`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> url);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> reachGoal</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    target</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    params</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> unknown</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    callback</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> void</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    ctx</span><span style="color:#D32F2F;--shiki-dark:#F97583">?:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> unknown</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  ) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">    if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (enabled) {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      ym</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(id</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "reachGoal"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> target</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> params</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> callback</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ctx);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    } </span><span style="color:#D32F2F;--shiki-dark:#F97583">else</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">      console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`%c[YandexMetrika](reachGoal)`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `color: orange`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> target);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { hit</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> reachGoal };</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> useYandexMetrika;</span></span></code></pre><p>Использовать <a href="https://yandex.ru/support/metrica/objects/method-reference.html">другие методы</a> можно аналогичным образом.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaMethod</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "init"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "hit"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "addFileExtension"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "extLink"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "file"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "firstPartyParams"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "firstPartyParamsHashed"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "getClientID"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "notBounce"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "params"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "reachGoal"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "setUserID"</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  |</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "userParams"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span></code></pre><p>На этом всё, по большому счёту, для использования <strong>Яндекс Метрики</strong> в <strong>Next.js</strong> приложении больше ничего делать не надо.</p><h2 id="react-yandex-metrika">Использование пакета react-yandex-metrika </h2><p>Можно написать своё решение, а можно воспользоваться одним из уже готовых пакетов, например <a href="https://www.npmjs.com/package/react-yandex-metrika"><strong>react-yandex-metrika</strong></a>.</p><p>Итак, для этого нам нужно:</p><ul><li><a href="#rym-create-component">создать компонент, который будет загружать счётчик и передавать ему события</a>,</li><li><a href="#rym-subscribe">подписаться на события перехода между страницами</a>,</li><li><a href="#rym-add-to-layout">добавить компонент в Root Layout</a>,</li><li><a href="#rym-dev">не прокидывать события при локальной отладке страниц</a></li></ul><h3 id="rym-create-component">Создание компонента </h3><p>Сначала установим пакет:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> i</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> react-yandex-metrika</span></span></code></pre><p>Далее нам необходимо создать компонент, назовём его <strong>YandexMetrikaContainer</strong>.</p><p>Так как счётчик должен быть <a href="https://nextjs.org/docs/app/building-your-application/rendering/client-components">Клиентским компонентом</a>, то в файл с компонентом, перед всеми импортами, необходимо добавить директиву <a href="https://react.dev/reference/rsc/use-client">"use client"</a>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">"use client"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span></code></pre><p>Далее создаём простой функциональный компонент, использующий <strong>YMInitializer</strong> из пакета <strong>react-yandex-metrika</strong>.</p><p>Прокидываем в него номер счётчика и другие необходимые нам настройки счётчика.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">"use client"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> React </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { YMInitializer } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react-yandex-metrika"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> YM_COUNTER_ID</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 12345678</span><span style="color:#24292EFF;--shiki-dark:#B392F0">; </span><span style="color:#C2C3C5;--shiki-dark:#6B737C">//Не настоящий :)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaContainer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> () </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YMInitializer</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      accounts</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{[</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YM_COUNTER_ID</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      options</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{{</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        defer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        webvisor</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        clickmap</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        trackLinks</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        accurateTrackBounce</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      version</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"2"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> YandexMetrikaContainer;</span></span></code></pre><p>Но это ещё не всё, помимо загрузки счётчика, нам нужно отправить в него событие попадания на страницу.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ym </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react-yandex-metrika"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> hit</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  ym</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"hit"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> url);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">useEffect</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(() </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  hit</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">window</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">location</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.pathname </span><span style="color:#D32F2F;--shiki-dark:#F97583">+</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> window</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">location</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.search);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> []);</span></span></code></pre><p>На этом бы можно было остановиться, но при переходе между страницами <strong>Next.js</strong> не рендерит весь <strong>Root Layout</strong>, поэтому событие будет вызвано только 1 раз.</p><p>Что бы исправить ситуацию, нам нужно подписаться на событие перехода между страницами.</p><h3 id="rym-subscribe">Подписка на события перехода по страницам </h3><p><strong>Next.js</strong> Содержит в себе пакет <strong>Router</strong>, который позволяет подписаться на нужное нам событие.</p><p>Выглядит это следующим образом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> Router </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next/router"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">Router</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">events</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.on</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"routeChangeComplete"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> callback);</span></span></code></pre><p>В итоге у нас должно получиться вот так:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> Router </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next/router"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ym </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react-yandex-metrika"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> hit</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  ym</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"hit"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> url);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">useEffect</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(() </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  hit</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">window</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">location</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.pathname </span><span style="color:#D32F2F;--shiki-dark:#F97583">+</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> window</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">location</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.search);</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  Router</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">events</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.on</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"routeChangeComplete"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#6F42C1;--shiki-dark:#B392F0"> hit</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(url));</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> []);</span></span></code></pre><p>Теперь настало время добавить компонент в <strong>Root Layout</strong>.</p><h3 id="rym-add-to-layout">Добавление в Root Layout </h3><p>Тут всё довольно просто, я решил добавить компонент после закрывающего тега <strong>&#x3C;/body></strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> RootLayout</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  children</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Readonly</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;{</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  children</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ReactNode</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}>) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">html</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> lang</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"ru"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> className</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"scroll-smooth"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">head</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        ...</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">head</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">body</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> className</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">inter</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.className}></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        ...</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">body</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YandexMetrikaContainer</span><span style="color:#24292EFF;--shiki-dark:#B392F0">/></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">html</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h3 id="rym-dev">Работа в Dev режиме </h3><p>Также мне не нужно чтобы счётчик срабатывал когда я запускаю приложение в режиме отладки, поэтому неплохо бы добавить какой-то признак того что мы в режиме отладки</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> analyticsEnabled</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> !!</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">env</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">NODE_ENV</span><span style="color:#D32F2F;--shiki-dark:#F97583"> ===</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "production"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span></code></pre><p>В итоге использование компонента будет выглядеть следующим образом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YandexMetrikaContainer</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> enabled</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{analyticsEnabled} /></span></span></code></pre><h3 id="rym-final">Итоговый результат </h3><p>Итоговый код компонента <strong>YandexMetrikaContainer</strong></p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">"use client"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> Router </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next/router"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> React</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { useCallback</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> useEffect } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ym</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { YMInitializer } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "react-yandex-metrika"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Props</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  enabled</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> boolean</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> YM_COUNTER_ID</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 12345678</span><span style="color:#24292EFF;--shiki-dark:#B392F0">; </span><span style="color:#C2C3C5;--shiki-dark:#6B737C">//Не настоящий :)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> YandexMetrikaContainer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Props</span><span style="color:#24292EFF;--shiki-dark:#B392F0">> </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> ({ enabled }) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> hit</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> useCallback</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    (url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">      if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (enabled) {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">        ym</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"hit"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> url);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      } </span><span style="color:#D32F2F;--shiki-dark:#F97583">else</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">        console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`%c[YandexMetrika](HIT)`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `color: orange`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> url);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    [enabled]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  useEffect</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(() </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    hit</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">window</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">location</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.pathname </span><span style="color:#D32F2F;--shiki-dark:#F97583">+</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> window</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">location</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.search);</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    Router</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">events</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.on</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"routeChangeComplete"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (url</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#6F42C1;--shiki-dark:#B392F0"> hit</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(url));</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [hit]);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  if</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#D32F2F;--shiki-dark:#F97583">!</span><span style="color:#24292EFF;--shiki-dark:#B392F0">enabled) </span><span style="color:#D32F2F;--shiki-dark:#F97583">return</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> null</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YMInitializer</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      accounts</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{[</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YM_COUNTER_ID</span><span style="color:#24292EFF;--shiki-dark:#B392F0">]}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      options</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{{</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        defer</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        webvisor</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        clickmap</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        trackLinks</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        accurateTrackBounce</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      }}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">      version</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"2"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> YandexMetrikaContainer;</span></span></code></pre><p>Итоговый код компонента <strong>Root Layout</strong></p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> analyticsEnabled</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> !!</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">process</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">env</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">NODE_ENV</span><span style="color:#D32F2F;--shiki-dark:#F97583"> ===</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "production"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> RootLayout</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  children</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Readonly</span><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;{</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  children</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ReactNode</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}>) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">html</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> lang</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"ru"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> className</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"scroll-smooth"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">head</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        ...</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">head</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">body</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> className</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span><span style="color:#1976D2;--shiki-dark:#79B8FF">inter</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.className}></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">        ...</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">body</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      &#x3C;</span><span style="color:#1976D2;--shiki-dark:#79B8FF">YandexMetrikaContainer</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> enabled</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#24292EFF;--shiki-dark:#B392F0">{analyticsEnabled} /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    &#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">html</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  );</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h2 id="alternatives">Альтернативные решения </h2><p>Вот ещё пара похожих пакетов <a href="https://www.npmjs.com/package/react-metrika">react-metrika</a> и <a href="https://www.npmjs.com/package/next-yandex-metrika">next-yandex-metrika</a>.</p><p>Все они делают примерно одно и то же. Подробно останавливаться на них не вижу смысла.</p><p>На этом всё, спасибо за внимание! 🎉</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/nextjs-yandex-metrika/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Добавляем RSS-фид к статическому Next.js приложению]]></title>
            <link>https://baranov.guru/posts/nextjs-rss/</link>
            <guid>https://baranov.guru/posts/nextjs-rss</guid>
            <pubDate>Tue, 14 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция по добавлению RSS фида к статическому Next.js приложению.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Так как формат <strong>RSS фидов</strong> ещё жив, решили добавить фид и для <a href="https://baranov.guru">этого блога</a>.</p><p>Процесс этот простой и проходит в 2 основных шага:</p><ul><li><a href="#generate">Сгенерировать фид</a>;</li><li><a href="#add">Добавить информацию о нём на страницы сайта</a>;</li></ul><h2 id="generate">Генерация RSS фида </h2><p>Для того чтобы сгенерировать фид, необходимо:</p><ul><li><a href="#frontmatter">получить список всех постов и их метаданные</a>,</li><li><a href="#xml">сформировать RSS фид в формате .XML</a></li><li><a href="#build">делать так при каждой сборке</a></li></ul><h3 id="frontmatter">Получение постов и их метаданных </h3><p>Так как посты у нас хранятся в репозитории проекта в формате <strong>.md</strong>, то для получения всех постов нам просто необходимо:</p><ul><li>получить <strong>список файлов</strong> в директории с постами,</li><li>пройти по каждому посту и получить <strong>метаданные</strong></li></ul><p>Для получения метаданных я воспользовался пакетом <a href="https://www.npmjs.com/package/gray-matter">gray-matter</a></p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> i</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> gray-matter</span></span></code></pre><p>В результате получился такой код:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> fs </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "fs"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { join } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "path"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> matter </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "gray-matter"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { Post } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "@/interfaces/post"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> postsDirectory</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> join</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">process</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.cwd</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "_posts"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getPostBySlug</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> string</span><span style="color:#24292EFF;--shiki-dark:#B392F0">) {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> realSlug</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> slug</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.replace</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">/\.md</span><span style="color:#D32F2F;--shiki-dark:#F97583">$</span><span style="color:#22863A;--shiki-dark:#FFAB70">/</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> ""</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> fullPath</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> join</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(postsDirectory</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">realSlug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">.md`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> fileContents</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> fs</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.readFileSync</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(fullPath</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "utf8"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { </span><span style="color:#1976D2;--shiki-dark:#79B8FF">data</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> content</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> } </span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> matter</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(fileContents);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { </span><span style="color:#D32F2F;--shiki-dark:#F97583">...</span><span style="color:#24292EFF;--shiki-dark:#B392F0">data</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> realSlug</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> content } </span><span style="color:#D32F2F;--shiki-dark:#F97583">as</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllPosts</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">[] {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> slugs</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> fs</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.readdirSync</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(postsDirectory);</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> posts</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> slugs</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .map</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((slug) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getPostBySlug</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(slug))</span></span>
<span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">    // сортируем посты по дате в порядке убывания</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    .sort</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((a</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> b) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> (</span><span style="color:#1976D2;--shiki-dark:#79B8FF">a</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date </span><span style="color:#D32F2F;--shiki-dark:#F97583">></span><span style="color:#1976D2;--shiki-dark:#79B8FF"> b</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date </span><span style="color:#D32F2F;--shiki-dark:#F97583">?</span><span style="color:#D32F2F;--shiki-dark:#F97583"> -</span><span style="color:#1976D2;--shiki-dark:#F8F8F8">1</span><span style="color:#D32F2F;--shiki-dark:#F97583"> :</span><span style="color:#1976D2;--shiki-dark:#F8F8F8"> 1</span><span style="color:#24292EFF;--shiki-dark:#B392F0">));</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  return</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> posts;</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h3 id="xml">Формирование .XML фида </h3><p>Для того чтобы добавить <strong>RSS фид</strong> мы воспользовались пакетом <a href="https://www.npmjs.com/package/feed">feed</a>.</p><p>Устанавливаем его командой:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> i</span><span style="color:#2B5581;--shiki-dark:#9DB1C5"> feed</span></span></code></pre><p>Алгоритм следующий:</p><ul><li>сформировать описание фида,</li><li>пройти по списку постов, и сформировать feedItem для каждого поста,</li><li>записать всё в .xml файл</li></ul><p>У нас получился вот такой скрипт:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> fs </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "fs"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { Feed } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "feed"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> path </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "node:path"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">import</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> { getAllPosts } </span><span style="color:#D32F2F;--shiki-dark:#F97583">from</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "../api"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> default</span><span style="color:#D32F2F;--shiki-dark:#F97583"> async</span><span style="color:#D32F2F;--shiki-dark:#F97583"> function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> generateRssFeed</span><span style="color:#24292EFF;--shiki-dark:#B392F0">() {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"Generating RSS feed..."</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> allPosts</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> getAllPosts</span><span style="color:#24292EFF;--shiki-dark:#B392F0">();</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`Found </span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">allPosts</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">length</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70"> posts.`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "https://baranov.guru"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> feedOptions</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    language</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "ru"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Baranov.Guru | RSS feed"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "Личный блог. Пишу о разработке софта, предпринимательстве и просто об интересных мне вещах"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    id</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> site_url</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    link</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> site_url</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    image</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/assets/authors/avatar.webp`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    favicon</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/favicon/favicon.ico`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    copyright</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `All rights reserved </span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#D32F2F;--shiki-dark:#F97583">new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.getFullYear</span><span style="color:#24292EFF;--shiki-dark:#B392F0">()</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">, Baranov.Guru`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    generator</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "Feed for Node.js"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    feedLinks</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      rss2</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/rss.xml`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> feed</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Feed</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(feedOptions);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  allPosts</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.forEach</span><span style="color:#24292EFF;--shiki-dark:#B392F0">((post) </span><span style="color:#D32F2F;--shiki-dark:#F97583">=></span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`Adding rss item for post </span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">    feed</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.addItem</span><span style="color:#24292EFF;--shiki-dark:#B392F0">({</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      title</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.title</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      id</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/posts/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      link</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">/posts/</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.slug</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      image</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> `</span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">site_url</span><span style="color:#D32F2F;--shiki-dark:#F97583">}${</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.coverImage</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">`</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      author</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> [{ name</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">author</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.name }]</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      description</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.description</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">      date</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#D32F2F;--shiki-dark:#F97583"> new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#1976D2;--shiki-dark:#79B8FF">post</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.date)</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    });</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">  const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> feedPath</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> path</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.resolve</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">"./out/feed.xml"</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  fs</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.writeFileSync</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(feedPath</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> feed</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.rss2</span><span style="color:#24292EFF;--shiki-dark:#B392F0">());</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">  console</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.log</span><span style="color:#24292EFF;--shiki-dark:#B392F0">(</span><span style="color:#22863A;--shiki-dark:#FFAB70">`Rss feed is written to </span><span style="color:#D32F2F;--shiki-dark:#F97583">${</span><span style="color:#24292EFF;--shiki-dark:#B392F0">feedPath</span><span style="color:#D32F2F;--shiki-dark:#F97583">}</span><span style="color:#22863A;--shiki-dark:#FFAB70">.`</span><span style="color:#24292EFF;--shiki-dark:#B392F0">);</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><h3 id="build">Автоматизация сборки фида </h3><p>Добавляем в package.json скрипт <strong>postbuild</strong>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">{</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">  "scripts"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "build"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "next build"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F8F8F8">    "postbuild"</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "npx tsx ./src/lib/feeds/generate.ts"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">}</span></span></code></pre><p>Он запустится сразу же после <strong>успешной сборки</strong> проекта и запишет фид в папку с результатами сборки.</p><h2 id="add">Добавление информации о фиде на страницы сайта </h2><p>Теперь осталось только добавить информацию о фиде на страницы сайта, для того чтобы различные расширения и боты могли легко найти его и отобразить для пользователей.</p><p>В файл с <strong>Root Layout</strong> нашего приложения добавляем следующий код:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">export</span><span style="color:#D32F2F;--shiki-dark:#F97583"> const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> metadata</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Metadata</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  alternates</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    types</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#FFAB70">      "application/rss+xml"</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "/feed.xml"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">    }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  }</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span></code></pre><p>Если вы всё сделали правильно, то в <strong>&#x3C;head></strong> элементе вашей страницы появится следующая строчка:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">head</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  &#x3C;</span><span style="color:#22863A;--shiki-dark:#FFAB70">link</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    rel</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"alternate"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    type</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"application/rss+xml"</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    href</span><span style="color:#D32F2F;--shiki-dark:#F97583">=</span><span style="color:#22863A;--shiki-dark:#FFAB70">"https://baranov.guru/feed.xml"</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  /></span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">&#x3C;/</span><span style="color:#22863A;--shiki-dark:#FFAB70">head</span><span style="color:#24292EFF;--shiki-dark:#B392F0">></span></span></code></pre><p>Всё готово! 🎉</p><h2>P.S.</h2><p>Вот ссылка на наш <a href="https://baranov.guru/feed.xml">RSS фид</a>.</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/nextjs-rss/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Next.js + Yandex Object Storage = Не работают ссылки]]></title>
            <link>https://baranov.guru/posts/yandex-object-storage-nextjs/</link>
            <guid>https://baranov.guru/posts/yandex-object-storage-nextjs</guid>
            <pubDate>Mon, 13 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Очень часто при попытке хостить Next.js приложение использующее App Router в S3 бакете можно столкнуться с тем что страницы приложения не открываются при заходе по прямой ссылке.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Размещая <a href="https://baranov.guru">этот блог</a> в <a href="https://baranov.guru/posts/yandex-object-storage"><strong>Yandex Object Storage</strong></a> мы столкнулись с некоторыми проблемами.</p><p>В этой статье мы расскажем об одной из них.</p><h2 id="problem">Проблема </h2><p>Очень часто при попытке хостить <strong>Next.js</strong> приложение использующее <strong>App Router</strong> в <strong>S3 бакете</strong> можно столкнуться с тем что:</p><ul><li><strong>Страницы приложения не открываются при заходе по прямой ссылке</strong>,</li><li>при этом при переходе через нажатие <strong>по внутренней ссылке всё работает как надо</strong>.</li></ul><p>На первый взгляд такое поведение вводит в ступор. Но у этой ситуации есть две вполне логичные причины.</p><h2 id="first">Причина 1 </h2><p>По умолчанию для страницы <strong>/about</strong> генерируются страницы вида <strong>/about.html</strong>.</p><p>Такой формат файлов не подходит для того чтобы хоститься в <strong>S3 бакете</strong> по той причине что URL-адрес <strong>example.com/about</strong> будет всегда отвечать ошибкой <strong>404</strong>.</p><p>Происходит это потому что в бакете <strong>нет объекта с таким именем</strong>.</p><p>Для того чтобы получить этот объект URL-адрес должен содержать ещё и расширение файла (<strong>example.com/about.html</strong>)</p><h2 id="second">Причина 2 </h2><p>По умолчанию <strong>Next.js</strong> перенаправляет URL-адреса с косой чертой в конце на их аналог без косой черты в конце.</p><p>Например, <strong>/about/</strong> будет перенаправляться на <strong>/about</strong>.</p><p>Нам нужно настроить это поведение так, чтобы оно действовало <strong>противоположным образом</strong>:</p><p>URL-адреса без завершающих косых черт <strong>должны перенаправляться</strong> на их аналоги <strong>с завершающими косыми чертами</strong>.</p><h2 id="solution">Решение </h2><p>Чтобы изменить поведение по умолчанию, нужно изменить пару параметров в файле <strong>next.config.js</strong>.</p><p>Добавляем настройку <em><strong>trailingSlash</strong></em> со значением <em><strong>true</strong></em>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  trailingSlash</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span></code></pre><p>И проверяем что значением поля <em><strong>output</strong></em> является <em><strong>export</strong></em>.</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  output</span><span style="color:#212121;--shiki-dark:#BBBBBB">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "export"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span></code></pre><p>Итоговый конфиг должен выглядеть следующим образом:</p><pre class="shiki shiki-themes min-light min-dark" style="background-color:#ffffff;--shiki-dark-bg:#1f1f1f;color:#24292eff;--shiki-dark:#b392f0" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#C2C3C5;--shiki-dark:#6B737C">/** </span><span style="color:#D32F2F;--shiki-dark:#F97583">@type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> {import('next').NextConfig}</span><span style="color:#C2C3C5;--shiki-dark:#6B737C"> */</span></span>
<span class="line"><span style="color:#D32F2F;--shiki-dark:#F97583">const</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> nextConfig</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> {</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  output</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#22863A;--shiki-dark:#FFAB70"> "export"</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">  trailingSlash</span><span style="color:#D32F2F;--shiki-dark:#F97583">:</span><span style="color:#1976D2;--shiki-dark:#79B8FF"> true</span><span style="color:#212121;--shiki-dark:#BBBBBB">,</span></span>
<span class="line"><span style="color:#24292EFF;--shiki-dark:#B392F0">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#1976D2;--shiki-dark:#79B8FF">module</span><span style="color:#24292EFF;--shiki-dark:#B392F0">.</span><span style="color:#1976D2;--shiki-dark:#79B8FF">exports</span><span style="color:#D32F2F;--shiki-dark:#F97583"> =</span><span style="color:#24292EFF;--shiki-dark:#B392F0"> nextConfig;</span></span></code></pre><p>При такой конфигурации страница <strong>/about</strong> генерит файл <strong>/about/index.html</strong> (вместо <strong>/about.html</strong> по умолчанию).</p><p>В таком случае при обращении по адресу <strong>example.com/about</strong>:</p><ul><li><strong>Next.js</strong> перенаправит нас по адресу <strong>example.com/about/</strong></li><li>Бакет при обращении по адресу <strong>example.com/about/</strong> отдаст нам файл <strong>/about/index.html</strong></li></ul><p>И мы увидим искомую страницу! 🎉</p><p>На этом всё. Спасибо за внимание!</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/yandex-object-storage-nextjs/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Хостинг в S3 от Яндекса]]></title>
            <link>https://baranov.guru/posts/yandex-object-storage/</link>
            <guid>https://baranov.guru/posts/yandex-object-storage</guid>
            <pubDate>Sun, 12 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Инструкция как использовать Yandex Object Storage для хостинга статического сайта.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Изначально статья была опубликована в 2024 году, в старом блоге проекта по адресу <strong>alexeybaranov.dev</strong>.</p><p>Данные в статье могли устареть. Если вы нашли ошибку, пожалуйста <a href="mailto:hello@baranov.guru">напишите нам</a>.</p></aside><p>Сегодня мы расскажем как использовать сервис <strong>Yandex Object Storage</strong> для хостинга статических сайтов. Итак, для того чтобы начать, нам необходимо:</p><ul><li><p>Зайти в облачную <a href="https://console.yandex.cloud/link/yandexgpt">консоль управления Yandex Cloud</a>.</p></li><li><p>Авторизоваться или, если у вас ещё нет аккаунта, зарегистрироваться.</p></li><li><p>Среди списка сервисов выбрать и перейти на страницу <a href="https://console.yandex.cloud/link/storage">Object Storage</a>.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/services.webp" alt="Список сервисов" title="Список сервисов"></p></li></ul><p>Сервис <strong>Object Storage</strong> — это универсальный и масштабируемый сервис для хранения данных, подходящий как для высоконагруженных систем с быстрым доступом к информации, так и для менее требовательных проектов. С его помощью можно хранить данные в виде объектов с текстовым идентификатором, использовать разные классы хранилищ, управлять жизненным циклом объектов, работать с большими объектами размером в несколько терабайт и публиковать статические веб-сайты. HTTP API сервиса совместим с <strong>API Amazon S3</strong>, что позволяет использовать разнообразные инструменты для работы с объектными хранилищами. В этой статье мы как раз остановимся на публикации статических сайтов.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/overview.webp" alt="Yandex Object Storage" title="Yandex Object Storage"></p><p>Для того чтобы захостить сайт необходимо сделать 3 вещи:</p><ul><li><a href="#create-bucket">Создать бакет и загрузить в него файлы сайта</a>;</li><li><a href="#connect-domain">Подключить к бакету свой домен</a>;</li><li><a href="#add-https">Настроить https подключение</a>;</li></ul><h2 id="create-bucket">Создание бакета </h2><p>Итак, для того чтобы разместить свой статический сайт в бакете <strong>Object Storage</strong> необходимо:</p><p>Нажать <strong>создать бакет</strong>.</p><p>Бакет - это логическая сущность, которая помогает организовать хранение объектов.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/create.webp" alt="Создание бакета" title="Создание бакета"></p><p>Загрузить файлы с сайтом в бакет одним из предложенных способов.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/upload.webp" alt="Загрузка объектов" title="Загрузка объектов"></p><p>После загрузки должно получиться что-то в этом роде.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/objects.webp" alt="Загруженные объекты" title="Загруженные объекты"></p><h2 id="connect-domain">Подключение своего домена </h2><p>Далее переходим на вкладку <strong>Веб-сайт</strong>.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/site-tab.webp" alt="Вкладка Веб-сайт" title="Вкладка Веб-сайт"></p><p>Если у вас ещё нет доменов в <strong>Cloud DNS</strong>, то система предложит вам создать их.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/empty-dns.webp" alt="Пустые домены" title="Пустые домены"></p><p>Нажимаем <strong>создать запись</strong> и попадаем на страницу создания записи.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/create-dns.webp" alt="Создание записи" title="Создание записи"></p><p>Нажимаем <strong>создать зону</strong> и создаём зону DNS.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/create-zone.webp" alt="Создание зоны" title="Создание зоны"></p><p>После этого нажимаем <strong>сохранить</strong> и создаём ресурсную запись в этой зоне.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/create-dns-record.webp" alt="Создание ресурсной записи" title="Создание ресурсной записи"></p><p>Добавляем указатель на файл с главной страницей сайта и нажимаем <strong>сохранить</strong>.</p><p>В итоге должно получиться как-то так.</p><h2 id="add-https">Подключение HTTPS </h2><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/site2.webp" alt="Результат создания домена" title="Результат создания домена"></p><p>Далее переходим на вкладку <strong>HTTPS</strong>.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/https-tab.webp" alt="Вкладка HTTPS" title="Вкладка HTTPS"></p><p>Видим сообщение о том что у нас ещё нет конфигурации <strong>HTTPS</strong>.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/empty-https.webp" alt="Нет конфигурации HTTPS" title="Нет конфигурации HTTPS"></p><p>Нажимаем <strong>настроить</strong> и попадаем на выбор источника сертификата.</p><p>Если у вас есть свой сертификат, то вы можете загрузить его нажав на вкладку <strong>свой сертификат</strong>. Если же нет, то вы можете воспользоваться сервисом <strong>Certificate Manager</strong>.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/empty-certificate.webp" alt="Выбор источника сертификата" title="Выбор источника сертификата"></p><p>Переходим в <strong>Certificate Manager</strong>.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/add-certificate-overview.webp" alt="Certificate Manager" title="Certificate Manager"></p><p>Нажимаем <strong>добавить сертификат</strong>. Выбираем опцию <strong>Сертификат от Let's Encrypt</strong>.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/create-certificate.webp" alt="Добавление сертификата" title="Добавление сертификата"></p><p>Заполняем необходимые данные для сертификата и нажимаем <strong>создать</strong>.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/create-certificate-2.webp" alt="Заполненный сертификат" title="Заполненный сертификат"></p><p>Далее заходим в сертификат.</p><p>И видим что для проверки прав на домен необходимо создать ресурсные записи <strong>CNAME</strong> либо <strong>TXT</strong> со специальным кодом проверки.</p><p>Нажимаем <strong>Создать запись</strong>.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/create-certificate-records.webp" alt="Проверка прав на домены" title="Проверка прав на домены"></p><p>Так как мы уже создали зону DNS, то все данные подтянутся автоматически. Нам лишь остаётся нажать на кнопку <strong>Создать</strong>.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/create-certificate-records-2.webp" alt="Создание ресурсной записи" title="Создание ресурсной записи"></p><p>После этого придётся подождать пока произойдёт проверка валидности записей, и сертификат будет выпущен.</p><p>Обычно этот процесс занимает <strong>15-20 минут</strong>, но теоретически может идти и до <strong>суток</strong>.</p><p>Возвращаемся на вкладку <strong>HTTPS</strong> в настройках нашего бакета.</p><p>И выбираем созданный сертификат.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/select-certificate.webp" alt="Выбор сертификата" title="Выбор сертификата"></p><p>Должно получиться как-то так.</p><p><img src="https://baranov.guru/assets/posts/yandex-object-storage/certificate-added.webp" alt="Результат добавления сертификата" title="Результат добавления сертификата"></p><p>Итак, если вы всё настроили правильно, то через пару минут ваш сайт станет доступен по вашему доменному имени по протоколу <strong>HTTPS</strong>! 🎉</p><p>На этом всё, спасибо за внимание!</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/yandex-object-storage/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Начинаем работу с YandexGPT API]]></title>
            <link>https://baranov.guru/posts/yandex-gpt-intro-2024/</link>
            <guid>https://baranov.guru/posts/yandex-gpt-intro-2024</guid>
            <pubDate>Fri, 03 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Рассказываем и показываем как использовать генеративные модели Яндекса через YandexGPT API.]]></description>
            <content:encoded><![CDATA[<div><aside class="post-disclaimer" role="note"><p>⚠️ Эта статья была опубликована в 2024 году и уже устарела.</p><p>Обновлённая версия статьи находится <a href="https://baranov.guru/posts/yandex-gpt-intro/">тут</a>.</p></aside><p>В одном из прошлых постов мы рассказывали про сервис 300 от Яндекса и его использовании через API. Сегодня мы расскажем как начать пользоваться лежащими в его основе генеративными моделями через <strong>YandexGPT API</strong> и сервис <strong>Foundation Models</strong>.</p><p>Итак, для того чтобы начать, нам необходимо:</p><ul><li>Зайти в облачную <a href="https://console.yandex.cloud/link/yandexgpt">консоль управления Yandex Cloud</a>.</li><li>Авторизоваться или, если у вас ещё нет аккаунта, зарегистрироваться.</li><li>Среди списка сервисов выбрать и перейти на страницу <a href="https://console.yandex.cloud/link/yandexgpt">Foundation Models</a>.</li></ul><p><img src="https://baranov.guru/assets/posts/yandex-gpt-intro/services.webp" alt="Список сервисов" title="Список сервисов"></p><p>Сервис <strong>Foundation Models</strong> объединяет в себе несколько больших генеративных нейросетей.</p><p><img src="https://baranov.guru/assets/posts/yandex-gpt-intro/overview.webp" alt="Foundation Models" title="Foundation Models"></p><p>На данный момент нам доступны несколько режимов их использования:</p><ul><li>Первый и самый простой - <strong>чат с YandexGPT</strong>. Это по сути аналог всем известного <strong>ChatGPT</strong>.</li></ul><p><img src="https://baranov.guru/assets/posts/yandex-gpt-intro/chat1.webp" alt="Чат с YandexGPT" title="Чат с YandexGPT"></p><p>В этом режиме в модель прокидывается контекст (история предыдущих запросов). Поэтому работа в таком режиме больше похожа на диалог с ИИ, но не всегда подходит для прикладных бизнес задач.</p><p><img src="https://baranov.guru/assets/posts/yandex-gpt-intro/chat2.webp" alt="Чат с YandexGPT" title="Чат с YandexGPT"></p><ul><li>Второй режим - это <strong>YandexART</strong>. Генеративная модель для создания изображений из текстового описания.</li></ul><p><img src="https://baranov.guru/assets/posts/yandex-gpt-intro/art2.webp" alt="YandexART" title="YandexART"></p><p>По умолчанию нейросеть отдаёт изображение размером <strong>1024x1024</strong> пикселя. В формате JPEG.</p><p><img src="https://baranov.guru/assets/posts/yandex-gpt-intro/art3.jpeg" alt="Пример изображения" title="Пример изображения"></p><ul><li>Ну и третий режим - это <strong>промт-режим</strong>. Он позволяет тонко настраивать поведение модели и по сути является песочницей, с помощью которой можно проверить интеграцию с API до того как выкатывать её в продакшен. Контекст не сохраняется автоматически, но вы можете прокидывать его в запросы.</li></ul><p><img src="https://baranov.guru/assets/posts/yandex-gpt-intro/promt1.webp" alt="Промт-режим" title="Промт-режим"></p><p>В следующих статьях я подробнее рассмотрю <strong>промт-режим</strong>. Расскажу про текущие квоты и лимиты, стоимость, синхронный и асинхронный режимы, эмбеддинги, а также про различия в доступных моделях генерации.</p><p><a href="https://youtu.be/pRHaXBs7RsQ">Посмотреть видео</a></p><p>На этом всё, спасибо за внимание!</p></div>]]></content:encoded>
            <enclosure url="https://baranov.guru/assets/posts/yandex-gpt-intro/cover.webp" length="0" type="image/webp"/>
        </item>
    </channel>
</rss>