$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

CLAUDE.md for Qwik Projects

Qwik resumes instead of hydrating — AI generates hydration-based patterns that defeat resumability. Rules for $, useSignal, routeLoader$, and zero-hydration patterns.

7 min read·October 1, 2025

Qwik resumes, not hydrates — AI generates hydration patterns that ship all the JS

$ lazy boundaries, useSignal, routeLoader$, actions, and zero-hydration patterns

Why Qwik Needs Rules That Prevent Hydration Patterns

Qwik is the only framework built around resumability — the page loads with zero JavaScript and lazily downloads only the code needed for the specific interaction the user triggers. There's no hydration step. AI assistants don't understand this model at all — they generate React/Next.js hydration patterns that force all component code to download on page load, defeating Qwik's entire value proposition.

The most critical AI failures: importing modules eagerly (breaking lazy loading), using React-style event handlers instead of $-suffixed handlers, generating useEffect-like patterns instead of Qwik's task system, and treating components as functions that re-execute (Qwik components execute once on the server and resume on the client without re-execution).

Qwik's $ suffix is the core concept AI must understand: any function suffixed with $ creates a lazy-loading boundary. The optimizer splits code at every $ — so event handlers, data loaders, and side effects are only downloaded when needed.

Rule 1: The $ Suffix and Lazy Loading

The rule: 'The $ suffix creates a lazy-loading boundary. Use it on: event handlers (onClick$), component definitions (component$), tasks (useTask$, useVisibleTask$), route loaders (routeLoader$), and actions (routeAction$). Every $ is a point where the optimizer can split code into a separate chunk that's only downloaded when needed. Never import eagerly what can be lazy-loaded with $.'

For event handlers: 'Always use $-suffixed handlers: onClick$={() => count.value++}. Never use plain onClick — it forces the handler code to be included in the initial bundle. The $ handler is only downloaded when the user actually clicks. This is Qwik's zero-JS-until-interaction model.'

For understanding: 'Think of $ as 'this function will be downloaded separately.' component$() creates a lazy component. routeLoader$() creates a lazy data loader. useTask$() creates a lazy side effect. The more code is behind $, the less JavaScript ships on initial page load.'

  • $ = lazy boundary — code after $ downloads only when needed
  • onClick$ for handlers — never plain onClick (forces eager download)
  • component$ for components — routeLoader$ for data — routeAction$ for mutations
  • useTask$ for side effects — useVisibleTask$ for DOM-dependent effects
  • More $ = less initial JS = faster page load
💡 $ = Lazy Download

Every $ suffix is a code-splitting point. onClick$ downloads the handler only when clicked. routeLoader$ downloads only when the route is visited. More $ = less initial JS = faster load. Think of $ as 'download separately.'

Rule 2: Signals for Reactive State

The rule: 'Use useSignal for primitive reactive state: const count = useSignal(0). Access and set with .value: count.value++. Use useStore for object state: const state = useStore({ name: "", items: [] }). Stores are deeply reactive — access properties directly, not .value. Use useComputed$ for derived values. Never use useState or useReducer — they don't exist in Qwik.'

For serialization: 'All state must be serializable — Qwik serializes state to HTML for resumability. No functions, Promises, DOM nodes, or class instances in signals or stores. If you need non-serializable data, use useVisibleTask$ to create it on the client side.'

For the difference from React: 'Qwik signals don't cause re-renders — they update only the specific DOM nodes that reference .value. This is fine-grained reactivity like Solid, not component-level like React. Never wrap components in memo() or useMemo — they don't exist and aren't needed.'

⚠️ Must Be Serializable

All Qwik state must be serializable — it's written to HTML for resumability. No functions, Promises, DOM nodes, or class instances in signals or stores. Non-serializable data crashes resumability.

Rule 3: Data Loading with routeLoader$

The rule: 'Use routeLoader$ for all route-level data loading. Route loaders run on the server and provide data to the page: export const useProducts = routeLoader$(async () => { return db.select().from(products); }). Access in components with useProducts(): const products = useProducts(). Loaders are cached per navigation — they don't re-run on client-side interactions.'

For dependent data: 'Use routeLoader$ with requestEvent for access to cookies, headers, and URL params: routeLoader$(async (requestEvent) => { const userId = requestEvent.cookie.get("userId"); }). Use redirect() for auth checks: if (!user) throw requestEvent.redirect(302, "/login").'

AI generates useEffect + fetch patterns from React. In Qwik, routeLoader$ is the equivalent of Next.js getServerSideProps — it runs on the server, provides data to the page, and is type-safe. Client-side fetching is only for user-initiated actions.

Rule 4: Mutations with routeAction$ and Form

The rule: 'Use routeAction$ for all data mutations: export const useAddTodo = routeAction$(async (data, requestEvent) => { ... }). Use Qwik City's <Form> component for form submissions: <Form action={addTodo}><input name="title" /><button>Add</button></Form>. Forms work without JavaScript (progressive enhancement). Use zod$ for server-side validation integrated with the action.'

For validation: 'Use zod$ as the second argument to routeAction$ for schema validation: routeAction$(handler, zod$({ title: z.string().min(1), done: z.boolean() })). Validation errors are automatically typed and available in the component via action.value?.fieldErrors.'

For optimistic UI: 'Use action.isRunning to show pending states. Use action.value for the action's return value. Use action.formData for optimistic access to the submitted form data before the action completes.'

  • routeAction$ for mutations — Form component for submissions
  • Progressive enhancement: forms work without JS
  • zod$ for integrated server-side validation — typed errors
  • action.isRunning for pending — action.formData for optimistic UI
  • Never use fetch for form submissions — use routeAction$ + Form
Forms Without JS

Qwik City forms work without JavaScript — progressive enhancement built in. routeAction$ + <Form> gives you server-side mutation handling, typed validation with zod$, and optimistic UI when JS is available.

Rule 5: Qwik City Conventions

The rule: 'Qwik City is Qwik's meta-framework (like Next.js for React). Use file-based routing in src/routes/: index.tsx, about/index.tsx, blog/[slug]/index.tsx. Use layout.tsx for nested layouts. Use plugin@auth.ts for middleware. Use useLocation() for URL info, useNavigate() for programmatic navigation.'

For head management: 'Export a head property from routes for SEO: export const head: DocumentHead = { title: "My Page", meta: [{ name: "description", content: "..." }] }. Use useDocumentHead() in layouts to render head tags. For dynamic head, export a function: export const head: DocumentHead = ({ resolveValue }) => ({ title: resolveValue(useProduct).name }).'

For middleware: 'Use plugin@[name].ts files in the routes directory for middleware that runs before route handlers. Common use: auth checking, rate limiting, CORS headers. Middleware has access to requestEvent for cookies, headers, and redirect.'

Complete Qwik Rules Template

Consolidated rules for Qwik + Qwik City projects.

  • $ suffix on all lazy boundaries: onClick$, component$, routeLoader$, routeAction$
  • useSignal for primitives (.value) — useStore for objects (deep reactive) — all serializable
  • routeLoader$ for data — runs on server, cached per navigation, typed access
  • routeAction$ + Form for mutations — zod$ for validation — progressive enhancement
  • File-based routing in src/routes/ — layout.tsx for nesting — plugin@*.ts for middleware
  • DocumentHead export for SEO — useDocumentHead in layouts
  • No hydration patterns — resumability means code downloads on interaction, not on load
  • Vitest for unit tests — Playwright for e2e — $ aware testing patterns