jpskill.com
🛠️ 開発・MCP コミュニティ

qwik

Qwikは、サーバーでアプリケーションの状態を保存し、必要な時にJavaScriptを読み込むことで、高速なウェブサイト表示を実現し、エッジ環境への展開にも適したウェブフレームワークを提供するSkill。

📜 元の英語説明(参考)

Qwik is a resumable web framework that delivers instant-loading applications by eliminating hydration. It serializes application state on the server and lazily loads JavaScript on interaction, making it ideal for edge deployment.

🇯🇵 日本人クリエイター向け解説

一言でいうと

Qwikは、サーバーでアプリケーションの状態を保存し、必要な時にJavaScriptを読み込むことで、高速なウェブサイト表示を実現し、エッジ環境への展開にも適したウェブフレームワークを提供するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o qwik.zip https://jpskill.com/download/15310.zip && unzip -o qwik.zip && rm qwik.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/15310.zip -OutFile "$d\qwik.zip"; Expand-Archive "$d\qwik.zip" -DestinationPath $d -Force; ri "$d\qwik.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して qwik.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → qwik フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

Qwik

Qwik は、アプリケーションの状態を HTML にシリアライズすることで、ハイドレーションを排除します。JavaScript はページロード時ではなく、ユーザーインタラクション時に遅延ロードされます。これは、アプリケーションの複雑さに関わらず、初期ロード時の JS がほぼゼロになることを意味します。

インストール

# Qwik City (メタフレームワーク) を使用して Qwik プロジェクトを作成
npm create qwik@latest
cd my-app
npm install
npm run dev

プロジェクト構成

# Qwik City プロジェクトのレイアウト
src/
├── entry.ssr.tsx         # SSR エントリー
├── root.tsx              # ルートコンポーネント
├── global.css
├── routes/               # ファイルベースルーティング
│   ├── layout.tsx        # ルートレイアウト
│   ├── index.tsx         # / ページ
│   └── articles/
│       ├── index.tsx     # /articles
│       └── [slug]/
│           └── index.tsx # /articles/:slug
├── components/           # 再利用可能なコンポーネント
│   └── article-card/
│       └── article-card.tsx
└── lib/                  # ユーティリティ

コンポーネント

// src/components/article-card/article-card.tsx — Qwik コンポーネント
import { component$ } from '@builder.io/qwik';
import { Link } from '@builder.io/qwik-city';

interface Props {
  title: string;
  slug: string;
  excerpt: string;
}

export const ArticleCard = component$<Props>((props) => {
  return (
    <article>
      <Link href={`/articles/${props.slug}`}>
        <h2>{props.title}</h2>
      </Link>
      <p>{props.excerpt}</p>
    </article>
  );
});

シグナルと状態

// src/routes/counter/index.tsx — シグナルとリアクティビティ
import { component$, useSignal, useComputed$, useTask$ } from '@builder.io/qwik';

export default component$(() => {
  const count = useSignal(0);
  const doubled = useComputed$(() => count.value * 2);

  useTask$(({ track }) => {
    track(() => count.value);
    console.log(`Count changed to ${count.value}`);
  });

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

routeLoader$ を使用したデータローディング

// src/routes/articles/index.tsx — サーバーサイドデータローディング
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import { ArticleCard } from '~/components/article-card/article-card';

export const useArticles = routeLoader$(async ({ env }) => {
  const res = await fetch(`${env.get('API_URL')}/articles`);
  return res.json() as Promise<Article[]>;
});

export default component$(() => {
  const articles = useArticles();

  return (
    <div>
      <h1>Articles</h1>
      {articles.value.map((article) => (
        <ArticleCard key={article.id} title={article.title} slug={article.slug} excerpt={article.excerpt} />
      ))}
    </div>
  );
});

サーバーアクション

// src/routes/articles/new/index.tsx — サーバーアクションを持つフォーム
import { component$ } from '@builder.io/qwik';
import { routeAction$, Form, zod$, z } from '@builder.io/qwik-city';

export const useCreateArticle = routeAction$(
  async (data, { redirect, env }) => {
    const res = await fetch(`${env.get('API_URL')}/articles`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });
    if (!res.ok) return { success: false, error: 'Failed to create' };
    throw redirect(302, '/articles');
  },
  zod$({
    title: z.string().min(1).max(200),
    body: z.string().min(1),
  })
);

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

  return (
    <Form action={action}>
      <input name="title" placeholder="Title" required />
      <textarea name="body" placeholder="Body" required />
      <button type="submit">Create</button>
      {action.value?.error && <p>{action.value.error}</p>}
    </Form>
  );
});

