Icanup
0% прочитано

Метатеги були в браузері, але не в HTML: навіщо мені знадобився Inertia SSR

Я була впевнена, що SEO вже налаштоване: у браузері title, description і canonical були на місці. Але Google Search Console показала іншу картину. Так я дійшла до Inertia SSR.

25 липня 2026 р. 5 хв читанняInertia.js

Як я виявила проблему

У браузері title, description і canonical відображалися правильно. Але в початковому HTML, який повертав сервер, цих SEO-тегів не було. Вони з’являлися лише після виконання JavaScript.

Я помітила це під час перевірки головної сторінки в Google Search Console. У звіті Google не було canonical, який я вказала для сторінки.

Спочатку це здалося дивним, тому що у DevTools усі метатеги були на місці. Компонент SeoHead отримував дані з Laravel через Inertia props і правильно додавав їх у head.

bash
curl -s https://icanup.com.ua/ \  | sed -n '/<head>/,/<\/head>/p'

Перевірка через curl показала іншу картину. У відповіді сервера залишався лише базовий title застосунку, а description і canonical були відсутні.

Тобто DevTools показував уже змінений DOM після запуску Vue, а curl - початковий HTML, який сервер віддавав до виконання JavaScript.

Що насправді відбувалося

До

<!-- Початковий HTML -->

<title inertia>Icanup</title>

<!-- description відсутній -->

<!-- canonical відсутній -->

Після

<!-- DOM після запуску Vue-->

<title>Блог | IcanUp</title>

<meta

name="description"

content="Практичні статті про PHP, Laravel і Vue.js."

>

<link

rel="canonical"

href="https://icanup.com.ua"

>

SEO-дані вже існували. Laravel правильно їх формував, Inertia передавала їх у props, а Vue-компонент додавав теги в документ.

Проблема була в моменті, коли це відбувалося. Метатеги з’являлися лише на клієнті, а в першій відповіді сервера їх ще не було.

Чому мені знадобився Inertia SSR

Я не хотіла, щоб критичні SEO-метадані залежали лише від виконання JavaScript. Для мене важливо, щоб title, description, canonical, robots, Open Graph і structured data були присутні вже в початковому HTML. Саме тому я вирішила додати Inertia SSR.

Що потрібно було налаштувати

Щоб додати SSR до мого Inertia-застосунку, потрібно було:

  • створити окрему точку входу для серверного рендерингу;

  • додати SSR-збірку у Vite;

  • перевірити сумісність Vue-компонентів із серверним середовищем;

  • запустити Inertia SSR як окремий процес;

  • додати процес у Supervisor;

  • оновити deployment для перебудови та перезапуску SSR;

  • перевірити SEO-теги в початковому HTML.

Як я налаштувала Inertia SSR

Спочатку я створила окрему точку входу для серверного рендерингу - resources/js/ssr.js.

У ній Inertia використовує ті самі Vue-сторінки та той самий спосіб їх пошуку, що й клієнтська частина застосунку. Відмінність у тому, що для першого запиту компоненти рендеряться через renderToString на сервері.

javascript
import { createInertiaApp } from '@inertiajs/vue3'import createServer from '@inertiajs/vue3/server'import { renderToString } from '@vue/server-renderer' createServer(page =>    createInertiaApp({        page,        render: renderToString,        title: title => `${title} | IcanUp`,        resolve: name => resolvePageComponent(            `./Pages/${name}.vue`,            import.meta.glob('./Pages/**/*.vue'),        ),        setup: ({ App, props, plugin }) =>            createSSRApp({                render: () => h(App, props),            }).use(plugin),    }),)

Після цього я додала окрему SSR-збірку у Vite. Тепер під час deployment збирається не лише клієнтський JavaScript, а й серверний bundle.

Сам SSR працює як окремий Node.js-процес. Я додала його в Supervisor окремо для dev і production, щоб процес автоматично запускався, перезапускався після помилки та відновлювався після перезавантаження сервера.

Deployment також довелося доповнити: після нової збірки SSR-процес потрібно перезапустити, інакше він продовжить використовувати попередню версію bundle.

Що довелося виправити під час запуску

Перший запуск SSR одразу показав місця, де компоненти залежали від браузерного середовища.

У клієнтському застосунку можна напряму звертатися до window, document або localStorage. Але під час серверного рендерингу цих об’єктів немає.

Після правок ті самі компоненти змогли працювати і під час SSR, і після hydration у браузері.

Ще одна проблема з’явилася вже під час перевірки результату: у початковому HTML було два теги title. Один залишався статично в Blade-шаблоні, а другий додавав Inertia SSR. Я прибрала статичний title і залишила формування head через Inertia.

Як я перевірила результат

Запущений процес у Supervisor ще не означає, що SSR справді працює правильно. Тому головною перевіркою для мене залишився початковий HTML, який повертає сервер.

bash
curl -s https://icanup.com.ua/ \  | sed -n '/<head>/,/<\/head>/p'

Після налаштування ця команда вже показувала правильний title, description, canonical, robots, Open Graph, Twitter і JSON-LD без запуску браузера. Важливо й те, що SSR відрендерив не лише head. У початковому HTML уже був вміст сторінки, а Vue після завантаження лише підключав інтерактивність через hydration.

Після повторної перевірки Google Search Console просканувала оновлену сторінку. Сторінка залишилася доступною для індексації, а актуальні SEO-метадані вже були присутні в початковій відповіді сервера.

Висновок

Спочатку мені здавалося, що SEO вже працює, тому що в DevTools усі метатеги були на місці.

Але DevTools показував DOM після виконання JavaScript, а не початкову відповідь сервера. Саме перевірка через curl і Google Search Console показала різницю.

У моєму випадку Inertia SSR став не просто додатковою оптимізацією. Він закрив конкретну потребу: title, description, canonical та інші SEO-дані тепер повертаються разом із початковим HTML.

При цьому мені не довелося відмовлятися від Vue або переписувати сторінки на Blade. Я залишила стек Laravel, Inertia і Vue, але додала серверний рендеринг для першого завантаження сторінки.