lunx.docs
DocsFrameworksQwik City

Qwik City Adapter

Resumability-first meta-framework with instant page loads, zero JS hydration overhead, 18 optimizer segments, and server actions. Verified: zero JS initial load on content pages.

Qwik 1.5 · Resumable · Zero JS
Resumability

Qwik serializes app state to HTML — on click, it resumes from HTML instead of re-executing JS.

Zero JS initial load

Content pages ship zero JavaScript by default. JS loads lazily only for the interactions you click.

18 optimizer segments

The Qwik optimizer splits components into fine-grained async segments automatically.

Server actions

routeAction$() handles form mutations server-side with automatic type-safe zod validation.

Signals

useSignal() and useStore() for reactive state with automatic subscriptions — no re-renders.

Edge-first SSR

Runs on Cloudflare Workers, Deno Deploy, or any Node.js runtime at the edge.

Setup

bash
npm create lunx-dev@latest my-qwik-app -- --framework qwik --ts
cd my-qwik-app && npm install && npm run dev

Qwik component

typescriptsrc/components/counter/counter.tsx
import { component$, useSignal, useTask$ } from '@builder.io/qwik'

// component$: lazily loaded — only runs when needed
export const Counter = component$<{ initial?: number }>(({ initial = 0 }) => {
  const count  = useSignal(initial)
  const doubled = useSignal(0)

  // useTask$: runs on server and client, auto-tracks dependencies
  useTask$(({ track }) => {
    track(() => count.value)  // re-runs when count changes
    doubled.value = count.value * 2
  })

  return (
    <div class="counter">
      <h2>Count: {count.value}</h2>
      <p>Doubled: {doubled.value}</p>
      {/* onClick$: handler loaded lazily only when user clicks */}
      <button onClick$={() => count.value--}>−</button>
      <button onClick$={() => count.value++}>+</button>
    </div>
  )
})

Route loader and action

typescriptsrc/routes/blog/index.tsx
import { component$ }       from '@builder.io/qwik'
import { routeLoader$, routeAction$, Form, zod$, z } from '@builder.io/qwik-city'

// Runs on server — data available before component renders
export const usePosts = routeLoader$(async ({ env }) => {
  const apiUrl = env.get('API_URL')
  const posts  = await fetch(`${apiUrl}/posts`).then(r => r.json())
  return posts as Post[]
})

// Server action with Zod validation
export const useCreatePost = routeAction$(
  async (data, { fail }) => {
    const saved = await db.post.create({ data })
    if (!saved) return fail(500, { message: 'Failed to create post' })
    return { success: true, id: saved.id }
  },
  zod$({
    title:   z.string().min(1).max(200),
    content: z.string().min(10),
  })
)

export default component$(() => {
  const posts      = usePosts()
  const createPost = useCreatePost()

  return (
    <div>
      <Form action={createPost}>
        <input name="title"   placeholder="Title" />
        <textarea name="content" placeholder="Content..." />
        <button type="submit">Create Post</button>
      </Form>

      {posts.value.map(post => (
        <article key={post.id}><h2>{post.title}</h2></article>
      ))}
    </div>
  )
})
Resumability vs Hydration
Traditional SSR frameworks (React, Vue) must re-execute all component JavaScript during hydration. Qwik serializes component state into HTML attributes — on interaction, it resumes from that serialized state without re-executing the entire component tree. This is why Qwik achieves nearly instant interactivity on any device, regardless of bundle size.