Разработка фронтенда сайта на Qwik
Qwik — фреймворк от команды Angular/Wiz с принципиально другой моделью исполнения JavaScript. Вместо гидратации (загрузить весь JS → выполнить → прикрепить события) Qwik использует resumability: сервер сериализует состояние приложения прямо в HTML, а браузер «возобновляет» работу с точки, где остановился сервер. Никакого повторного выполнения кода при загрузке страницы.
Это не очередной DX-эксперимент. Это архитектурное решение для сайтов, где каждые 100ms задержки стоят конверсии.
Модель исполнения: чем Qwik отличается от всех остальных
Обычный фреймворк при загрузке страницы:
- Браузер получает HTML (быстро)
- Загружается весь JS-бандл (медленно на 3G/слабых устройствах)
- Фреймворк запускает гидратацию — повторно создаёт компонентное дерево
- Прикрепляются обработчики событий
- Страница становится интерактивной
Qwik:
- Браузер получает HTML с сериализованным состоянием
- JS не загружается вообще до первого взаимодействия пользователя
- При клике/вводе загружается только тот chunk, который нужен для этого события
- Состояние восстанавливается мгновенно из HTML
Результат — O(1) загрузка независимо от размера приложения. Lighthouse score 100 не как исключение, а как базовое состояние.
Архитектура Qwik-проекта
Qwik City — мета-фреймворк поверх Qwik (аналог Next.js для React):
src/
routes/
index.tsx # /
products/
index.tsx # /products
[id]/
index.tsx # /products/:id
components/
ui/
layout/
lib/
api.ts
Каждый файл маршрута экспортирует routeLoader$ для серверных данных и routeAction$ для мутаций — это не хуки, это серверные функции, которые компилятор выносит в отдельные edge-функции.
Ключевые примитивы
$-суффикс — символ оптимизатора. Любая функция с $ будет выделена в отдельный lazy chunk:
import { component$, useSignal, $ } from '@builder.io/qwik';
export const Counter = component$(() => {
const count = useSignal(0);
// Этот обработчик НЕ загружается при рендере страницы
// Загрузится только при первом клике
const increment = $(() => {
count.value++;
});
return (
<button onClick$={increment}>
Кликов: {count.value}
</button>
);
});
routeLoader$ — серверные данные с типобезопасностью:
import { routeLoader$ } from '@builder.io/qwik-city';
import type { RequestHandler } from '@builder.io/qwik-city';
export const useProductData = routeLoader$(async ({ params, env }) => {
const apiKey = env.get('API_KEY');
const res = await fetch(`https://api.example.com/products/${params.id}`, {
headers: { Authorization: `Bearer ${apiKey}` }
});
if (!res.ok) throw new Error('Product not found');
return res.json() as Promise<Product>;
});
export default component$(() => {
const product = useProductData();
return (
<article>
<h1>{product.value.name}</h1>
<p>{product.value.description}</p>
</article>
);
});
routeAction$ — обработка форм и мутации без клиентского JS:
export const useAddToCart = routeAction$(async (data, { cookie }) => {
const cartId = cookie.get('cartId')?.value;
await addItemToCart(cartId, data.productId, data.quantity);
return { success: true };
}, zod$({ productId: z.string(), quantity: z.number().min(1) }));
Форма работает даже без JavaScript в браузере — Qwik использует нативный form submit как fallback.
Оптимизатор и сборка
Qwik использует Vite + qwikVite plugin. Оптимизатор анализирует AST и автоматически:
- Разбивает код на мелкие chunks по
$-границам - Генерирует манифест
q-manifest.jsonс маппингом символов на файлы - Инлайнит сериализованное состояние в HTML через
<script type="qwik/json"> - Применяет prefetch-стратегии для предзагрузки вероятных следующих взаимодействий
Prefetch можно тонко настраивать:
// vite.config.ts
import { qwikVite } from '@builder.io/qwik/optimizer';
import { qwikCity } from '@builder.io/qwik-city/vite';
export default defineConfig({
plugins: [
qwikCity(),
qwikVite({
client: {
outDir: 'dist/client',
},
}),
],
});
Управление состоянием
Qwik не нуждается в Redux или Zustand. Встроенные инструменты:
| Примитив | Назначение |
|---|---|
useSignal<T>() |
Локальное реактивное значение |
useStore<T>() |
Реактивный объект (deep reactive) |
useContext / createContextId |
Глобальный контекст |
useResource$ |
Асинхронные данные с SSR-поддержкой |
Для сложного глобального состояния подходит паттерн с createContextId и useStore:
export const AppContext = createContextId<AppState>('app.state');
export const AppProvider = component$(() => {
const state = useStore<AppState>({
user: null,
theme: 'light',
cart: [],
});
useContextProvider(AppContext, state);
return <Slot />;
});
Тестирование и качество
- Vitest — unit-тесты компонентов и серверных функций
- Playwright — e2e тесты, включая проверку что JS не загружается до взаимодействия
- @builder.io/qwik/testing — утилиты для рендера компонентов в тестах
Метрика, которую стоит отслеживать в CI: размер initial JS bundle должен быть < 5 KB (только Qwik-загрузчик, без компонентов).
Деплой
Qwik City поддерживает адаптеры:
- Cloudflare Pages — edge-функции + глобальный CDN, рекомендуемый вариант
- Vercel Edge Runtime — без холодного старта
- Node.js / Express — для self-hosted
-
AWS Lambda — через адаптер
@builder.io/qwik-city/adapters/aws-lambda - Static — если маршруты не требуют серверной логики
Сроки реализации
- Неделя 1–2: архитектура, роутинг, компонентная база, дизайн-система
- Неделя 3: серверные загрузчики, интеграция с CMS/API, формы
- Неделя 4: оптимизация prefetch-стратегий, SEO (meta, OpenGraph, структурированные данные)
- Неделя 5: тестирование, настройка CI/CD, адаптер для деплоя
- Неделя 6: нагрузочное тестирование, финальная оптимизация Core Web Vitals
Когда Qwik — правильный выбор
Qwik особенно эффективен для контентных сайтов с высокой интерактивностью — e-commerce, медиа, лендинги с формами, порталы. Если большая часть трафика приходит с мобильных устройств в регионах с медленным интернетом — разница в конверсии будет измеримой.
Для внутренних инструментов и дашбордов, где пользователи на десктопах с быстрым соединением, преимущества Qwik менее выражены — там лучше подойдёт SvelteKit или Next.js.







