Як я виявила проблему
Я помітила це під час перевірки головної сторінки в Google Search Console. У звіті Google не було canonical, який я вказала для сторінки.
Спочатку це здалося дивним, тому що у DevTools усі метатеги були на місці. Компонент SeoHead отримував дані з Laravel через Inertia props і правильно додавав їх у head.
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 на сервері.
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 у браузері.
Як я перевірила результат
Запущений процес у Supervisor ще не означає, що SSR справді працює правильно. Тому головною перевіркою для мене залишився початковий HTML, який повертає сервер.
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.
Висновок
Спочатку мені здавалося, що SEO вже працює, тому що в DevTools усі метатеги були на місці.
Але DevTools показував DOM після виконання JavaScript, а не початкову відповідь сервера. Саме перевірка через curl і Google Search Console показала різницю.
У моєму випадку Inertia SSR став не просто додатковою оптимізацією. Він закрив конкретну потребу: title, description, canonical та інші SEO-дані тепер повертаються разом із початковим HTML.
При цьому мені не довелося відмовлятися від Vue або переписувати сторінки на Blade. Я залишила стек Laravel, Inertia і Vue, але додала серверний рендеринг для першого завантаження сторінки.