レイアウト

// src/routes/layout.tsx — ルートレイアウト
import { component$, Slot } from '@builder.io/qwik';
import { Link } from '@builder.io/qwik-city';

export default component$(() => {
  return (
    <div>
      <header>
        <nav>
          <Link href="/">Home</Link>
          <Link href="/articles">Articles</Link>
        </nav>
      </header>
      <main>
        <Slot />
      </main>
    </div>
  );
});

ミドルウェア

// src/routes/admin/layout.tsx — onRequest を介した認証ミドルウェア
import { type RequestHandler } from '@builder.io/qwik-city';

export const onRequest: RequestHandler = async ({ cookie, redirect }) => {
  const token = cookie.get('session')?.value;
  if (!token) throw redirect(302, '/login');
};

複雑な状態のための useStore

// src/routes/dashboard/index.tsx — ネストされたリアクティブな状態のためのストア
import { component$, useStore, $ } from '@builder.io/qwik';

export default component$(() => {
  const state = useStore({
    articles: [] as Article[],
    filter: '',
    loading: false,
  });

  const fetchArticles = $(async () => {
    state.loading = true;
    const res = await fetch('/api/articles');
    state.articles = await res.json();
    state.loading = false;
  });

  return (
    <div>
      <button onClick$={fetchArticles}>Load</button>
      <input bind:value={state.filter} placeholder="Filter..." />
      {state.loading ? <p>Loading...</p> : (
        state.articles
          .filter((a) => a.title.includes(state.filter))
          .map((a) => <div key={a.id}>{a.title}</div>)
      )}
    </div>
  );
});

デプロイメント

# デプロイメントアダプターを追加
npm run qwik add cloudflare-pages  # または: vercel, netlify, node-server, deno
npm run build
npm run deploy

主要なパターン

  • 関数に $ サフィックスを使用する (onClick$, component$, routeLoader$) — これはシリアライゼーションの境界を示します
  • プリミティブには useSignal、オブジェクトには useStore を使用する — Solid の signal/store の分割に似ています
  • routeLoader$ は SSR 中にサーバー上で実行される — データは HTML にシリアライズされます
  • routeAction$ は Zod バリデーションを使用してサーバーサイドでフォームの送信を処理します
  • JavaScript はインタラクションごとに遅延ロードされる — ハイドレーションステップはありません
  • サーバーサイドミドルウェア (認証、リダイレクト) にはレイアウトで onRequest ハンドラーを使用する
  • デプロイ

(原文はここで切り詰められています)

📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Qwik

Qwik eliminates hydration by serializing the application state into HTML. JavaScript loads lazily on user interaction, not on page load. This means near-zero JS on initial load regardless of app complexity.

Installation

# Create Qwik project with Qwik City (meta-framework)
npm create qwik@latest
cd my-app
npm install
npm run dev

Project Structure

# Qwik City project layout
src/
├── entry.ssr.tsx         # SSR entry
├── root.tsx              # Root component
├── global.css
├── routes/               # File-based routing
│   ├── layout.tsx        # Root layout
│   ├── index.tsx         # / page
│   └── articles/
│       ├── index.tsx     # /articles
│       └── [slug]/
│           └── index.tsx # /articles/:slug
├── components/           # Reusable components
│   └── article-card/
│       └── article-card.tsx
└── lib/                  # Utilities

Components

// src/components/article-card/article-card.tsx — Qwik component
import { component$ } from '@builder.io/qwik';
import { Link } from '@builder.io/qwik-city';

interface Props {
  title: string;
  slug: string;
  excerpt: string;
}

