За три тижні Inertia.js вийшла у трьох версіях: v3.9.0 і v3.9.1 9 жовтня та v3.10.0 30 жовтня, того ж дня, що й серверний адаптер inertia-laravel v3.6.0. Найпомітніше - підтримка HTTP-методу QUERY, але для більшості проєктів не менш важливі тихі зміни безпеки у v3.9.1. Попередній крок, v3.8 з WhenMounted і BigInt-пропсами, ми розбирали окремо.
Метод QUERY: пошук без query string
QUERY - новий HTTP-метод, безпечний та ідемпотентний, як GET, але дані він передає в тілі запиту, а не в URL. Для пошуку й фільтрів із великими або вкладеними наборами параметрів це зручніше за довгий query string. На боці Laravel маршрут оголошує Route::query(): на гілку 13.x його перенесли в #61797, і він вийшов у Laravel 13.35.0. HTTP-клієнт і тестові хелпери для QUERY ми описували ще в 13.19.
Route::query('/products', [ProductController::class, 'search']);
У #3328 клієнт Inertia отримав метод router.query(), значення query для method у <Link> і <Form> та метод query() у useForm і useHttp:
router.query('/products', {
categories: ['Keyboards', 'Monitors'],
price: { min: 100, max: 500 },
})
const form = useForm({ search: '', categories: [] })
form.query('/products')
<Link href="/products" method="query" :data="{ brand: 'Acme' }" prefetch>Acme products</Link>
<Form action="/products" method="query">
<!-- ... -->
</Form>
Сторінка, відрендерена QUERY-візитом, запам'ятовує тіло запиту. Відкладені пропси, часткові перезавантаження, polling, <WhenVisible> і нескінченна прокрутка надсилають ті самі фільтри повторно, тож router.reload({ only: ['facets'] }) піде як QUERY з початковими даними. Оскільки метод безпечний, такі посилання можна prefetch-ити: кеш ведеться окремо для кожного тіла запиту.
Редірект після QUERY: чому потрібен 303
Браузер замінює метод на GET під час переходу за 302 лише після POST. Для решти методів він повторює запит з тим самим методом і тілом. Тож QUERY-запит, який, скажімо, не пройшов валідацію або натрапив на редірект на сторінку входу, прийшов би на нову адресу знову як QUERY і найімовірніше отримав би 405. У inertia-laravel #927 QUERY додали до методів, чиї 302-редіректи для Inertia-запитів перетворюються на 303, поруч із PUT, PATCH і DELETE. Тому оновлювати варто обидва пакети разом.
Головна поведінкова різниця: як і POST, QUERY-візит за замовчуванням зберігає стан компонента, а URL у браузері не змінюється. Зміна фільтрів не створює записів в історії, але після оновлення сторінки фільтри зникають. Якщо результат пошуку треба надіслати посиланням або зберегти в закладках, GET лишається кращим вибором. І ще одна деталь з #61797: у Laravel 13.x QUERY-маршрути, на відміну від GET, проходять перевірку CSRF, бо обробник отримує тіло запиту.
Polling на паузі у фонових вкладках
Досі polling у прихованій вкладці сповільнювався до кожного десятого інтервалу, якщо не стояв keepAlive, а повністю зупинити його було не можна. У v3.9.0 (#3292) usePoll() і router.poll() отримали опцію background:
import { usePoll } from '@inertiajs/vue3'
usePoll(5000, {}, { background: 'pause' })
'pause' - поки вкладка прихована, запитів немає. Коли вона знову видима, запит іде одразу, якщо за час відсутності настав його час, інакше poll чекає залишок поточного інтервалу. Швидке перемикання вкладок не породжує запит на кожне перемикання.
'throttle' - типове значення, колишня поведінка.
'continue' - повний інтервал і у фоні; замінює keepAlive: true, який тепер депрекейт.
Заодно виправлено poll, створений у вже прихованій вкладці: раніше він працював на повному інтервалі до наступної зміни видимості.
Безпека у v3.9.1
Обидві зміни не потребують дій, але варто знати, що змінилося.
- XSRF-заголовок лише для свого origin (#3313). Вбудований HTTP-клієнт додавав значення cookie
XSRF-TOKEN як заголовок X-XSRF-TOKEN до кожного запиту, зокрема на чужі домени, наприклад на presigned-URL для завантаження в S3. Тепер заголовок іде лише на URL з тим самим origin, що й поточна сторінка. Якщо він справді потрібен якомусь іншому origin, додайте його в обробнику http.onRequest.
- Унікальний IV для зашифрованої історії (#3315). Усі записи зашифрованої історії мали один AES-GCM IV, згенерований раз і збережений у
sessionStorage. Тепер кожен запис отримує свій випадковий IV. Записи старих версій просто буде завантажено з сервера заново. Те саме виправлення перенесено в гілку 2.x у v2.3.29.
Стабільніша навігація у v3.10
Кілька виправлень стосуються гонок, які важко відтворити, але неприємно ловити в продакшені:
- клієнтський візит (
router.push(), router.replace()) на інший шлях скасовує асинхронні запити до сторінки, яку залишили (#3333), а не тихо викидає їхні відповіді з onSuccess, що все одно спрацьовував;
- poll, відкладені пропси чи часткове перезавантаження, що повернулися після переходу, наприклад після кнопки «Назад», більше не повертають стару сторінку або URL (#3344);
- відносні URL у
Link і Form резолвляться відносно поточної сторінки: посилання pass на /a1/a2/a3 раніше вело на /a1/a2/a1/a2/pass (#3358);
- shared once-пропси, наприклад переклади, більше не губляться на instant visits під час швидких кліків між prefetch-посиланнями (#3356).
SSR: некоректні запити і репліки
Продакшен-сервер SSR падав від одного запиту на /render без коректного об'єкта сторінки, наприклад від простого curl чи сканера портів, і разом із ним зникали всі рендери в процесі. Тепер у такому разі він відповідає 400 з повідомленням про помилку і працює далі (#3314).
У Laravel-адаптері inertia:start-ssr отримав опцію --no-stop (inertia-laravel #925). Команда завжди спершу викликає inertia:stop-ssr, який шле /shutdown на inertia.ssr.url. Коли за однією адресою стоять кілька реплік, як за балансувальником чи Kubernetes Service, новий екземпляр гасив здорового сусіда, і репліки перезапускали одна одну по колу. З --no-stop зупинку пропущено:
php artisan inertia:start-ssr --no-stop
Типова поведінка не змінилася.
Чи варто оновлюватися
Так, і бажано обидва пакети одразу: @inertiajs/* до v3.10.0 і inertiajs/inertia-laravel до v3.6.0. Ламаючих змін у релізах немає, а виправлення безпеки та гонок навігації отримують усі, навіть ті, хто не планує QUERY. Якщо ви ще на 2.x, оновіться хоча б до v2.3.29 заради нового IV для історії.
Перед тим як переводити фільтри на QUERY, оцініть, чи потрібні користувачам посилання на результати: метод не залишає слідів в URL. І перевірте, що у вас Laravel 13.35 або новіший: саме там з'явився Route::query(). Якщо ви вручну використовували keepAlive: true у polling, замініть його на background: 'continue', поки депрекейт не перетворився на видалення.