Qwik — JavaScript-фреймворк, который делает ставку на возобновляемость вместо гидратации. Страница приходит готовым HTML, а JavaScript не выполняет приложение заново: обработчики подгружаются по мере взаимодействия. Первый экран остаётся отзывчивым даже на медленном соединении. Вокруг ядра выстроен Qwik City с файловым роутингом, загрузкой данных и формами. В 2026 году ветка Qwik 1.x стабильна, а Qwik 2 развивается отдельно.
Почему гидратация тормозит
В классическом SSR сервер отдаёт HTML, затем браузер скачивает бандл, выполняет компоненты, восстанавливает состояние и разрешает нажатия. Это гидратация. Чем больше приложение, тем дольше оно «оживает».
Qwik сериализует состояние в HTML и не повторяет выполнение компонентов при загрузке. Теоретически стоимость старта не растёт линейно с размером приложения. На практике всё зависит от объёма сериализованных данных, числа обработчиков и сторонних библиотек, поэтому замеры нужны всегда.
Как работает возобновляемость
В HTML попадает не только разметка, но и ссылки на границы ленивой загрузки. Символ $ создаёт точку разделения: компилятор выносит её в отдельный чанк. Загрузчик qwikloader подгружает обработчик в момент клика.
Состояние должно быть сериализуемым. Замыкания на объекты браузера, классы с методами и большие структуры усложняют возобновление. Держите состояние простым, а тяжёлую логику выносите в server$ или в загрузчики маршрута.
Создание проекта
Понадобится Node.js 18.17 или новее. Мастер создаст проект, установит зависимости и предложит стартовый шаблон. Для разработки используется порт 5173, для предпросмотра production-сборки — 4173.
npm create qwik@latest
cd my-app
npm install
npm run start
npm run preview
Внутри проекта есть каталог src/routes с файловым роутингом, root.tsx и точки входа. Файл index.tsx в папке маршрута экспортирует компонент по умолчанию. Сборка и предпросмотр запускаются через скрипты package.json, поэтому не вызывайте Vite напрямую без необходимости.
Компоненты и сигналы
Компонент объявляется через component$. Сигнал useSignal хранит локальное значение, useStore — объект, а useComputed$ пересчитывает производное значение. Обработчики событий помечаются суффиксом $ и загружаются отдельно.
import { component$, useSignal, useComputed$ } from '@builder.io/qwik';
export const Counter = component$(() => {
const count = useSignal(0);
const double = useComputed$(() => count.value * 2);
return (
<button onClick$={() => count.value++}>
{count.value} / {double.value}
</button>
);
});
Не оборачивайте каждую мелочь в отдельный компонент: каждая граница $ — это потенциально отдельный чанк и запрос. Группируйте связанную логику и проверяйте, какие чанки реально загружаются в DevTools.
Маршруты, данные и формы
routeLoader$ выполняет загрузку на сервере до рендера и отдаёт сигнал в компонент. routeAction$ обрабатывает отправку формы, в том числе без JavaScript. Компонент Form использует нативную форму, а при включённом JS отправляет данные без перезагрузки страницы.
import { component$ } from '@builder.io/qwik';
import { routeLoader$, Form, routeAction$ } from '@builder.io/qwik-city';
export const usePosts = routeLoader$(async () => {
return [{ id: 1, title: 'Первый пост' }];
});
export const useAddPost = routeAction$(async (data) => {
return { ok: true, title: String(data.title) };
});
export default component$(() => {
const posts = usePosts();
const action = useAddPost();
return (
<>
<ul>{posts.value.map((p) => <li key={p.id}>{p.title}</li>)}</ul>
<Form action={action}>
<input name="title" />
<button type="submit">Добавить</button>
</Form>
{action.value?.ok && <p>Сохранено</p>}
</>
);
});
Валидацию делайте на сервере, например через Zod, и не доверяйте полям формы. Для клиентских вызовов есть server$: функция выполняется на сервере.
Сборка и деплой
Адаптер выбирается командой npm run qwik add. Для статического сайта подойдёт адаптер static, для серверного рендеринга — Node или другой поддерживаемый рантайм. После сборки проверьте каталог, который создал адаптер: у разных режимов он отличается.
npm run qwik add static
npm run build
npm run preview
Статическую сборку отдайте через nginx. Хешированные файлы из build кэшируются надолго, а HTML — нет, иначе пользователи останутся на старой версии. Для SSR нужен процесс Node под systemd и reverse proxy.
server {
listen 80;
server_name example.com;
root /var/www/qwik/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /build/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Диагностика и оптимизация
Откройте DevTools и посмотрите, что загружается до первого клика. У хорошо собранного Qwik-приложения не должно быть большого общего бандла: обработчики появляются только после взаимодействия. Проверьте, что HTML содержит сериализованное состояние и загрузчик, а не пустой контейнер.
curl -s https://example.com | grep -o 'qwikloader[^"]*' | head
du -sh dist
find dist -type f -name '*.js' -size +100k -exec ls -lh {} \;
- сравните размер HTML до и после добавления состояния;
- проверьте, какие чанки грузятся при клике и при наведении;
- убедитесь, что тяжёлые библиотеки не попали в начальный бандл;
- измерьте LCP и INP на мобильном профиле;
- не используйте
useVisibleTask$для логики, которая может работать на сервере; - проверьте, что формы работают при отключённом JavaScript.
Сериализация — источник неожиданного веса. Большой объект в useStore попадёт в HTML и увеличит первый ответ. Дробите состояние и подгружайте детали по требованию.
Когда Qwik не нужен
Для лендинга из трёх экранов или внутренней админки с тяжёлым клиентским состоянием выгода может быть незаметной. Экосистема Qwik меньше, чем у React или Vue, а переход на Qwik 2 потребует проверки зависимостей. Если команда не готова разбираться с сериализацией и границами $, классический SSR может оказаться проще.
Qwik хорошо подходит для контентных сайтов, интернет-магазинов и приложений, где важен быстрый первый экран и много статических страниц. Решение стоит принимать по замерам, а не по обещанию «нулевой гидратации».
Итог
Qwik меняет точку старта: браузер не выполняет приложение целиком, а возобновляет его по мере надобности. Это даёт быстрый первый экран, но требует дисциплины в состоянии. Начните с небольшого проекта, измерьте бандлы и только потом переносите крупный интерфейс.
- создайте проект через
npm create qwik@latest; - освойте
component$, сигналы и задачи; - перенесите загрузку данных в
routeLoader$; - проверьте формы без JavaScript;
- соберите production-версию и измерьте чанки;
- выберите адаптер под свой хостинг и настройте кэш.