export const ArticleCard = component$<Props>((props) => {
  return (
    <article>
      <Link href={`/articles/${props.slug}`}>
        <h2>{props.title}</h2>
      </Link>
      <p>{props.excerpt}</p>
    </article>
  );
});

Signals and State

// src/routes/counter/index.tsx — signals and reactivity
import { component$, useSignal, useComputed$, useTask$ } from '@builder.io/qwik';

export default component$(() => {
  const count = useSignal(0);
  const doubled = useComputed$(() => count.value * 2);

  useTask$(({ track }) => {
    track(() => count.value);
    console.log(`Count changed to ${count.value}`);
  });

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

Data Loading with routeLoader$

// src/routes/articles/index.tsx — server-side data loading
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import { ArticleCard } from '~/components/article-card/article-card';

export const useArticles = routeLoader$(async ({ env }) => {
  const res = await fetch(`${env.get('API_URL')}/articles`);
  return res.json() as Promise<Article[]>;
});

export default component$(() => {
  const articles = useArticles();

  return (
    <div>
      <h1>Articles</h1>
      {articles.value.map((article) => (
        <ArticleCard key={article.id} title={article.title} slug={article.slug} excerpt={article.excerpt} />
      ))}
    </div>
  );
});

Server Actions

// src/routes/articles/new/index.tsx — form with server action
import { component$ } from '@builder.io/qwik';
import { routeAction$, Form, zod$, z } from '@builder.io/qwik-city';

export const useCreateArticle = routeAction$(
  async (data, { redirect, env }) => {
    const res = await fetch(`${env.get('API_URL')}/articles`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });
    if (!res.ok) return { success: false, error: 'Failed to create' };
    throw redirect(302, '/articles');
  },
  zod$({
    title: z.string().min(1).max(200),
    body: z.string().min(1),
  })
);

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

  return (
    <Form action={action}>
      <input name="title" placeholder="Title" required />
      <textarea name="body" placeholder="Body" required />
      <button type="submit">Create</button>
      {action.value?.error && <p>{action.value.error}</p>}
    </Form>
  );
});

Layouts

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

export default component$(() => {
  return (
    <div>
      <header>
        <nav>
          <Link href="/">Home</Link>
          <Link href="/articles">Articles</Link>
        </nav>
      </header>
      <main>
        <Slot />
      </main>
    </div>
  );
});

Middleware

// src/routes/admin/layout.tsx — auth middleware via onRequest
import { type RequestHandler } from '@builder.io/qwik-city';

export const onRequest: RequestHandler = async ({ cookie, redirect }) => {
  const token = cookie.get('session')?.value;
  if (!token) throw redirect(302, '/login');
};

useStore for Complex State

// src/routes/dashboard/index.tsx — store for nested reactive state
import { component$, useStore, $ } from '@builder.io/qwik';

export default component$(() => {
  const state = useStore({
    articles: [] as Article[],
    filter: '',
    loading: false,
  });

  const fetchArticles = $(async () => {
    state.loading = true;
    const res = await fetch('/api/articles');
    state.articles = await res.json();
    state.loading = false;
  });

  return (
    <div>
      <button onClick$={fetchArticles}>Load</button>
      <input bind:value={state.filter} placeholder="Filter..." />
      {state.loading ? <p>Loading...</p> : (
        state.articles
          .filter((a) => a.title.includes(state.filter))
          .map((a) => <div key={a.id}>{a.title}</div>)
      )}
    </div>
  );
});

Deployment

# Add deployment adapter
npm run qwik add cloudflare-pages  # or: vercel, netlify, node-server, deno
npm run build
npm run deploy

Key Patterns

  • Use $ suffix on functions (onClick$, component$, routeLoader$) — it marks serialization boundaries
  • Use useSignal for primitives, useStore for objects — similar to Solid's signal/store split
  • routeLoader$ runs on the server during SSR — data is serialized into HTML
  • routeAction$ handles form submissions server-side with Zod validation
  • JavaScript loads lazily per interaction — no hydration step
  • Use onRequest handlers in layouts for server-side middleware (auth, redirects)
  • Deploy anywhere: Cloudflare, Vercel, Netlify, Deno, Node — via adapters