Claude Code Plugins

Community-maintained marketplace

Feedback

Builds instant-loading web applications with Qwik's resumable architecture and fine-grained lazy loading. Use when creating highly performant sites, when user mentions Qwik, resumability, zero hydration, or O(1) startup time.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name qwik
description Builds instant-loading web applications with Qwik's resumable architecture and fine-grained lazy loading. Use when creating highly performant sites, when user mentions Qwik, resumability, zero hydration, or O(1) startup time.

Qwik

Framework that delivers instant-loading applications through resumability - apps boot with ~1kb JS regardless of complexity.

Quick Start

# Create new project
npm create qwik@latest

# Options:
# - Empty App
# - QwikCity App (recommended)
# - Playground

cd my-qwik-app
npm run dev

Core Concepts

Resumability vs Hydration

Traditional frameworks hydrate (re-execute all code on client). Qwik resumes (continues where server left off):

  • No hydration - App state serialized in HTML
  • O(1) startup - Constant time regardless of app size
  • Lazy execution - Code loads only when needed

The $ Convention

The $ suffix marks serialization boundaries for the optimizer:

import { component$, useSignal, $ } from '@builder.io/qwik';

// component$ - Lazy-loaded component
export default component$(() => {
  const count = useSignal(0);

  // $() - Lazy-loaded callback
  const handleClick = $(() => {
    count.value++;
  });

  return <button onClick$={handleClick}>Count: {count.value}</button>;
});

Components

Basic Component

import { component$ } from '@builder.io/qwik';

export const Greeting = component$(() => {
  return <h1>Hello, Qwik!</h1>;
});

Props

import { component$ } from '@builder.io/qwik';

