What Qwik Does
- Resumability: Skip hydration entirely — apps resume from server state
- O(1) Time-to-Interactive: Startup time independent of app size
- Fine-Grained Lazy Loading: Auto-split code at function boundaries
- Zero JS on Initial Load: Interactive pages with 0 JavaScript
- React-like Syntax: JSX and component model familiar to React devs
- Qwik City: Meta-framework with routing, layouts, server actions
- SSR/SSG/Static: Flexible rendering modes
- Optimized Bundles: Automatic code splitting at event handler level
- Signal-based: Fine-grained reactivity like SolidJS
Key Concept: Resumability
Traditional Hydration (React, Vue, etc.):
Server: Render HTML
Client: Download JS → Execute all components → Attach listeners → Interactive
Time: Proportional to app size
Qwik Resumability:
Server: Render HTML + serialize state
Client: No JS execution needed
User clicks button → Load ONLY that handler → Execute
Time: O(1) - constant regardless of app sizeGetting Started
Create Project
npm create qwik@latest
# Choose:
# - my-app (name)
# - Basic App (template)
# - Select Qwik City (meta framework)Project Structure
my-app/
├── src/
│ ├── routes/ ← File-based routing
│ │ ├── index.tsx
│ │ ├── about/
│ │ │ └── index.tsx
│ │ └── blog/
│ │ └── [slug]/
│ │ └── index.tsx
│ ├── components/
│ │ └── header/
│ │ └── header.tsx
│ ├── root.tsx ← Root component
│ └── entry.ssr.tsx ← SSR entry
├── vite.config.ts
└── package.jsonBasic Components
Counter Component
import { component$, useSignal, $ } from '@builder.io/qwik';
export default component$(() => {
const count = useSignal(0);
return (
<div>
<h1>Counter: {count.value}</h1>
<button onClick$={() => count.value++}>
Increment
</button>
</div>
);
});Note: component$ and onClick$ — the $ marker tells Qwik to split this code for lazy loading.
Important: Only Runs on Interaction
import { component$, useSignal } from '@builder.io/qwik';
export default component$(() => {
const count = useSignal(0);
// This console.log runs on server during SSR
console.log('Component setup');
return (
<div>
<p>Count: {count.value}</p>
<button onClick$={() => {
// THIS CODE is lazy-loaded ONLY when button clicked
// Not included in initial JS bundle
count.value++;
}}>
Click
</button>
</div>
);
});Async Data with useResource$
import { component$, useResource$, Resource } from '@builder.io/qwik';
export default component$(() => {
const postsResource = useResource$(async () => {
const res = await fetch('https://api.example.com/posts');
return res.json();
});
return (
<Resource
value={postsResource}
onPending={() => <div>Loading...</div>}
onRejected={(error) => <div>Error: {error.message}</div>}
onResolved={(posts) => (
<ul>
{posts.map((post) => <li key={post.id}>{post.title}</li>)}
</ul>
)}
/>
);
});Qwik City (Meta Framework)
File-Based Routing
// src/routes/blog/[slug]/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
// Runs on server before rendering
export const useBlogPost = routeLoader$(async ({ params }) => {
const post = await db.post.findUnique({ where: { slug: params.slug } });
return post;
});
export default component$(() => {
const post = useBlogPost();
return (
<article>
<h1>{post.value?.title}</h1>
<div dangerouslySetInnerHTML={post.value?.content} />
</article>
);
});Server Actions
import { component$ } from '@builder.io/qwik';
import { Form, routeAction$, z, zod$ } from '@builder.io/qwik-city';
// Server-side action
export const useCreatePost = routeAction$(
async (data, { redirect }) => {
const post = await db.post.create({ data });
throw redirect(302, `/blog/${post.slug}`);
},
zod$({
title: z.string().min(1),
content: z.string().min(10),
})
);
export default component$(() => {
const createPost = useCreatePost();
return (
<Form action={createPost}>
<input name="title" placeholder="Title" />
<textarea name="content" placeholder="Content" />
<button type="submit">Create Post</button>
{createPost.value?.failed && (
<p>Errors: {JSON.stringify(createPost.value.fieldErrors)}</p>
)}
</Form>
);
});Layouts
// src/routes/layout.tsx
import { component$, Slot } from '@builder.io/qwik';
export default component$(() => {
return (
<>
<header>My Site</header>
<main>
<Slot /> {/* Child routes render here */}
</main>
<footer>© 2024</footer>
</>
);
});Performance Characteristics
Initial Page Load (Traditional React):
HTML: 10KB
JS: 300KB+ (React + app code)
Time-to-Interactive: 2-5 seconds
Initial Page Load (Qwik):
HTML: 15KB (includes serialized state)
JS: 0KB (nothing to execute!)
Time-to-Interactive: ~0 seconds
When user interacts:
Download 3KB chunk for specific handler
Execute handler
Update DOMWhy Resumability?
Problem with hydration:
1. Server renders HTML (fast)
2. User sees content
3. Client downloads framework + components (slow)
4. Client RE-EXECUTES all components (builds VDOM)
5. Reconciles with existing DOM
6. Attaches event listeners
7. NOW interactive (1-5 seconds later)
Hydration = doing everything twiceQwik solves this by:
1. Server renders HTML + serializes component state
2. Client sees content
3. Client needs ZERO JavaScript to be interactive
4. On first user interaction, download ONLY the specific handler
5. Execute handler, update DOM
6. DoneQwik vs Alternatives
| Feature | Qwik | React | Solid | Astro |
|---|---|---|---|---|
| Hydration | None (Resume) | Required | Required | Islands |
| Time-to-Interactive | O(1) | O(n) | O(n) | O(islands) |
| Initial JS | 0KB | Full bundle | Full bundle | Minimal |
| Syntax | JSX + $ markers | JSX | JSX | JSX/MD |
| Learning curve | Medium | Easy | Easy | Easy |
| Best for | Large apps | General | Performance | Content sites |
FAQ
Q: What does the $ symbol in Qwik mean?
A: $ is Qwik's "lazy loading boundary" marker. Each function marked with $ is compiled into an independent chunk that loads on demand. This is the core mechanism behind Qwik's fine-grained code splitting.
Q: Qwik or Astro — which should I choose? A: Astro uses "island architecture" — static HTML + a few interactive islands, great for content sites. Qwik makes the entire app resumable, suited to highly interactive web applications. Pick Astro for content blogs and Qwik for SaaS applications.
Q: Is the ecosystem mature? A: Qwik is relatively new (released in 2022) and its ecosystem is still growing. But the core team (including Angular's creator) is highly active, and large companies like Builder.io use it in production. Well worth considering for new projects that want maximum performance.
Sources & Credits
- GitHub: QwikDev/qwik — 22K+ ⭐ | MIT
- Official site: qwik.dev