Qwik is a revolutionary framework that rethinks how web applications are built and delivered. By introducing the concept of resumability, Qwik achieves instant page loads and exceptional performance without requiring hydration on the client.
What is Qwik?
Qwik is a new kind of framework that delivers lightning-fast page loads by shipping minimal JavaScript and resuming application state where the server left off. Unlike traditional frameworks that require downloading and executing the entire framework on the client, Qwik breaks applications into tiny, lazy-loaded chunks.
Key Features
- Resumability: Continue where the server left off
- Zero Hydration: No framework execution on client startup
- Lazy Everything: Components, event handlers, and data loading are lazy by default
- Fine-Grained Reactivity: Updates only what changes
- Edge-Ready: Runs on any JavaScript runtime
- Familiar Syntax: React-like component model
Installation and Setup
Creating a New Qwik App
npm create qwik@latest
cd my-qwik-app
npm install
npm run devProject Structure
my-qwik-app/
├── src/
│ ├── components/
│ ├── routes/
│ ├── entry.ssr.tsx
│ ├── root.tsx
│ └── global.css
├── public/
├── package.json
└── tsconfig.jsonQwik Components
Basic Component Structure
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return <div>Hello Qwik!</div>;
});The $ Suffix
Qwik uses the $ suffix to mark things for lazy loading:
import { component$, useSignal } from '@builder.io/qwik';
export const Counter = component$(() => {
const count = useSignal(0);
return (
<div>
<span>Count: {count.value}</span>
<button onClick$={() => count.value++}>
Increment
</button>
</div>
);
});Component Best Practices
- 1Always Use component$: Marks component for lazy loading
- 2Use $ for Event Handlers: Lazy loads handler code
- 3Keep Components Focused: Single responsibility
- 4Type Props: Leverage TypeScript
interface CardProps {
title: string;
description: string;
}
export const Card = component$((props: CardProps) => {
return (
<div class="card">
<h2>{props.title}</h2>
<p>{props.description}</p>
</div>
);
});State Management
useSignal
Signals hold reactive state:
import { component$, useSignal } from '@builder.io/qwik';
export default component$(() => {
const count = useSignal(0);
const text = useSignal('');
return (
<div>
<input
value={text.value}
onInput$={(e) => {
text.value = (e.target as HTMLInputElement).value;
}}
/>
<p>Typed: {text.value}</p>
<button onClick$={() => count.value++}>
Count: {count.value}
</button>
</div>
);
});useStore
For complex state, use stores:
import { component$, useStore } from '@builder.io/qwik';
interface User {
name: string;
email: string;
}
export default component$(() => {
const user = useStore<User>({
name: 'John Doe',
email: 'john@example.com',
});
return (
<div>
<input
value={user.name}
onInput$={(e) => {
user.name = (e.target as HTMLInputElement).value;
}}
/>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
</div>
);
});useComputed
Create derived state:
import { component$, useSignal, useComputed$ } from '@builder.io/qwik';
export default component$(() => {
const firstName = useSignal('John');
const lastName = useSignal('Doe');
const fullName = useComputed$(() => {
return `${firstName.value} ${lastName.value}`;
});
return (
<div>
<input
value={firstName.value}
onInput$={(e) => {
firstName.value = (e.target as HTMLInputElement).value;
}}
/>
<p>Full Name: {fullName.value}</p>
</div>
);
});Conditional Rendering
Show Component
import { component$, useSignal, Show } from '@builder.io/qwik';
export default component$(() => {
const isLoggedIn = useSignal(false);
return (
<Show
when={isLoggedIn.value}
fallback={<button onClick$={() => (isLoggedIn.value = true)}>Login</button>}
>
<div>
<h1>Welcome back!</h1>
<button onClick$={() => (isLoggedIn.value = false)}>Logout</button>
</div>
</Show>
);
});Multiple Conditions
import { component$, useSignal, Show, When } from '@builder.io/qwik';
export default component$(() => {
const status = useSignal<'loading' | 'success' | 'error'>('loading');
return (
<Show
when={status.value === 'loading'}
fallback={
<Show
when={status.value === 'success'}
fallback={<div>Error loading data</div>}
>
<div>Data loaded successfully!</div>
</Show>
}
>
<div>Loading...</div>
</Show>
);
});List Rendering
For Component
import { component$, useSignal, For } from '@builder.io/qwik';
interface Todo {
id: number;
text: string;
completed: boolean;
}
export default component$(() => {
const todos = useSignal<Todo[]>([
{ id: 1, text: 'Learn Qwik', completed: false },
{ id: 2, text: 'Build an app', completed: false },
]);
return (
<ul>
<For each={todos.value}>
{(todo) => (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange$={() => {
todo.completed = !todo.completed;
}}
/>
<span>{todo.text}</span>
</li>
)}
</For>
</ul>
);
});Key Prop
For efficient updates, use keys:
<For each={items.value}>
{(item, index) => (
<div key={item.id}>
{item.name}
</div>
)}
</For>Event Handling
Event Listeners
import { component$ } from '@builder.io/qwik';
export default component$(() => {
const handleClick$ = () => {
console.log('Button clicked!');
};
const handleInput$ = (e: Event) => {
const input = e.target as HTMLInputElement;
console.log('Input value:', input.value);
};
return (
<div>
<button onClick$={handleClick$}>Click Me</button>
<input onInput$={handleInput$} />
</div>
);
});Event Modifiers
export default component$(() => {
return (
<div>
<button onClick$={(e) => {
e.preventDefault();
console.log('Button clicked, but form not submitted');
}}>
Submit
</button>
<input
onKeyDown$={(e) => {
if (e.key === 'Enter') {
console.log('Enter key pressed');
}
}}
/>
</div>
);
});Lifecycle Hooks
useTask$
Run code in response to state changes:
import { component$, useSignal, useTask$ } from '@builder.io/qwik';
export default component$(() => {
const count = useSignal(0);
useTask$(({ track }) => {
track(() => count.value);
console.log('Count changed:', count.value);
});
return (
<button onClick$={() => count.value++}>
Count: {count.value}
</button>
);
});useVisibleTask$
Run code when component is visible:
import { component$, useVisibleTask$ } from '@builder.io/qwik';
export default component$(() => {
useVisibleTask$(() => {
console.log('Component is visible in the viewport');
});
return <div>Watch me appear</div>;
});useOnDocument
Listen to document-level events:
import { component$, useOnDocument, $ } from '@builder.io/qwik';
export default component$(() => {
useOnDocument(
'click',
$((event) => {
console.log('Document clicked', event);
})
);
return <div>Click anywhere</div>;
});Props and Children
Passing Props
import { component$, type PropsOf } from '@builder.io/qwik';
interface ButtonProps {
variant?: 'primary' | 'secondary';
onClick$?: () => void;
}
export const Button = component$<ButtonProps>((props) => {
return (
<button
class={`btn btn-${props.variant || 'primary'}`}
onClick$={props.onClick$}
>
{props.children}
</button>
);
});
// Usage
export default component$(() => {
return (
<Button variant="primary" onClick$={() => console.log('Clicked')}>
Click Me
</Button>
);
});Children Prop
interface CardProps {
title: string;
children: any;
}
export const Card = component$<CardProps>((props) => {
return (
<div class="card">
<h2>{props.title}</h2>
<div class="content">{props.children}</div>
</div>
);
});Context API
Creating Context
import { createContext, useContextProvider, useContext } from '@builder.io/qwik';
interface ThemeContext {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
export const ThemeContext = createContext<ThemeContext>('theme');
export const ThemeProvider = component$(({ children }: { children: any }) => {
const theme = useSignal<'light' | 'dark'>('light');
const store = useStore({
theme: theme.value,
toggleTheme: $(() => {
theme.value = theme.value === 'light' ? 'dark' : 'light';
store.theme = theme.value;
}),
});
useContextProvider(ThemeContext, store);
return <>{children}</>;
});
// Consuming context
export const ThemedComponent = component$(() => {
const theme = useContext(ThemeContext);
return (
<div class={theme.theme}>
Current theme: {theme.theme}
<button onClick$={theme.toggleTheme}>Toggle</button>
</div>
);
});Loading Resources
useResource
Fetch data reactively:
import { component$, useResource, useSignal } from '@builder.io/qwik';
export default component$(() => {
const userId = useSignal(1);
const userResource = useResource$(({ track }) => {
track(() => userId.value);
return fetch(`/api/users/${userId.value}`).then(r => r.json());
});
return (
<div>
<button onClick$={() => userId.value++}>Next User</button>
<Resource
value={userResource}
onPending={() => <div>Loading...</div>}
onRejected={(error) => <div>Error: {error.message}</div>}
onResolved={(user) => (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)}
/>
</div>
);
});useSuspense
Show loading state during data fetching:
import { component$, useResource, useSuspense } from '@builder.io/qwik';
export default component$(() => {
const productsResource = useResource$(() => {
return fetch('/api/products').then(r => r.json());
});
return (
<Suspense fallback={<div>Loading products...</div>}>
<Resource value={productsResource}>
{(products) => (
<div>
{products.map((p: any) => (
<div key={p.id}>{p.name}</div>
))}
</div>
)}
</Resource>
</Suspense>
);
});Server-Side Rendering
Qwik City Integration
Qwik works seamlessly with Qwik City for routing:
// src/routes/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
export const useProducts = routeLoader$(async () => {
const response = await fetch('https://api.example.com/products');
const products = await response.json();
return products;
});
export default component$(() => {
const products = useProducts();
return (
<div>
<h1>Products</h1>
{products.value.map((product: any) => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
});Best Practices
- 1Always Use $ Suffix: Mark components and handlers for lazy loading
- 2Minimize Client JavaScript: Keep code on server when possible
- 3Use Signals: For reactive state
- 4Leverage Server: Load data on server
- 5Optimize Imports: Import only what you need
- 6Test Performance: Monitor bundle size and load times
Conclusion
Qwik represents a paradigm shift in how we build web applications. By embracing resumability and lazy loading, Qwik delivers exceptional performance without sacrificing developer experience. The familiar React-like syntax combined with innovative concepts makes it easy to adopt while delivering superior results.
Start building with Qwik today and experience the future of web development—where instant page loads and minimal JavaScript are the norm, not the exception.