Qwik achieves instant page load by serializing application state and listeners into the HTML — resuming on the server without re-executing JavaScript on the client. SPA hydration re-runs all component code to attach event listeners; Qwik serializes listener locations and restores them on demand. useSignal() creates reactive values that load their JavaScript only when first used. server$() runs code on the server from a client component with RPC ergonomics. useTask$() handles side effects with automatic dependency tracking. QwikCity adds file-based routing with type-safe loaders and actions. Claude Code generates Qwik components, server functions, route loaders, and the optimization patterns for near-zero TTI web applications.
CLAUDE.md for Qwik Projects
## Qwik Stack
- Version: @builder.io/qwik >= 1.9, @builder.io/qwik-city >= 1.9
- Core: component$(), useSignal(), useStore() — all$ suffix = lazy-loadable chunk
- Data: routeLoader$ (server-side), routeAction$ (mutations), server$ (ad-hoc RPC)
- Side effects: useTask$ (reactive), useVisibleTask$ (browser-only, sparingly)
- Styling: Tailwind CSS or CSS Modules — no CSS-in-JS (serialization overhead)
- Auth: qwik-auth via routeLoader$ with session checks
- Testing: vitest + @builder.io/qwik/testing
Core Qwik Patterns
// src/components/orders/order-dashboard.tsx
import { component$, useSignal, useStore, $, useTask$ } from '@builder.io/qwik'
import type { Signal } from '@builder.io/qwik'
interface Order {
id: string
customerId: string
amount: number
status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'
createdAt: string
}
interface DashboardState {
orders: Order[]
filter: string
loading: boolean
error: string | null
}
export const OrderDashboard = component$(() => {
// useSignal: reactive primitive value
const searchQuery = useSignal('')
const selectedStatus = useSignal('all')
// useStore: reactive object with fine-grained tracking
const state = useStore<DashboardState>({
orders: [],
filter: 'all',
loading: false,
error: null,
})
// useTask$: runs when dependencies change (server + client)
useTask$(async ({ track, cleanup }) => {
// track() subscribes to signal changes
const query = track(() => searchQuery.value)
const status = track(() => selectedStatus.value)
state.loading = true
const abortController = new AbortController()
cleanup(() => abortController.abort())
try {
const params = new URLSearchParams({ q: query, status })
const response = await fetch(`/api/orders?${params}`, {
signal: abortController.signal,
})
state.orders = await response.json()
state.error = null
} catch (e) {
if (!(e instanceof DOMException)) {
state.error = 'Failed to load orders'
}
} finally {
state.loading = false
}
})
const totalRevenue = useComputed$(() =>
state.orders
.filter(o => o.status === 'delivered')
.reduce((sum, o) => sum + o.amount, 0)
)
return (
<div class="max-w-4xl mx-auto p-6">
<div class="flex gap-4 mb-6">
<input
type="search"
value={searchQuery.value}
onInput$={(e) => searchQuery.value = (e.target as HTMLInputElement).value}
placeholder="Search orders..."
class="border rounded px-3 py-2 flex-1"
/>
<select
value={selectedStatus.value}
onChange$={(e) => selectedStatus.value = (e.target as HTMLSelectElement).value}
class="border rounded px-3 py-2"
>
<option value="all">All</option>
<option value="pending">Pending</option>
<option value="shipped">Shipped</option>
<option value="delivered">Delivered</option>
</select>
</div>
{state.loading && <div class="text-center py-8">Loading...</div>}
{state.error && <div class="text-red-500">{state.error}</div>}
<p class="text-gray-500 text-sm mb-4">
{state.orders.length} orders · ${(totalRevenue.value / 100).toFixed(2)} revenue
</p>
<div class="space-y-3">
{state.orders.map((order) => (
<OrderCard key={order.id} order={order} />
))}
</div>
</div>
)
})
// Inline event handlers: $() wraps lazily-loaded chunks
export const OrderCard = component$(({ order }: { order: Order }) => {
const cancelling = useSignal(false)
const handleCancel = $(async () => {
cancelling.value = true
try {
await fetch(`/api/orders/${order.id}/cancel`, { method: 'POST' })
} finally {
cancelling.value = false
}
})
return (
<div class="border rounded-lg p-4 flex justify-between">
<div>
<span class="font-mono text-sm">{order.id}</span>
<p class="font-bold">${(order.amount / 100).toFixed(2)}</p>
</div>
<div class="flex items-center gap-3">
<span class="text-sm">{order.status}</span>
{order.status === 'pending' && (
<button
onClick$={handleCancel}
disabled={cancelling.value}
class="text-red-500 text-sm disabled:opacity-50"
>
{cancelling.value ? 'Cancelling...' : 'Cancel'}
</button>
)}
</div>
</div>
)
})
QwikCity Loaders and Actions
// src/routes/orders/index.tsx — QwikCity route with loader and action
import { component$ } from '@builder.io/qwik'
import { routeLoader$, routeAction$, Form, zod$, z } from '@builder.io/qwik-city'
import { db } from '~/lib/db'
import { getSession } from '~/lib/auth'
// Server-side loader: runs before component renders
export const useOrdersLoader = routeLoader$(async (requestEvent) => {
const session = await getSession(requestEvent)
if (!session?.user) {
throw requestEvent.redirect(302, '/login')
}
const orders = await db.orders.findMany({
where: { userId: session.user.id },
orderBy: { createdAt: 'desc' },
take: 50,
})
return { orders, user: session.user }
})
// Server-side action: handles form POST mutations
export const useCancelOrder = routeAction$(
async (data, requestEvent) => {
const session = await getSession(requestEvent)
if (!session?.user) return requestEvent.fail(401, { message: 'Unauthorized' })
const order = await db.orders.findUnique({ where: { id: data.orderId } })
if (!order || order.userId !== session.user.id) {
return requestEvent.fail(404, { message: 'Order not found' })
}
if (order.status !== 'pending') {
return requestEvent.fail(400, { message: 'Only pending orders can be cancelled' })
}
await db.orders.update({
where: { id: data.orderId },
data: { status: 'cancelled' },
})
return { success: true }
},
zod$({ orderId: z.string().min(1) })
)
export default component$(() => {
const loader = useOrdersLoader()
const cancelAction = useCancelOrder()
return (
<div class="max-w-4xl mx-auto p-6">
<h1 class="text-2xl font-bold mb-6">Orders</h1>
{loader.value.orders.map((order) => (
<div key={order.id} class="border rounded-lg p-4 mb-3">
<div class="flex justify-between items-center">
<span class="font-mono">{order.id}</span>
<span>{order.status}</span>
</div>
{order.status === 'pending' && (
// Progressive enhancement: Form works without JS
<Form action={cancelAction}>
<input type="hidden" name="orderId" value={order.id} />
<button type="submit" class="text-red-500 text-sm mt-2">
{cancelAction.isRunning ? 'Cancelling...' : 'Cancel Order'}
</button>
</Form>
)}
</div>
))}
</div>
)
})
server$ for Ad-hoc RPCs
// Inline server functions — run on server, called from client
import { server$ } from '@builder.io/qwik-city'
const getOrderAnalytics = server$(async function(customerId: string) {
// 'this' is the RequestEvent context
const session = await getSession(this)
if (!session?.user) throw new Error('Unauthorized')
const [totalOrders, totalRevenue, avgOrderValue] = await Promise.all([
db.orders.count({ where: { userId: customerId } }),
db.orders.aggregate({
where: { userId: customerId, status: 'delivered' },
_sum: { amount: true },
}),
db.orders.aggregate({
where: { userId: customerId },
_avg: { amount: true },
}),
])
return {
totalOrders,
totalRevenue: totalRevenue._sum.amount ?? 0,
avgOrderValue: Math.round(avgOrderValue._avg.amount ?? 0),
}
})
// Use in a component
export const CustomerSummary = component$(({ customerId }: { customerId: string }) => {
const analytics = useSignal<Awaited<ReturnType<typeof getOrderAnalytics>> | null>(null)
useVisibleTask$(async () => {
// Load analytics only when component becomes visible
analytics.value = await getOrderAnalytics(customerId)
})
return (
<div>
{analytics.value ? (
<div class="grid grid-cols-3 gap-4">
<div>
<p class="text-2xl font-bold">{analytics.value.totalOrders}</p>
<p class="text-gray-500">Total Orders</p>
</div>
<div>
<p class="text-2xl font-bold">${(analytics.value.totalRevenue / 100).toFixed(0)}</p>
<p class="text-gray-500">Total Revenue</p>
</div>
<div>
<p class="text-2xl font-bold">${(analytics.value.avgOrderValue / 100).toFixed(0)}</p>
<p class="text-gray-500">Avg Order</p>
</div>
</div>
) : (
<div class="animate-pulse h-16 bg-gray-100 rounded" />
)}
</div>
)
})
For the SolidJS framework that achieves similar fine-grained DOM updates without a virtual DOM but uses traditional hydration rather than resumability, see the SolidJS advanced guide for signals and SolidStart. For the Astro framework that pre-renders static HTML and uses islands for interactive components, the Astro advanced guide covers Content Layer and View Transitions. The Claude Skills 360 bundle includes Qwik skill sets covering resumability patterns, server functions, and QwikCity routing. Start with the free tier to try Qwik component generation.