web-meta-framework-qwik
QwikというWebフレームワークを用いて、表示速度を向上させ、サーバーとクライアント間での処理を効率化し、Webサイトやアプリケーション開発をスムーズにするSkill。
📜 元の英語説明(参考)
Qwik resumable framework - zero hydration, $ lazy boundaries, signals, Qwik City file-based routing, routeLoader$, routeAction$, server$ RPC, serialization rules
🇯🇵 日本人クリエイター向け解説
QwikというWebフレームワークを用いて、表示速度を向上させ、サーバーとクライアント間での処理を効率化し、Webサイトやアプリケーション開発をスムーズにするSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o web-meta-framework-qwik.zip https://jpskill.com/download/10294.zip && unzip -o web-meta-framework-qwik.zip && rm web-meta-framework-qwik.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/10294.zip -OutFile "$d\web-meta-framework-qwik.zip"; Expand-Archive "$d\web-meta-framework-qwik.zip" -DestinationPath $d -Force; ri "$d\web-meta-framework-qwik.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
web-meta-framework-qwik.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
web-meta-framework-qwikフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Qwikフレームワークのパターン
クイックガイド: Qwikは再開可能です。アプリケーションの状態をサーバーでシリアライズし、フレームワークのコードを再実行することなく(ハイドレーションなしで)クライアントで再開します。すべての
$サフィックスは、オプティマイザーがコードを別々のチャンクに分割する遅延ロード境界を示します。ユーザーがトリガーするインタラクションのコードのみがダウンロードされます。すべてのコンポーネントにはcomponent$、状態にはuseSignal/useStore、サーバーデータにはrouteLoader$、ミューテーションにはrouteAction$、アドホックなサーバーRPCにはserver$を使用します。重要なメンタルモデル:$境界を越えるものはすべてシリアライズ可能でなければなりません。
<critical_requirements>
重要:このSkillを使用する前に
すべてのコードは、CLAUDE.mdのプロジェクト規約に従う必要があります (kebab-case、名前付きエクスポート、インポート順序、
import type、名前付き定数)
(すべてのコンポーネントをcomponent$()でラップする必要があります - 単純な関数は遅延ロードできず、フックを使用できず、<Slot />を使用できません)
($クロージャでキャプチャされるすべての値がシリアライズ可能であることを確認する必要があります - シリアライズ不可能なキャプチャは型チェックには合格しますが、実行時に失敗します)
(useTask$またはuseResource$でフェッチする代わりに、初期サーバーデータにはrouteLoader$を使用する必要があります - ローダーはレンダリング前に実行され、SSRストリーミングと統合されます)
(event.preventDefault()を呼び出す代わりに、JSX属性としてpreventdefault:clickを使用する必要があります - イベントハンドラーは非同期的にロードされるため、同期的なEvent APIは使用できません)
(routeLoader$とrouteAction$をルートファイル(src/routes/のindex.tsxまたはlayout.tsx)からエクスポートする必要があります - エクスポートされていない、または誤った場所に配置されたローダー/アクションは何もしません)
(ストアのプロパティをトップレベルで分割代入しないでください - 分割代入はリアクティビティを壊します。Proxy参照が失われるためです)
</critical_requirements>
自動検出: Qwik, component$, useSignal, useStore, useTask$, useVisibleTask$, useComputed$, useResource$, routeLoader$, routeAction$, server$, sync$, QRL, noSerialize, @builder.io/qwik, @builder.io/qwik-city, Qwik City, $(), onClick$, onInput$, Slot, q:slot, preventdefault, stoppropagation, useStylesScoped$, resumable, resumability
使用する場面:
- インタラクティビティが重要なWebアプリを構築する場合(ハイドレーションの遅延をゼロにする)
- 従来のハイドレーションでは大量のJSが出荷される複雑なインタラクティビティを持つアプリ
- 手動のコード分割なしで、きめ細かい遅延ロードが必要なプロジェクト
server$を介したサーバーローダー、アクション、およびRPCを備えたフルスタックアプリ- JavaScriptなしでフォームが機能するプログレッシブエンハンスメント
使用しない場面:
- 最小限のインタラクティビティを備えた静的コンテンツサイト(静的サイトジェネレーターを使用)
- チームがQwikに相当するものがないReactエコシステムライブラリに深く投資しているプロジェクト
- シリアライズ不可能なランタイム状態(クラスインスタンス、副作用のあるクロージャ)に大きく依存するアプリ
カバーされる主要なパターン:
- 再開可能性のメンタルモデルと
$サフィックスの規約 component$、props、および<Slot />を使用したコンポーネント定義- リアクティブな状態:
useSignal、useStore、useComputed$ - ライフサイクル:
useTask$、useVisibleTask$、useResource$ - イベント処理:
onClick$、preventdefault:click、sync$ - Qwik Cityルーティング:ファイルベースのルート、レイアウト、動的パラメータ
- サーバーデータ:
routeLoader$、routeAction$、server$ - シリアライゼーションルールと
$境界
詳細なリソース:
- 意思決定フレームワークとアンチパターンについては、reference.mdを参照してください。
コアパターン:
- examples/core.md - コンポーネント、シグナル、ストア、タスク、イベント、スロット
- examples/routing.md - ファイルベースのルーティング、routeLoader$, routeAction$, server$, ミドルウェア
- examples/serialization.md - シリアライゼーションルール、$境界、シリアライズ不可能なパターン
<philosophy>
哲学
Qwikは再開可能性に基づいて構築されています。これは、サーバーがアプリケーションの状態全体(コンポーネントツリー、リスナー、状態)をHTMLにシリアライズし、クライアントがフレームワークのコードを再実行することなく、サーバーが中断したところから正確に再開できるという考え方です。
ハイドレーションフレームワークとの違い:
従来のSSRフレームワークは、サーバーでHTMLをレンダリングし、クライアントですべてのコンポーネントコードを再実行して、イベントリスナーをアタッチし、コンポーネントツリーを再構築します。これがハイドレーションです。クライアントはサーバーの作業を再生します。
Qwikはこれを完全にスキップします。サーバーはすべてをHTMLにシリアライズします。ユーザーがボタンをクリックすると、クリックハンドラーのコードのみがダウンロードされて実行されます。フレームワーク自体、コンポーネントツリー、およびその他すべてのハンドラーは、必要になるまでアンロードされたままになります。
$サフィックスがコアメカニズムです。 $で終わるすべての関数は、遅延ロード境界です。Qwikオプティマイザーは、各$マーカーでコードを別々のチャンクに分割します。これは次のことを意味します。
component$()- コンポーネントのレンダリング関数は、必要なときにのみロードされますonClick$()- クリックハンドラーは、ユーザーがクリックしたときにのみロードされますrouteLoader$()- ローダーはサーバー側でのみ実行されますuseTask$()- タスクは、追跡された依存関係が変更されたときにロードされます
トレードオフ: コードは$境界を越えてシリアライズ可能である必要があるため、$クロージャでシリアライズ不可能な値(クラスインスタンス、関数、DOMノード)をキャプチャできません。この制約は、インスタントインタラクティビティの代償です。
Qwikを使用する場面:
- time-to-interactiveが重要なインタラクティブアプリ
- 従来のハイドレーションでは大量のJSを事前にダウンロードする大規模アプリ
- サーバーロジックに
routeLoader$/routeAction$/server$を活用するフルスタックアプリ - プログレッシブエンハンスメント(QwikフォームはJSなしで機能します)
Qwikを使用しない場面:
- インタラクティビティがほとんどない静的コンテンツサイト
- Qwikに相当するものがないReact固有のライブラリに大きく依存するプロジェクト
- 広範なシリアライズ不可能なランタイム状態を必要とするアプリ
</philosophy>
<patterns>
コアパターン
パターン1:component$を使用したコンポーネント
すべてのQwikコンポーネントはcomponent$()でラップする必要があります。これはオプションではありません。遅延ロード、フックを有効にします。
(原文はここで切り詰められています)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Qwik Framework Patterns
Quick Guide: Qwik is resumable - it serializes application state on the server and resumes on the client without re-executing framework code (no hydration). Every
$suffix marks a lazy-loading boundary where the optimizer splits code into separate chunks. Only the code for the interaction the user triggers gets downloaded. Usecomponent$for all components,useSignal/useStorefor state,routeLoader$for server data,routeAction$for mutations, andserver$for ad-hoc server RPC. The critical mental model: anything crossing a$boundary must be serializable.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST wrap every component in component$() - plain functions cannot be lazy-loaded, cannot use hooks, and cannot use <Slot />)
(You MUST ensure all values captured in a $ closure are serializable - non-serializable captures pass type-checking but fail at runtime)
(You MUST use routeLoader$ for initial server data instead of fetching in useTask$ or useResource$ - loaders run before render and integrate with SSR streaming)
(You MUST use preventdefault:click as a JSX attribute instead of calling event.preventDefault() - event handlers load asynchronously so synchronous Event APIs are unavailable)
(You MUST export routeLoader$ and routeAction$ from route files (index.tsx or layout.tsx in src/routes/) - unexported or misplaced loaders/actions silently do nothing)
(You MUST NOT destructure store properties at the top level - destructuring breaks reactivity because you lose the Proxy reference)
</critical_requirements>
Auto-detection: Qwik, component$, useSignal, useStore, useTask$, useVisibleTask$, useComputed$, useResource$, routeLoader$, routeAction$, server$, sync$, QRL, noSerialize, @builder.io/qwik, @builder.io/qwik-city, Qwik City, $(), onClick$, onInput$, Slot, q:slot, preventdefault, stoppropagation, useStylesScoped$, resumable, resumability
When to use:
- Building web apps where instant interactivity matters (zero hydration delay)
- Apps with complex interactivity that would ship too much JS with traditional hydration
- Projects needing fine-grained lazy loading without manual code-splitting
- Full-stack apps with server loaders, actions, and RPC via
server$ - Progressive enhancement where forms work without JavaScript
When NOT to use:
- Static content sites with minimal interactivity (use a static site generator)
- Projects where the team is deeply invested in React ecosystem libraries that have no Qwik equivalents
- Apps that rely heavily on non-serializable runtime state (class instances, closures with side effects)
Key patterns covered:
- Resumability mental model and the
$suffix convention - Component definition with
component$, props, and<Slot /> - Reactive state:
useSignal,useStore,useComputed$ - Lifecycle:
useTask$,useVisibleTask$,useResource$ - Event handling:
onClick$,preventdefault:click,sync$ - Qwik City routing: file-based routes, layouts, dynamic params
- Server data:
routeLoader$,routeAction$,server$ - Serialization rules and the
$boundary
Detailed Resources:
- For decision frameworks and anti-patterns, see reference.md
Core patterns:
- examples/core.md - Components, signals, stores, tasks, events, slots
- examples/routing.md - File-based routing, routeLoader$, routeAction$, server$, middleware
- examples/serialization.md - Serialization rules, $ boundary, non-serializable patterns
<philosophy>
Philosophy
Qwik is built on resumability - the idea that the server can serialize the entire application state (component tree, listeners, state) into HTML, and the client can resume exactly where the server left off without re-executing any framework code.
How it differs from hydration frameworks:
Traditional SSR frameworks render HTML on the server, then re-execute all component code on the client to attach event listeners and rebuild the component tree. This is hydration - the client replays the server's work.
Qwik skips this entirely. The server serializes everything into the HTML. When a user clicks a button, only the click handler's code downloads and executes. The framework itself, the component tree, and all other handlers stay unloaded until needed.
The $ suffix is the core mechanism. Every function ending in $ is a lazy-loading boundary. The Qwik optimizer splits code at each $ marker into separate chunks. This means:
component$()- the component's render function loads only when neededonClick$()- the click handler loads only when the user clicksrouteLoader$()- the loader runs server-side onlyuseTask$()- the task loads when its tracked dependencies change
The tradeoff: Because code must be serializable to cross $ boundaries, you cannot capture non-serializable values (class instances, functions, DOM nodes) in $ closures. This constraint is the price of instant interactivity.
When to use Qwik:
- Interactive apps where time-to-interactive matters
- Large apps where traditional hydration downloads too much JS upfront
- Full-stack apps leveraging
routeLoader$/routeAction$/server$for server logic - Progressive enhancement (Qwik forms work without JS)
When NOT to use Qwik:
- Static content sites with little interactivity
- Projects heavily dependent on React-specific libraries without Qwik equivalents
- Apps requiring extensive non-serializable runtime state
</philosophy>
<patterns>
Core Patterns
Pattern 1: Components with component$
Every Qwik component must be wrapped in component$(). This is not optional - it enables lazy loading, hooks, and <Slot />.
import { component$, useSignal } from "@builder.io/qwik";
interface CounterProps {
initial?: number;
label: string;
}
export const Counter = component$<CounterProps>(({ initial = 0, label }) => {
const count = useSignal(initial);
return (
<div>
<span>
{label}: {count.value}
</span>
<button onClick$={() => count.value++}>+</button>
</div>
);
});
Why good: component$ enables the optimizer to split this into a lazy chunk, typed props via generic, useSignal for reactive state, onClick$ handler loads only on click
// BAD: Plain function component
export const Counter = (props: { label: string }) => {
// Cannot use hooks here - useSignal will throw
// Cannot use <Slot /> - only works inside component$
return <div>{props.label}</div>;
};
Why bad: Without component$ wrapper, hooks throw at runtime, <Slot /> breaks, optimizer cannot split the code, component is not resumable
Pattern 2: Reactive State - useSignal vs useStore
useSignal holds a single reactive value accessed via .value. useStore holds a reactive object with deep tracking by default.
import { component$, useSignal, useStore } from "@builder.io/qwik";
export const UserProfile = component$(() => {
// useSignal for primitives and flat values
const isEditing = useSignal(false);
const selectedTab = useSignal<"profile" | "settings">("profile");
// useStore for objects/arrays - deep reactivity by default
const user = useStore({
name: "Alice",
email: "alice@example.com",
preferences: {
theme: "dark",
notifications: true,
},
});
return (
<div>
<h1>{user.name}</h1>
{isEditing.value ? (
<input
value={user.name}
onInput$={(_, el) => {
user.name = el.value;
}}
/>
) : (
<button
onClick$={() => {
isEditing.value = true;
}}
>
Edit
</button>
)}
</div>
);
});
Why good: useSignal for simple toggles/selections (accessed via .value), useStore for structured data (mutate properties directly), deep reactivity tracks user.preferences.theme changes automatically
// BAD: Destructuring a store
const { name, email } = useStore({ name: "Alice", email: "a@b.com" });
// name and email are now plain strings - NOT reactive
// Changing them does nothing to the UI
Why bad: Destructuring extracts primitive values from the Proxy, breaking reactivity - you must keep the store reference intact and access store.name directly
Pattern 3: Computed Values with useComputed$
useComputed$ derives values from signals/stores. It re-runs only when dependencies change. Synchronous only.
import { component$, useSignal, useComputed$ } from "@builder.io/qwik";
const TAX_RATE = 0.08;
const FREE_SHIPPING_THRESHOLD = 100;
export const CartSummary = component$(() => {
const subtotal = useSignal(85);
const tax = useComputed$(() => subtotal.value * TAX_RATE);
const shipping = useComputed$(() =>
subtotal.value >= FREE_SHIPPING_THRESHOLD ? 0 : 9.99,
);
const total = useComputed$(() => subtotal.value + tax.value + shipping.value);
return (
<div>
<p>Subtotal: ${subtotal.value.toFixed(2)}</p>
<p>Tax: ${tax.value.toFixed(2)}</p>
<p>Shipping: ${shipping.value.toFixed(2)}</p>
<p>Total: ${total.value.toFixed(2)}</p>
</div>
);
});
Why good: Automatic dependency tracking (no dependency arrays), read-only signal prevents accidental mutation, recomputes only when subtotal changes, named constants for magic numbers
Pattern 4: Tasks and Lifecycle
useTask$ runs before render (server + client). useVisibleTask$ runs after render (browser only). Use track() to declare reactive dependencies.
import {
component$,
useSignal,
useTask$,
useVisibleTask$,
} from "@builder.io/qwik";
import { server$ } from "@builder.io/qwik-city";
const DEBOUNCE_MS = 300;
export const SearchBox = component$(() => {
const query = useSignal("");
const results = useSignal<string[]>([]);
// Runs before render, re-runs when query changes
useTask$(({ track, cleanup }) => {
const searchTerm = track(() => query.value);
if (!searchTerm) {
results.value = [];
return;
}
const debounceTimer = setTimeout(async () => {
const data = await fetchResults(searchTerm);
results.value = data;
}, DEBOUNCE_MS);
cleanup(() => clearTimeout(debounceTimer));
});
return (
<div>
<input
value={query.value}
onInput$={(_, el) => {
query.value = el.value;
}}
/>
<ul>
{results.value.map((r) => (
<li key={r}>{r}</li>
))}
</ul>
</div>
);
});
const fetchResults = server$(async function (term: string) {
// Runs on server only - safe to access DB, env vars, etc.
const db = this.env.get("DATABASE_URL");
// ... query database
return ["result1", "result2"];
});
Why good: track() explicitly declares what triggers re-runs, cleanup() prevents timer leaks, server$ keeps the fetch server-side
When to use each:
| Hook | Runs | Use for |
|---|---|---|
useTask$ |
Server + client, before render | Data init, side effects on state change |
useVisibleTask$ |
Browser only, after render | DOM manipulation, browser APIs, animations |
useComputed$ |
Synchronous, auto-tracked | Derived values (formatting, filtering) |
useResource$ |
Server + client, non-blocking | Async data that shouldn't block render |
Pattern 5: Event Handling
Event handlers use the on{Event}$ convention. Because handlers load asynchronously, synchronous Event APIs (preventDefault, stopPropagation, currentTarget) are NOT available - use declarative attributes instead.
import { component$, useSignal, $ } from "@builder.io/qwik";
export const LoginForm = component$(() => {
const email = useSignal("");
// Extracted handler - wrap with $() for reuse
const handleSubmit = $((e: SubmitEvent) => {
// Submit email.value to server
});
return (
<form preventdefault:submit onSubmit$={handleSubmit}>
<input
type="email"
value={email.value}
onInput$={(_, el) => {
email.value = el.value;
}}
/>
<button type="submit">Login</button>
</form>
);
});
Why good: preventdefault:submit replaces e.preventDefault() declaratively, second parameter of onInput$ gives the element directly (avoiding async currentTarget issues), extracted handler uses $() wrapper
// BAD: Calling synchronous Event APIs
<form onSubmit$={(e) => {
e.preventDefault(); // WRONG - handler is async, this is a no-op
e.stopPropagation(); // WRONG - same reason
}}>
Why bad: Event handlers are lazy-loaded asynchronously, so preventDefault() and stopPropagation() execute too late to have any effect - use preventdefault:submit and stoppropagation:submit attributes instead
Pattern 6: Content Projection with Slot
<Slot /> projects child content. Named slots use the q:slot attribute. Only works inside component$().
import { component$, Slot } from "@builder.io/qwik";
export const Card = component$<{ variant?: "default" | "outlined" }>(
({ variant = "default" }) => {
return (
<div class={`card card-${variant}`}>
<header class="card-header">
<Slot name="header" />
</header>
<div class="card-body">
<Slot /> {/* Default slot */}
</div>
<footer class="card-footer">
<Slot name="footer" />
</footer>
</div>
);
},
);
// Usage
export const Page = component$(() => {
return (
<Card variant="outlined">
<h2 q:slot="header">Card Title</h2>
<p>This goes in the default slot.</p>
<div q:slot="footer">
<button>Action</button>
</div>
</Card>
);
});
Why good: Named slots via q:slot attribute, default slot for main content, parent and child render independently
Gotcha: q:slot must be on a direct child of the component. Wrapping slotted content in an intermediate element breaks projection.
Pattern 7: routeLoader$ for Server Data
routeLoader$ runs on the server before the page renders. It must be exported from a route file. Returns a read-only signal.
// src/routes/products/[id]/index.tsx
import { component$ } from "@builder.io/qwik";
import { routeLoader$ } from "@builder.io/qwik-city";
export const useProduct = routeLoader$(async (requestEvent) => {
const productId = requestEvent.params.id;
const product = await db.products.findById(productId);
if (!product) {
return requestEvent.fail(404, {
errorMessage: `Product ${productId} not found`,
});
}
return product;
});
export default component$(() => {
const product = useProduct(); // ReadonlySignal
return product.value.failed ? (
<p>{product.value.errorMessage}</p>
) : (
<div>
<h1>{product.value.name}</h1>
<p>${product.value.price}</p>
</div>
);
});
Why good: Server-only execution, runs before render (no loading states during SSR), type-safe error handling with fail(), read-only signal prevents accidental client-side mutation
Pattern 8: routeAction$ for Mutations
routeAction$ handles form submissions and mutations. Supports Zod validation. Must be exported from route files.
// src/routes/contact/index.tsx
import { component$ } from "@builder.io/qwik";
import { routeAction$, Form, zod$, z } from "@builder.io/qwik-city";
export const useContactAction = routeAction$(
async (data, requestEvent) => {
// data is validated and typed: { name: string; email: string; message: string }
await sendEmail(data);
return { success: true };
},
zod$({
name: z.string().min(1),
email: z.string().email(),
message: z.string().min(10),
}),
);
export default component$(() => {
const action = useContactAction();
return (
<Form action={action}>
<input name="name" />
<input name="email" type="email" />
<textarea name="message" />
{action.value?.fieldErrors?.email && (
<p class="error">{action.value.fieldErrors.email}</p>
)}
{action.value?.failed && <p class="error">{action.value.message}</p>}
{action.value?.success && <p>Message sent!</p>}
<button type="submit" disabled={action.isRunning}>
{action.isRunning ? "Sending..." : "Send"}
</button>
</Form>
);
});
Why good: <Form> works without JS (progressive enhancement), Zod validation runs server-side with typed errors, action.isRunning for loading state, action.value.failed discriminates success/failure
</patterns>
<red_flags>
RED FLAGS
High Priority Issues
- Using plain functions instead of
component$()- Hooks throw,<Slot />breaks, optimizer cannot split code, component is not resumable - Destructuring store properties -
const { name } = storeextracts a plain value, breaking reactivity. Always accessstore.namedirectly - Calling
event.preventDefault()insideonClick$- Handler loads asynchronously, sopreventDefault()is a no-op. Usepreventdefault:clickattribute - Putting
routeLoader$/routeAction$in non-route files without re-exporting - They silently do nothing unless exported fromsrc/routes/**/index.tsxorlayout.tsx - Capturing non-serializable values in
$closures - Class instances, functions, DOM nodes pass type-checking but fail at runtime with serialization errors
Medium Priority Issues
- Using
useVisibleTask$whenuseTask$would work -useVisibleTask$is browser-only and runs after render; preferuseTask$by default for better SSR - Fetching data in
useTask$instead ofrouteLoader$- Loaders integrate with SSR streaming and run before render;useTask$blocks rendering - Using
client:load-style thinking - Qwik is not an islands framework. Every component is already lazy-loaded at the interaction level. You do not choose what to hydrate. - Over-capturing in
$closures - Closing over an entire store when you only need one property forces Qwik to serialize the whole store
Common Mistakes
- Using
useStore({ deep: true })explicitly - Deep is already the default. Passing it is redundant. Pass{ deep: false }only when you need shallow tracking - Using arrow functions for store methods - Arrow functions lose
thisbinding. Use regularfunction(){}syntax for methods on stores - Confusing
@builder.io/qwikvs@builder.io/qwik-cityimports - Components, signals, tasks from@builder.io/qwik. Routing, loaders, actions,server$from@builder.io/qwik-city - Inline
<style>tags in components - Causes double-loading (SSR + client). UseuseStylesScoped$()or CSS modules instead
Gotchas & Edge Cases
useTask$withouttrack()runs once on mount - Without tracking any signal, it behaves like an initialization hook, not a reactive effectuseTask$blocks rendering - Long async operations inuseTask$delay the component render. UseuseResource$for non-blocking asynconInput$second parameter - The callback receives(event, element)whereelementis the target. Useel.valueinstead ofevent.currentTarget.value(currentTarget is null in async handlers)- Middleware does NOT run for
server$calls - Layout-levelonRequest/onGethandlers are skipped forserver$RPC. Useplugin.tsfor middleware that must run onserver$requests - Version skew with
server$- Client and server must run the same code version. Stale client deployments cause undefined behavior useStylesScoped$uses emoji-based class hashing - Scoped styles apply via emoji characters in selectors. Use:global()to break out when styling<Slot />content- Props are shallowly immutable - Reassigning a primitive prop from a child does nothing. Pass a
Signalinstead if the child needs to write back - Deep store mutations may not trigger updates - Tracking
store[key].nestedrequires tracking the specific property, not just the key.useStorewith{ deep: false }disables deep tracking <Slot />does not work in inline components - Onlycomponent$()functions support<Slot />. Arrow functions or plain functions will silently fail
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST wrap every component in component$() - plain functions cannot be lazy-loaded, cannot use hooks, and cannot use <Slot />)
(You MUST ensure all values captured in a $ closure are serializable - non-serializable captures pass type-checking but fail at runtime)
(You MUST use routeLoader$ for initial server data instead of fetching in useTask$ or useResource$ - loaders run before render and integrate with SSR streaming)
(You MUST use preventdefault:click as a JSX attribute instead of calling event.preventDefault() - event handlers load asynchronously so synchronous Event APIs are unavailable)
(You MUST export routeLoader$ and routeAction$ from route files (index.tsx or layout.tsx in src/routes/) - unexported or misplaced loaders/actions silently do nothing)
(You MUST NOT destructure store properties at the top level - destructuring breaks reactivity because you lose the Proxy reference)
Failure to follow these rules will cause silent runtime failures, broken reactivity, serialization errors, or loaders/actions that never execute.
</critical_reminders>