Claude Code for Qwik: Resumable Applications with Zero JavaScript Hydration — Claude Skills 360 Blog
Blog / Frontend / Claude Code for Qwik: Resumable Applications with Zero JavaScript Hydration
Frontend

Claude Code for Qwik: Resumable Applications with Zero JavaScript Hydration

Published: January 16, 2027
Read time: 8 min read
By: Claude Skills 360

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.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free