SSR для Laravel + React: Как настроить серверный рендеринг через Inertia

SSR для Laravel + React: Как настроить серверный рендеринг через Inertia

За последние годы я перевёл на серверный рендеринг больше десятка Inertia-проектов — от каталогов до медиаплатформ. В моих проектах после внедрения SSR LCP обычно снижался с 2,5–4 до 0,8–1,4 секунды. Конкретный результат зависит от сервера, кеширования, изображений, шрифтов и размера клиентского JavaScript-бандла.

Ниже — рабочая настройка SSR для существующих проектов на Laravel 12/13, Inertia 2.x, React 19 и Vite 7.

Зачем нужен SSR

Обычный Inertia-проект без SSR возвращает браузеру корневой элемент приложения и данные страницы. Основной React-интерфейс появляется после загрузки и выполнения JavaScript.

Google и Яндекс умеют выполнять JavaScript, но SSR позволяет сразу вернуть содержательную HTML-разметку. Это упрощает обработку страниц поисковыми системами, социальными сетями, Telegram и другими парсерами. (Google for Developers)

При SSR отдельный Node-процесс рендерит React-компоненты в HTML до отправки ответа. После загрузки страницы React выполняет гидрацию и подключает обработчики событий к уже готовой разметке.

Шаг 1. Устанавливаем зависимости

Если Inertia и Ziggy ещё не установлены:

composer require inertiajs/inertia-laravel tightenco/ziggy

npm install @inertiajs/react react react-dom @vitejs/plugin-react

Создаём middleware:

php artisan inertia:middleware

Регистрируем его в bootstrap/app.php:

<?php

use App\Http\Middleware\HandleInertiaRequests;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->web(append: [
            HandleInertiaRequests::class,
        ]);
    })
    ->create();

Корневой шаблон resources/views/app.blade.php:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">

    <meta
        name="viewport"
        content="width=device-width, initial-scale=1"
    >

    @viteReactRefresh
    @vite(['resources/js/app.jsx'])
    @inertiaHead
</head>
<body>
    @inertia
</body>
</html>

Шаг 2. Настраиваем Ziggy

Конфигурацию Ziggy необходимо передавать через Inertia props. Не записывайте route() и данные текущего запроса в global или globalThis: Node-процесс постоянно работает и обслуживает разные запросы.

Открываем app/Http/Middleware/HandleInertiaRequests.php:

<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Inertia\Middleware;
use Tighten\Ziggy\Ziggy;

class HandleInertiaRequests extends Middleware
{
    protected $rootView = 'app';

    public function share(Request $request): array
    {
        return [
            ...parent::share($request),

            'locale' => app()->getLocale(),

            'timezone' => config('app.timezone'),

            'ziggy' => fn (): array =>