Svelte
Form actions и прогрессивное улучшение
Форма входа должна работать даже у того, у кого JavaScript выключен или ещё не загрузился по медленной сети. Классический подход React-приложения - перехватить submit на клиенте и отправить fetch - ломается ровно в этот момент: без JS кнопка ничего не делает. SvelteKit строит наоборот. Сначала обычная HTML-форма с method POST, которую браузер умеет отправлять сам. Сервер обрабатывает её в action. А затем, если JS есть, одна директива use:enhance превращает ту же форму в плавную отправку без перезагрузки страницы.
- Форма входа: работает и при отключённом JS, и плавно с use:enhance, когда скрипты загрузились
- Корзина магазина: кнопки добавить и удалить - именованные actions на одной странице товара
- Комментарии под статьёй: ошибки валидации возвращаются через fail и отображаются без потери введённого текста
- Настройки профиля: форма сохранения через прогрессивное улучшение, индикатор отправки во время запроса
- GitHub и многие SSR-сайты: критичные формы остаются нативными POST, так они переживают сбой клиентского JS
Предварительные знания
- Файловый роутинг и роль +page.server.js в SvelteKit
- HTML-формы: атрибуты method, action, name у полей ввода
- Понятие HTTP POST и того, что браузер умеет отправлять форму без JavaScript
Action и обычная HTML-форма
Action это серверная функция для обработки отправленной формы. Она объявляется в +page.server.js внутри экспортируемого объекта actions. Форма с одним обработчиком использует ключ default. Поля формы читаются на сервере через request.formData(), а результат action возвращается странице как проп form.
Ключевая деталь: эта форма работает без единой строки клиентского JavaScript. Атрибут method="POST" говорит браузеру отправить данные нативно, сервер выполнит action и вернёт страницу заново уже с заполненным form. Имена полей (name="email") обязательны - именно по ним formData находит значения.
Функция fail из @sveltejs/kit возвращает ошибку валидации с HTTP-статусом и объектом, который попадёт в проп form. В отличие от error(), fail не уводит на страницу ошибки, а возвращает пользователя к той же форме с введёнными данными и подсказкой, что поправить.
Почему форма с method="POST" и action в +page.server.js работает даже при отключённом JavaScript?
Именованные actions: несколько форм на странице
Когда на одной странице несколько форм - добавить в избранное, удалить, обновить количество - default не хватает: непонятно, какая форма сработала. Тогда каждому обработчику дают имя в объекте actions, а форма указывает, какой action вызвать, через action="?/имя".
Именованный action и default нельзя смешивать в одном объекте actions: либо одна форма с default, либо набор именованных. Попытка держать и default, и именованные приводит к ошибке при сборке.
Префикс ?/ в action="?/add" - не случайный синтаксис. SvelteKit кодирует выбранный action в строке запроса, поэтому даже без JavaScript браузер отправит POST на URL с ?/add, и сервер поймёт, какой обработчик звать. Скрытое поле hidden передаёт id товара, которого нет в видимых полях.
На странице корзины две формы: добавить и удалить товар. Как сервер различит, какую именно отправили?
use:enhance: то же самое, но без перезагрузки
Нативная отправка формы перезагружает страницу. Это надёжно, но не плавно. Директива use:enhance из `$app/forms` перехватывает отправку, когда JavaScript доступен, шлёт запрос в фоне и сама обновляет проп form и перезапускает load - всё без полной перезагрузки. Если JS не загрузился, форма всё равно работает как обычный POST. Это и есть прогрессивное улучшение: базовая функциональность сначала, плавность сверху.
Функция-аргумент use:enhance вызывается перед отправкой - удобно выставить индикатор загрузки. Она может вернуть колбэк, который сработает с ответом сервера. Вызов update() применяет стандартное поведение: обновляет form и перезапускает load. Без своего колбэка use:enhance делает это сам по умолчанию, так что в простом случае хватает голого use:enhance без аргументов.
- Без use:enhance — Браузер делает полный POST и перезагружает страницу. Работает всегда, но мигает и теряет позицию прокрутки. Базовый, надёжный уровень
- С use:enhance — Отправка идёт в фоне, страница не перезагружается, form и load обновляются точечно. Требует JS, но даёт плавность поверх рабочей базы
Порядок разработки имеет смысл такой: сначала форма работает как нативный POST с серверным action, потом сверху навешивается use:enhance. Тогда даже при сбое клиентского JS пользователь всё ещё может отправить форму, а не упирается в мёртвую кнопку.
Что делает директива use:enhance, добавленная к рабочей форме с серверным action?
Связь с другими темами
Form actions - стандартный способ записи данных со страницы. Рядом стоят:
- Load-функции — Чтение данных страницы; после успешного action SvelteKit перезапускает load, чтобы показать свежее состояние
- API-эндпоинты +server.js — Альтернатива для записи, когда нужен программный JSON-API, а не привязанная к странице форма
- Hooks — Action читает сессию и пользователя из event.locals, которые заполняет серверный handle
Итог
- Actions объявляются в объекте actions внутри +page.server.js и обрабатывают POST-запросы формы на сервере
- Одна форма без имени попадает в action default; несколько форм на странице различают именованными actions через action="?/имя"
- Данные формы читаются через await request.formData(); ошибки валидации возвращают через fail(status, { ... })
- Без JavaScript форма работает как обычный HTML POST: браузер сам отправляет данные и получает ответ
- Директива use:enhance перехватывает отправку на клиенте, делает её без перезагрузки и сама обновляет проп form и данные load
Связанные уроки
- sv-19-load-functions — Load читает данные для страницы, actions их записывают. Это две половины одного цикла на маршруте
- sv-21-server-routes — Когда запись данных нужна не из формы, а как программный API для разных клиентов, вместо action берут +server.js
- sv-22-hooks — Action часто проверяет сессию через event.locals, которые наполняет handle в hooks.server.js