Двомовне SEO для сайту на React і Vite
Still Brewing UAОпубліковано: Оновлено:
Перемикач мови, який лише підміняє текст у браузері, дає пошуковим системам одну адресу й одну версію контенту. Для двох індексованих мов потрібні дві адреси, і кожна має віддавати готовий HTML своєю мовою. Ось як це зробити без фреймворку.
Мова визначається адресою
Кожна мова отримує власний шлях: один під `/en/`, інший під `/uk/`. Цей шлях — єдине джерело істини. Збережене налаштування може вирішувати, куди потрапить відвідувач уперше, але воно ніколи не повинно перекривати мову, яка вже вказана в адресі.
Це правило важливе на практиці. Якщо хтось поділився посиланням на українську сторінку, а вона відкрилася англійською через значення в браузері відвідувача, посилання зламане для всіх, хто його отримає — а сканер, що зберіг би налаштування, проіндексував би не той контент.
1. явне значення (використовується під час пререндеру)
2. мовний сегмент в адресі <- завжди перемагає
3. збережене налаштування <- лише на запасному "/"
4. мова за замовчуваннямВіддавайте готовий HTML, а не порожню оболонку
Односторінковий застосунок зазвичай віддає порожній контейнер і наповнює його за допомогою JavaScript. Пошукові системи вміють виконувати JavaScript, але немає причин ставити індексацію в залежність від нього, коли вміст відомий уже під час збірки.
Після клієнтської збірки відрендеріть застосунок по одному разу для кожної адреси й запишіть результат у HTML-файл цієї адреси. Тоді кожен файл містить справжній текст своєю мовою ще до запуску будь-якого скрипта. Фреймворк не потрібен: Vite вміє завантажити застосунок на боці сервера під час збірки, а React — відрендерити його у статичний рядок.
Одну деталь легко пропустити. Вміст, який плавно з’являється під час прокручування, спочатку прихований; якщо пререндер зафіксує саме цей стан, у статичному HTML буде текст, який присутній, але невидимий. Рендерте вже показаний стан, коли браузера немає, — і файл, який завантажує сканер, покаже завершену сторінку.
Окремі метадані для кожної мови
Кожній мовній версії потрібні власні title, description і метадані для соцмереж. Якщо будувати їх із того самого набору перекладів, що рендерить сторінку, вони синхронізуються автоматично — метадані не можуть розійтися з тим, що читає відвідувач.
- `<html lang>`, що відповідає мові сторінки
- окремі `<title>` і meta description для кожної мови
- canonical, який вказує сам на себе
- `og:title`, `og:description`, `og:url` і `og:locale`
- title і description для картки Twitter
- локалізоване Open Graph зображення та його alt-текст
Якщо мову можна перемкнути й усередині сторінки, оновлюйте head одночасно. Інакше після перемикання у вкладці лишається заголовок попередньої мови, а все, що читає живий DOM, бачить сторінку, у якої адреса й метадані суперечать одне одному.
Canonical і hreflang
Кожна сторінка вказує canonical сама на себе. Мовні версії є альтернативами одна одної, а не дублікатами якогось оригіналу, тому жодна не повинна бути canonical для іншої.
Обидві версії далі несуть однаковий набір взаємних альтернатив, включно з `x-default` для відвідувачів, чиєї мови ви не охоплюєте.
<link rel="canonical" href="https://example.com/en/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/" />
<link rel="alternate" hreflang="uk" href="https://example.com/uk/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/" />Альтернативи мають бути взаємними. Сторінку, яка вказує альтернативу, що не посилається назад, буде проігноровано.
Використовуйте `uk`, а не `ua`
Код української мови — `uk`. `UA` — це код країни Україна, і вони не взаємозамінні. `hreflang="ua"` некоректний і буде відкинутий.
Якщо той самий код використовується для сегмента адреси, атрибута `lang` і значення hreflang, узгодженість забезпечується сама собою — код записаний лише в одному місці.
Що робити з кореневою адресою
Корінь може лишатися корисним і не конкурувати за індексацію: віддавайте там мову за замовчуванням, а canonical спрямуйте на справжню адресу цієї мови, і згодом налаштуйте редирект на сервері. У будь-якому разі не додавайте корінь до sitemap, щоб він ніколи не пропонувався як окрема сторінка.
Sitemap і robots.txt
Перелічіть кожну індексовану адресу один раз і дайте кожному запису той самий набір мовних альтернатив, що несе сама сторінка. Якщо генерувати файл із того самого значення адреси сайту, що використовує збірка, sitemap ніколи не вкаже на застарілий домен.
<url>
<loc>https://example.com/en/</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/"/>
<xhtml:link rel="alternate" hreflang="uk" href="https://example.com/uk/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/"/>
<lastmod>2026-09-02</lastmod>
</url>Простір імен `xhtml` треба оголосити на елементі `urlset`, інакше альтернативи не будуть прочитані. Далі `robots.txt` указує на sitemap із тим самим абсолютним походженням.
Search Console
Коли адреси вже працюють, підтвердьте право власності на сайт і надішліть sitemap. Інструмент перевірки URL показує HTML, який справді було отримано, — це найшвидший спосіб переконатися, що до сканера доходить пререндерений вміст, а не порожня оболонка, і що кожна сторінка повідомляє саме той canonical, який ви задумали.
Перевіряйте кожну мову напряму
Відкривайте адресу кожної мови напряму, а не через перемикач, і дивіться на віддану вихідну розмітку, а не на відрендерений DOM. Вони можуть відрізнятися, а індексація починається саме з відданого коду.
- кожна адреса повертає 200 і правильний `<html lang>`
- у вихідному коді є справжній текст цією мовою, а не заглушка
- жодна сторінка не містить речень іншою мовою
- canonical і hreflang абсолютні та взаємні
- збережене налаштування не перекриває адресу
- перемикання мови зберігає поточну сторінку, її query та hash
Джерела
Схожі нотатки
- Розгортання React і Vite сайту на Nginx із версіями релізівЯк публікувати статичний сайт на React і Vite на сервері з Nginx: каталоги релізів, перевірка контрольних сум і атомарне перемикання symlink.Читати нотатку
- Безпечний чекліст розгортання статичного сайту на спільному серверіПрактичний чекліст, як додати статичний сайт на сервер, де вже працюють інші проєкти, не зачепивши те, що вже працювало.Читати нотатку
Потрібно зробити щось подібне?
Ці нотатки — з роботи, яку я вже запустив. Якщо потрібне те саме й зроблене як слід, розкажіть, що ви задумали.