Qwik framework: resumability вместо hydration — будущее фронтенда?
Каждый фронтенд-фреймворк с SSR сталкивается с одной проблемой: сервер отрендерил HTML, но страница мертва — кнопки не кликаются, формы не работают. Чтобы оживить интерфейс, нужна гидрация: скачать весь JS, выполнить компоненты заново, привязать обработчики. Qwik предлагает радикальную альтернативу — resumability. Приложение не переигрывается с нуля, а продолжает работу с того места, где остановился сервер.
Проблема hydration: bottleneck для TTI
При гидрации фреймворк восстанавливает обработчики событий, дерево компонентов и состояние. Для этого скачивается весь код компонентов и выполняется заново:
- TTI растёт линейно с размером приложения.
- Дублирование работы — сервер уже выполнил компоненты, но браузер делает это повторно.
- Весь JS загружается eagerly — даже код кнопки в футере скачивается при загрузке.
На мобильных устройствах гидрация может занимать 2-5 секунд. Пользователь видит интерфейс, кликает — и ничего не происходит.
Resumability: как работает Qwik
Qwik сериализует всё необходимое прямо в HTML. Браузеру не нужно ничего переигрывать — он продолжает (resumes) с того места, где остановился сервер. Три механизма:
1. Обработчики в DOM. Вместо привязки listeners через JS, Qwik записывает ссылки в атрибуты:
<button on:click="./chunk-abc.js#handleClick">
Нажми меня
</button>
Глобальный Qwikloader (<1 КБ) перехватывает события и лениво загружает нужный чанк только при взаимодействии.
2. Ленивое восстановление компонентов. Дерево сериализуется в HTML-комментарии. Компонент восстанавливается только когда ему нужно перерендериться.
3. Сериализация состояния. Стейт сохраняется в JSON в HTML. Компонент восстанавливается независимо от родителя.
import { component$, useSignal } from '@builder.io/qwik';
export const Counter = component$(() => {
const count = useSignal(0);
return (
<button onClick$={() => count.value++}>
Клики: {count.value}
</button>
);
});
Суффикс $ — маркер для оптимизатора: «эту функцию можно вынести в отдельный чанк и загрузить лениво».
Qwik City: роутинг, loaders, actions
Qwik City — мета-фреймворк (аналог Next.js). Файловый роутинг, серверные загрузчики, actions для мутаций:
// src/routes/posts/[id]/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
export const usePost = routeLoader$(async ({ params }) => {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
return res.json();
});
export default component$(() => {
const post = usePost();
return (
<article>
<h1>{post.value.title}</h1>
<p>{post.value.body}</p>
</article>
);
});
Сравнение метрик Web Vitals
Resumability даёт преимущество на начальную загрузку. Сравнение для e-commerce (~200 компонентов):
Метрика | Qwik | Next.js 14 | Astro | SolidStart
─────────────────────────────────────────────────────────────
TTI | 0.8s | 3.2s | 0.9s* | 2.1s
LCP | 1.1s | 1.4s | 1.0s | 1.3s
JS при загрузке | ~2 KB | ~180 KB | 0 KB* | ~95 KB
FID | 12ms | 85ms | 15ms* | 45ms
* Astro отлично работает для статики, но при добавлении интерактивности JS растёт. В Qwik объём JS при загрузке остаётся константным (~1-2 КБ) независимо от сложности страницы.
Когда выбирать Qwik
Идеальные use cases: e-commerce с интерактивными фильтрами и корзиной; контентные сайты с интерактивностью без жертв по TTI; приложения, критичные к производительности на мобильных.
Ограничения:
- Молодая экосистема. Нет аналога shadcn/ui или Material UI для Qwik.
- Ментальная модель. Суффикс
$и правила сериализации требуют привыкания. Не всё можно передать через границу сериализации. - Рынок труда. Вакансий с Qwik практически нет.
Вывод
Qwik — не эволюция гидрации, а её замена. Resumability решает фундаментальную проблему: зачем браузеру переделывать работу сервера? Для интерактивных SSR-приложений с критичным TTI — Qwik заслуживает рассмотрения. Для статики Astro проще, для SPA — React привычнее. Но идея resumability уже влияет на индустрию: React Server Components движутся в том же направлении.