interface ButtonProps {
  label: string;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

export const Button = component$<ButtonProps>((props) => {
  return (
    <button
      class={`btn btn-${props.variant ?? 'primary'}`}
      disabled={props.disabled}
    >
      {props.label}
    </button>
  );
});

// Usage
<Button label="Click me" variant="primary" />

Slots (Content Projection)

import { component$, Slot } from '@builder.io/qwik';

export const Card = component$(() => {
  return (
    <div class="card">
      <div class="card-header">
        <Slot name="header" />
      </div>
      <div class="card-body">
        <Slot /> {/* Default slot */}
      </div>
      <div class="card-footer">
        <Slot name="footer" />
      </div>
    </div>
  );
});

// Usage
<Card>
  <div q:slot="header">Card Title</div>
  <p>Card content goes here</p>
  <button q:slot="footer">Action</button>
</Card>

State Management

Signals (Primitives)

import { component$, useSignal } from '@builder.io/qwik';

export default component$(() => {
  const count = useSignal(0);
  const name = useSignal('');

  return (
    <div>
      <p>Count: {count.value}</p>
      <button onClick$={() => count.value++}>Increment</button>

      <input
        value={name.value}
        onInput$={(e) => (name.value = (e.target as HTMLInputElement).value)}
      />
      <p>Hello, {name.value}!</p>
    </div>
  );
});

Stores (Objects)

import { component$, useStore } from '@builder.io/qwik';

interface TodoItem {
  id: number;
  text: string;
  completed: boolean;
}

export default component$(() => {
  const state = useStore({
    todos: [] as TodoItem[],
    newTodo: '',
  });

  const addTodo = $(() => {
    if (state.newTodo.trim()) {
      state.todos.push({
        id: Date.now(),
        text: state.newTodo,
        completed: false,
      });
      state.newTodo = '';
    }
  });

  return (
    <div>
      <input
        value={state.newTodo}
        onInput$={(e) => (state.newTodo = (e.target as HTMLInputElement).value)}
      />
      <button onClick$={addTodo}>Add</button>

      <ul>
        {state.todos.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange$={() => (todo.completed = !todo.completed)}
            />
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
});

Computed Values

import { component$, useSignal, useComputed$ } from '@builder.io/qwik';

export default component$(() => {
  const price = useSignal(100);
  const quantity = useSignal(2);

  const total = useComputed$(() => {
    return price.value * quantity.value;
  });

  return (
    <div>
      <p>Price: ${price.value}</p>
      <p>Quantity: {quantity.value}</p>
      <p>Total: ${total.value}</p>
    </div>
  );
});

Context

import {
  component$,
  createContextId,
  useContextProvider,
  useContext,
  useStore,
} from '@builder.io/qwik';

// Define context
interface UserContext {
  name: string;
  isLoggedIn: boolean;
}

export const UserContextId = createContextId<UserContext>('user-context');

// Provider component
export const App = component$(() => {
  const userState = useStore<UserContext>({
    name: 'Guest',
    isLoggedIn: false,
  });

  useContextProvider(UserContextId, userState);

  return <UserProfile />;
});

// Consumer component
export const UserProfile = component$(() => {
  const user = useContext(UserContextId);

  return (
    <div>
      <p>Welcome, {user.name}!</p>
      {!user.isLoggedIn && (
        <button onClick$={() => {
          user.name = 'John';
          user.isLoggedIn = true;
        }}>
          Log In
        </button>
      )}
    </div>
  );
});

Tasks & Lifecycle

useTask$ (Reactive Side Effects)

import { component$, useSignal, useTask$ } from '@builder.io/qwik';

export default component$(() => {
  const query = useSignal('');
  const results = useSignal<string[]>([]);

  // Runs on server and client, tracks dependencies
  useTask$(async ({ track, cleanup }) => {
    const searchQuery = track(() => query.value);

    if (!searchQuery) {
      results.value = [];
      return;
    }

    // Debounce
    const controller = new AbortController();
    cleanup(() => controller.abort());

    const timer = setTimeout(async () => {
      const res = await fetch(`/api/search?q=${searchQuery}`, {
        signal: controller.signal,
      });
      results.value = await res.json();
    }, 300);

    cleanup(() => clearTimeout(timer));
  });

  return (
    <div>
      <input
        value={query.value}
        onInput$={(e) => (query.value = (e.target as HTMLInputElement).value)}
      />
      <ul>
        {results.value.map((r) => (
          <li key={r}>{r}</li>
        ))}
      </ul>
    </div>
  );
});

useVisibleTask$ (Client-Only)

import { component$, useSignal, useVisibleTask$ } from '@builder.io/qwik';

export default component$(() => {
  const canvasRef = useSignal<HTMLCanvasElement>();
  const mousePos = useSignal({ x: 0, y: 0 });

  // Only runs on client when component is visible
  useVisibleTask$(({ cleanup }) => {
    const canvas = canvasRef.value;
    if (!canvas) return;

    const ctx = canvas.getContext('2d');

    const handleMouseMove = (e: MouseEvent) => {
      mousePos.value = { x: e.clientX, y: e.clientY };
    };

    window.addEventListener('mousemove', handleMouseMove);
    cleanup(() => window.removeEventListener('mousemove', handleMouseMove));
  });

  return (
    <div>
      <canvas ref={canvasRef} width={400} height={300} />
      <p>Mouse: {mousePos.value.x}, {mousePos.value.y}</p>
    </div>
  );
});

Event Handling

import { component$, useSignal, $ } from '@builder.io/qwik';

export default component$(() => {
  const message = useSignal('');

  // Inline handler
  const handleClick = $(() => {
    message.value = 'Button clicked!';
  });

  // With event parameter
  const handleInput = $((event: Event) => {
    const target = event.target as HTMLInputElement;
    message.value = target.value;
  });

  // Prevent default
  const handleSubmit = $((event: SubmitEvent) => {
    event.preventDefault();
    // Handle form submission
  });

  return (
    <form preventdefault:submit onSubmit$={handleSubmit}>
      <input onInput$={handleInput} />
      <button onClick$={handleClick}>Click</button>
      <p>{message.value}</p>
    </form>
  );
});

Qwik City (Routing)

File-Based Routes

src/routes/
  layout.tsx           # Root layout
  index.tsx            # /
  about/
    index.tsx          # /about
  blog/
    index.tsx          # /blog
    [slug]/
      index.tsx        # /blog/:slug
  api/
    users/
      index.ts         # API endpoint: /api/users

Page Component

// src/routes/index.tsx
import { component$ } from '@builder.io/qwik';
import type { DocumentHead } from '@builder.io/qwik-city';

export default component$(() => {
  return (
    <div>
      <h1>Welcome to Qwik</h1>
    </div>
  );
});

export const head: DocumentHead = {
  title: 'Home | My App',
  meta: [
    { name: 'description', content: 'Welcome to my Qwik application' },
  ],
};

Layout

// src/routes/layout.tsx
import { component$, Slot } from '@builder.io/qwik';
import { Link } from '@builder.io/qwik-city';

export default component$(() => {
  return (
    <div class="app">
      <nav>
        <Link href="/">Home</Link>
        <Link href="/about">About</Link>
        <Link href="/blog">Blog</Link>
      </nav>
      <main>
        <Slot />
      </main>
    </div>
  );
});

Data Loading (routeLoader$)

// src/routes/blog/[slug]/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';

export const usePost = routeLoader$(async ({ params, status }) => {
  const res = await fetch(`https://api.example.com/posts/${params.slug}`);

  if (!res.ok) {
    status(404);
    return null;
  }

  return res.json();
});

export default component$(() => {
  const post = usePost();

  if (!post.value) {
    return <p>Post not found</p>;
  }

  return (
    <article>
      <h1>{post.value.title}</h1>
      <div dangerouslySetInnerHTML={post.value.content} />
    </article>
  );
});

export const head: DocumentHead = ({ resolveValue }) => {
  const post = resolveValue(usePost);
  return {
    title: post?.title ?? 'Not Found',
  };
};

Server Actions (routeAction$)

// src/routes/contact/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeAction$, zod$, z, Form } from '@builder.io/qwik-city';

export const useContactForm = routeAction$(
  async (data, { fail }) => {
    const result = await sendEmail({
      to: 'contact@example.com',
      from: data.email,
      subject: data.subject,
      body: data.message,
    });

    if (!result.success) {
      return fail(500, { message: 'Failed to send message' });
    }

    return { success: true };
  },
  zod$({
    email: z.string().email(),
    subject: z.string().min(1),
    message: z.string().min(10),
  })
);

export default component$(() => {
  const action = useContactForm();

  return (
    <Form action={action}>
      <input name="email" type="email" placeholder="Your email" />
      {action.value?.fieldErrors?.email && (
        <span class="error">{action.value.fieldErrors.email}</span>
      )}

      <input name="subject" placeholder="Subject" />
      <textarea name="message" placeholder="Message" />

      <button type="submit" disabled={action.isRunning}>
        {action.isRunning ? 'Sending...' : 'Send'}
      </button>

      {action.value?.success && <p class="success">Message sent!</p>}
      {action.value?.message && <p class="error">{action.value.message}</p>}
    </Form>
  );
});

API Endpoints

// src/routes/api/users/index.ts
import type { RequestHandler } from '@builder.io/qwik-city';

export const onGet: RequestHandler = async ({ json }) => {
  const users = await db.users.findMany();
  json(200, users);
};

export const onPost: RequestHandler = async ({ request, json, status }) => {
  const body = await request.json();

  const user = await db.users.create({
    data: body,
  });

  status(201);
  json(201, user);
};

// src/routes/api/users/[id]/index.ts
export const onGet: RequestHandler = async ({ params, json, status }) => {
  const user = await db.users.findUnique({
    where: { id: params.id },
  });

  if (!user) {
    status(404);
    json(404, { error: 'User not found' });
    return;
  }

  json(200, user);
};

Middleware

// src/routes/admin/layout.tsx
import { component$, Slot } from '@builder.io/qwik';
import type { RequestHandler } from '@builder.io/qwik-city';

export const onRequest: RequestHandler = async ({ cookie, redirect }) => {
  const session = cookie.get('session');

  if (!session) {
    throw redirect(302, '/login');
  }
};

export default component$(() => {
  return (
    <div class="admin-layout">
      <Slot />
    </div>
  );
});

Styling

Scoped CSS

// src/routes/index.tsx
import { component$, useStylesScoped$ } from '@builder.io/qwik';

export default component$(() => {
  useStylesScoped$(`
    .hero {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      padding: 4rem;
      color: white;
    }
    .hero h1 {
      font-size: 3rem;
    }
  `);

  return (
    <section class="hero">
      <h1>Welcome</h1>
    </section>
  );
});

Global Styles

import { component$, useStyles$ } from '@builder.io/qwik';
import globalStyles from './global.css?inline';

export default component$(() => {
  useStyles$(globalStyles);

  return <div>...</div>;
});

Deployment

# Add deployment adapter
npm run qwik add

# Available adapters:
# - Cloudflare Pages
# - Netlify Edge
# - Vercel Edge
# - Node.js
# - AWS Lambda
# - Azure Functions
# - Deno
# - Bun
# - Static (SSG)

# Build
npm run build

# Preview
npm run preview

Performance Tips

  1. Use routeLoader$ for data that should be preloaded
  2. Avoid useVisibleTask$ unless absolutely necessary (client-only)
  3. Keep components small - optimizer works better with granular code
  4. Use signals over stores for simple state
  5. Leverage serialization - avoid closures that capture complex objects

Reference Files