フレームワーク

Qwikとは?使い方とベストプラクティスをv1.20.1とv2 betaの実ビルドで解説

Qwikとは?使い方とベストプラクティスをv1.20.1とv2 betaの実ビルドで解説

Qwik(クイック)は、サーバーで描画したHTMLに「どのイベントでどのコードを読み込むか」を書き込み、ブラウザ側でハイドレーションをせずに処理を再開するJavaScriptフレームワークです。2026年9月時点では安定版のv1系(1.20.1)と、パッケージ名から作り直したv2系(2.0.0-beta.45)が並走しています。この記事では両方の版でプロジェクトを実際にビルドし、出力されたHTMLとJavaScriptの中身から、仕組み・使い方・公式ベストプラクティスを確認します。

まとめ:Qwikの要点(2026年9月時点)

  • Qwikの中核はresumability(再開可能性)です。リスナーの位置・コンポーネント境界・状態をSSR時にHTMLへシリアライズし、クライアントでは再実行しません。
  • 本番運用の既定はv1.20.1(@builder.io/qwik、2026-09-23公開)です。npm create qwik@latestもv1のプロジェクトを作ります。
  • v2は@qwik.dev/coreと@qwik.dev/routerに改名済みで、2025-06からbetaのままです。ただし@qwik.dev/coreのlatestタグはbeta.45を指すため、パッケージ名で直接入れるとbetaが入ります。
  • 「初期JavaScriptゼロ」は正確ではありません。実測では、v1の最小ページでも3,100バイトのqwikloaderを読み込み、コアの50,423バイトをmodulepreloadで先読みしていました。遅らせているのはダウンロードではなく実行です。
  • 公式のベストプラクティスは「シグナルの読み取りをテンプレートかuseComputed$に寄せる」「useVisibleTask$は最後の手段」「window.locationでなくuseLocation()を使う」が中心です。
  • 独自クラスのインスタンスは、そのままではシリアライズできません。実測でもSSGビルドがCode(3)(Only primitive and object literals can be serialized)で失敗しました。保持が必要な場合はnoSerialize()で対象から外します。

以下、仕組み、実測値、版の選び方、使い方、ベストプラクティス、採用判断の順に解説します。

Qwikの仕組み:ハイドレーションを使わないresumability

QwikはBuilder.ioのGitHub組織(BuilderIO/qwik、GitHub APIの作成日は2021-05-19)で開発が始まりました。リポジトリは現在QwikDev/qwikに移っており、旧URLからは転送されます。Builder.ioはREADMEで「special sponsor」として表記されています(MITライセンス、GitHub APIで2026-09-27に取得したスター数は22,068)。コンポーネントはJSXとTypeScriptで書くため、見た目はReactに近くなります。ただしページを起動する方法が根本的に異なります。

ハイドレーションが復元する3つの情報

公式ドキュメントの「Resumable vs. Hydration」は、SSRやSSGのアプリがブラウザで起動する際に、フレームワークが次の3つを復元する必要があると説明しています。1つ目はイベントリスナー(どのDOMにどのハンドラーを付けるか)、2つ目はコンポーネントツリー、3つ目はアプリの状態です。ReactやVueはこれをハイドレーションで行います。ハイドレーション対象のコンポーネントでは、対応するコードをダウンロードして実行します。ただし、React Server Componentsのコードはブラウザへ配信されないため、ページ内の全コンポーネントが対象になるわけではありません。レンダリング方式全体の違いはCSR/SSR/SSGの違いと選定基準で整理しています。

Qwikはこの3つをSSR/SSGの時点で集め、HTMLへ書き出します。ブラウザ側では再実行(リプレイ)せず、サーバーで止めた場所から処理を再開します。ハイドレーション自体が無いので、サーバーとクライアントで描画結果が食い違って起きるハイドレーションエラーとも仕組み上は無縁です。

$記号・QRL・qwikloaderの役割

Qwikでcomponent$やonClick$のように末尾へ$が付く関数は、Optimizer(ビルド時の変換器)に「ここで別チャンクへ切り出してよい」と伝える目印です。切り出された関数は、チャンクのファイル名とシンボル名を組み合わせたQRL(Qwik URL)で参照されます。HTMLに残るのはコード本体ではなくこのQRLです。

ページ起動時にはqwikloaderが読み込まれ、必要なイベント種別に対応するグローバルリスナーを登録します。ユーザーがボタンを押すと、qwikloaderがイベントの発生元から属性をたどってQRLを読み、該当チャンクだけをダウンロードして実行します。遅延ロードを開発者が設計しなくても、これが既定の動作になります。

実測:最小構成のSSG出力に残るJavaScript

「Qwikは初期JavaScriptがゼロ」という説明をよく見かけますが、実際のビルド結果では少し異なります。create-qwikのemptyテンプレートに、カウンター1つとルートローダー1つを置いたページをv1.20.1とv2.0.0-beta.45でSSGビルドし、出力を比べました(2026-09-27、Node.js v26.5.0)。

項目 v1.20.1 v2.0.0-beta.45
ボタンの属性 on:click q-e:click
コンテナの状態 q:container="paused" q:container="paused"
状態のscript qwik/json 195文字 qwik/state 2,884文字
index.html 2,640バイト 5,461バイト
qwikloader 3,100バイト(gzip 1,678) 5,222バイト(gzip 2,386)
先読みされる最大チャンク 50,423バイト(gzip 20,345) 107,112バイト(gzip 38,334)
同梱Vite 7.3.1 8.2.1

バイト数はdist/build/に出力された配信ファイルの実サイズで、gzipはgzip -9 -nで圧縮した値です。v1のボタンは<button on:click="q-JOju1guk.js#s_fUGjiPat2oI[0]">として出力されていました。このチャンクは921バイト(gzip 472)ですが、先頭でコアのチャンクをimportしているため、クリック時の処理にはコアも必要です。HTMLの<head>にはコアへのmodulepreloadが入っており、loadイベント後にはrequestIdleCallbackでハンドラーのチャンクも先読みしていました。このビルドでは、ダウンロードはページ表示の直後に済ませ、実行だけをクリックまで遅らせています。回線が細い環境で転送量を見積もるときは、この先読みも含めて計算してください。

qwikloaderの公式ページは、サイズを「about 1 kb minified」とし、別の接続を張らずに済むよう<script>へのインライン配信を推奨しています。一方、v1.20.1の既定ビルドでは3,100バイトの外部ファイルとして<script type="module" async src="/build/…">で読み込まれていました。ドキュメントの数値をそのまま性能予算に使わず、自分のビルド結果で確認してください。

v2 betaでは、公式の移行ガイドが示すとおり属性名がq-e:系に変わり、状態はqwik/stateとqwik/vnodeの2つのscriptに分かれていました。移行ガイドは「シリアライズされた状態が最大30%小さくなる」と説明していますが、この最小ページではv2の方がHTMLが大きくなっています。削減効果はページ構成によって変わるため、移行前に自分のアプリで測ってから判断するのが確実です。

v1.20.1とv2 betaの現状と選び方

Qwikの版を確認するときは、パッケージ名が2系統ある点に注意が必要です。npmレジストリのdist-tagsを2026-09-27に取得した結果は次のとおりです。

パッケージ latestタグ 公開日
@builder.io/qwik 1.20.1 2026-09-23
@builder.io/qwik-city 1.20.1 2026-09-23
@qwik.dev/core 2.0.0-beta.45 2026-09-22
@qwik.dev/router 2.0.0-beta.45 2026-09-22
create-qwik 1.20.1(betaタグは2.0.0-beta.45) 2026-09-23

v2は2024-11-15に2.0.0-alpha.0、2025-06-04に2.0.0-beta.1が公開され、それから1年以上betaが続いています。一方、v1は2026年に入ってからも1.19.0(1月)、1.20.0(5月)、1.20.1(9月)と更新されています。

2026年9月時点の判断基準は明確です。本番で使うならv1.20.1で始めてください。betaは破壊的変更が入り得るうえ、サードパーティのライブラリが@builder.io/qwikを前提にしている場合があります。移行ガイドでも、package managerのoverridesで旧パッケージ名をnpm:@qwik.dev/core@^2へ差し替える手順を案内しています。v2を試すのは、非同期のuseComputed$やVite 8対応を検証したい新規の実験プロジェクトに限るのが無難です。

v1からv2への主な変更点

公式の移行ガイド(Migrating to Qwik v2)は、v2を「フレームワークの全面的な書き直し」と位置付け、npx qwik migrate-v2による自動移行を案内しています。コードに影響する主な変更は次のとおりです。

  • インポート先の変更:@builder.io/qwik→@qwik.dev/core、@builder.io/qwik-city→@qwik.dev/router(Qwik CityはQwik Routerに改称)
  • ルートの<QwikCityProvider>をuseQwikRouter()フックに置き換え
  • useResource$は非推奨になり、abortSignalを受け取れる非同期のuseComputed$に置き換え
  • useVisibleTask$のeagernessオプションを削除
  • 静的出力のアダプタ名がstaticからssgに変更(実測で確認。v2でqwik add staticを実行すると「Invalid integration」エラーになる)

HTML属性名の変更(on:click→q-e:click)は、JSXのonClick$を書くだけなら影響しません。ただし、E2Eテストやスクレイピングで属性を直接セレクタに使っている場合は壊れます。

Qwikの使い方:プロジェクト作成からデプロイまで

ここからはv1.20.1を前提に、Qwik City(v2ではQwik Router)でページを作る手順を追います。コードはすべてローカルでビルドし、型チェックとLintが通ることを確認済みです。

プロジェクト作成とNode.jsの要件

公式のGetting StartedにはNode.js v18.17以上とありますが、テンプレートが入れるVite 7.3.1(v2 betaではVite 8.2.1)のenginesはどちらも^20.19.0 || >=22.12.0で、実際にはこちらが下限になります。生成されるpackage.jsonのenginesは^18.17.0 || ^20.3.0 || >=21.0.0でした(依存しているsharpのNode-API要件によるもの)。作成コマンドは次のとおりです。

npm create qwik@latest
# 対話なしで作る場合(テンプレートと出力先を指定し、-iで依存も入れる)
npx [email protected] empty my-app -i
cd my-app
npm start

テンプレートはempty・playground・library・e2e-libraryの4種類です。v2を試す場合はnpm create qwik@betaのようにbetaタグを明示します。生成物にはvite.config.tsとsrc/routes/、src/entry.ssr.tsxが含まれます。npm run devで起動する開発サーバーはvite --mode ssrで、開発時もSSRで動作します。

ファイルベースルーティングと状態管理

src/routes/配下のディレクトリがURLに対応し、各ディレクトリのindex.tsxがページになります。src/routes/about/index.tsxは/about/、[id]ディレクトリは動的パラメータです。次のコードは、サーバーで値を取得して表示し、クリックでカウンターを増やすページです。

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

export const useServerTime = routeLoader$(() => {
  return { iso: new Date().toISOString() };
});

export default component$(() => {
  const count = useSignal(0);
  const doubled = useComputed$(() => count.value * 2);
  const time = useServerTime();
  return (
    <main>
      <p>SSR time: {time.value.iso}</p>
      <button onClick$={() => count.value++}>+1</button>
      <p>count: {count.value} / doubled: {doubled.value}</p>
    </main>
  );
});

useSignalは単一の値を.valueで読み書きし、useStoreはオブジェクト全体をリアクティブにします。派生値はuseComputed$で作ります。シグナルの変更はそれを読んでいるDOMだけを更新するため、Reactのようにコンポーネント関数全体を再実行する必要がありません。Signalベースの設計はSolidJSのSignalと発想が近いですが、Qwikでは状態がHTMLへシリアライズされる点が異なります。

サーバー処理:routeLoader$・routeAction$・server$

データの取得にはrouteLoader$、フォーム送信などの更新にはrouteAction$を使います。どちらもページのindex.tsx(またはlayout.tsx)からexportする必要があります。次の例では、zod$で入力を検証します。

import { component$ } from '@builder.io/qwik';
import { routeLoader$, routeAction$, zod$, z, Form } from '@builder.io/qwik-city';

const todos: string[] = [];

export const useTodos = routeLoader$(() => todos);

export const useAddTodo = routeAction$(
  (data) => {
    todos.push(data.title);
    return { ok: true };
  },
  zod$({ title: z.string().min(1) })
);

export default component$(() => {
  const list = useTodos();
  const add = useAddTodo();
  return (
    <>
      <Form action={add}>
        <input name="title" />
        <button type="submit">追加</button>
      </Form>
      {add.value?.failed && <p>タイトルは必須です</p>}
      <ul>
        {list.value.map((t, index) => (
          <li key={index}>{t}</li>
        ))}
      </ul>
    </>
  );
});

ビルド後のHTMLでは、<Form>が<form action="?qaction=…" method="post">として出力されていました。JavaScriptが読み込まれる前でも、通常のPOSTとして送信できます。このモジュール変数todosは、同じサーバープロセス内の利用者で共有され、再起動すると失われるサンプル用データです。実運用では、利用者ごとのアクセス制御を備えたDBなどに置き換えてください。コンポーネント内から任意のサーバー関数を呼びたい場合は、server$で包んだ関数がRPCとして呼び出されます。

デプロイ:qwik addで追加するアダプタ

作成直後のプロジェクトはアダプタが未設定で、npm run deployを実行しても「npm run qwik addでサーバーアダプタを入れる」と表示されるだけです。v1.20.1に同梱されているアダプタは、aws-lambda・azure-swa・bun・cloud-run・cloudflare-pages・cloudflare-workers・deno・express・fastify・firebase・netlify-edge・node-server・static・vercel-edgeの14種類でした。

# 直前のフォーム例を公開する場合:サーバー処理に対応するアダプタ
npx qwik add cloudflare-pages
# サーバー処理が不要な静的サイトでは、上のコマンドの代わりに次を実行
# npx qwik add static
npm run build              # 型チェック・Lint・クライアントとサーバーのビルドをまとめて実行

staticアダプタではadapters/static/vite.config.tsが生成され、originの値を実際のドメインに書き換える必要があります。ビルドの最後には、配信時にキャッシュヘッダーを適切に設定するよう促す注意書きが出ます。公式のDeploymentsページによれば、dist/buildとdist/assetsのファイル名には内容のハッシュが付くため、Cache-Control: public, max-age=31536000, immutableで配信するよう推奨されています。

Qwikのベストプラクティス(公式ガイドの要点)

公式のBest Practicesページ(v1とv2で見出しは共通)は、主に「コアの実行とJavaScriptの先行ロードをどう減らすか」について書かれています。ここでは、コードを書くときに判断が必要な4点を取り上げます。

シグナルの読み取り位置と再実行範囲

Qwikは、シグナルを読んだ関数を、そのシグナルが変わるたびに再実行します。コンポーネント関数の本体でconst doubleCount = count.value * 2のように読むと、値が変わるたびにテンプレート全体が再描画されます。JSXの中で直接読むか、useComputed$(() => count.value * 2)に切り出せば、再実行されるのはその式だけになります。Reactの感覚で「レンダー関数の先頭で値を計算してから返す」書き方をすると、Qwikの細かな更新が効かなくなります。

useVisibleTask$の使用条件と代替API

useVisibleTask$はコンポーネントが画面に入った時点でクライアント側のコードを実行するため、コアの読み込みと実行を強制します。公式は、代わりに次のAPIを使うよう案内しています。サーバーで済む処理にはuseTask$、ルート要素のイベントにはuseOn、windowやdocumentのイベントにはuseOnWindowやuseOnDocumentです。テンプレートに同梱されているeslint-plugin-qwikはqwik/no-use-visible-taskルールで警告を出します。実際にwindow.innerWidthを読むコードで警告が出ることを確認しました。どうしても必要な場合だけ、// eslint-disable-next-line qwik/no-use-visible-taskを付けて明示的に使います。

useLocation()によるURL参照とSSGの制約

URLやクエリパラメータによる分岐をwindow.locationで書くと、ブラウザでしか評価できないため、JavaScriptの先行ロードが発生します。useLocation()を使えば、同じ分岐をSSRの時点でHTMLとして確定できます。例外はSSGです。ビルド時にはリクエストごとのクエリパラメータが存在しないため、クエリを使う処理はイベントハンドラーの中に移すよう公式は勧めています。

独自クラスのシリアライズ制約と除外方法

resumabilityは状態をHTMLへシリアライズできることが前提です。公式の説明によれば、Qwikは循環参照、Date、URL、Map、Set、DOM参照、QRLで包んだクロージャーをシリアライズできます。一方、独自クラスのインスタンスやStreamは、そのままではシリアライズできません。v1でもnoSerialize()で対象から除外して保持できますが、その値はSSRからブラウザへ引き継がれないため、クライアント側で再初期化する必要があります。useSignal(new Cart())のようにクラスのインスタンスを状態へ入れたページをv1.20.1でSSGビルドすると、QWIK ERROR Code(3)でビルド全体が失敗しました。v1のコアで定義されているCode(3)の文言は「Only primitive and object literals can be serialized」です。状態はプレーンなオブジェクトと配列で持ち、振る舞いは関数として分けてください。

React・Vue・Astroとの違いと採用判断

Qwikが有利になるのは、初期表示の速さが売上や検索流入に直結し、かつページ内の操作が多いサイトです。ECの商品一覧や、多数のウィジェットを持つランディングページがこれに当たります。コンテンツ中心でほとんど操作がないサイトであれば、必要な部分だけをハイドレートするAstroで十分です。Astroの位置付けはAstroとReactの違いも参考になります。Vueのエコシステムで同じ課題を解くならNuxt、React資産を活かしたフルスタック構成ならTanStack Startが比較対象になります。

観点 Qwik React系(Next.js等) Astro
起動方式 resumability ハイドレーション 部分ハイドレーション
更新単位 シグナル単位 コンポーネント再実行 島ごとのUIライブラリ次第
npm週間DL 69,344 — 6,543,626
既存UIライブラリ Qwik用が中心 React用を利用可 React・Vue等を流用可

週間ダウンロード数は2026-09-19から25の期間でnpmのAPIから取得しました(@builder.io/qwikとastroの比較)。v2の@qwik.dev/coreは同じ期間で4,302件でした。

採用すべきでない条件もはっきりしています。Reactのコンポーネントライブラリを大量に使う管理画面や、ブラウザでしか動かないSDK(地図・リッチエディタ・決済ウィジェットなど)が画面の大半を占めるアプリは、Qwikに向きません。Reactの部品を@builder.io/qwik-reactのqwikify$で包む方法もありますが、包んだ部分は通常のReactとしてハイドレーションされます。こうした部分はqwikify$やuseVisibleTask$で囲うことになり、resumabilityの利点が消えるうえ、状態のシリアライズ制約だけが残ります。加えて、2026年9月時点ではv1からv2への移行が控えています。長期保守を前提とする案件では、移行作業の工数も見積もりに含めてください。

よくある質問

Qwikとは何ですか?

Builder.ioで開発が始まったJavaScriptフレームワークです。SSRで生成したHTMLにイベントリスナーの位置と状態を書き込み、ブラウザではハイドレーションをせずに、操作されたときに必要なコードだけを読み込んで実行します。この方式をresumabilityと呼びます。

Qwikの使い方(始め方)は?

Node.js 20.19以上の20系、または22.12以上の環境でnpm create [email protected]を実行し、テンプレート(まずはempty)を選びます。src/routes/にディレクトリとindex.tsxを置くとページになり、状態はuseSignal、サーバーからのデータ取得はrouteLoader$で書きます。公開する前にnpx qwik addでデプロイ先のアダプタを追加します。

Qwikのベストプラクティスは何ですか?

公式ガイドの中心は、シグナルの読み取りをテンプレートかuseComputed$に寄せること、useVisibleTask$をuseTask$やuseOn系で置き換えること、window.locationではなくuseLocation()を使うことです。これに加えて、SSRから引き継ぐ状態はシリアライズ可能な値にし、独自クラスなどを保持する場合はnoSerialize()による除外とクライアント側での再初期化を検討します。

Qwikの最新バージョンはいくつですか?

2026年9月27日時点で、安定版は2026年9月23日に公開されたv1.20.1です(@builder.io/qwik)。v2は@qwik.dev/coreの2.0.0-beta.45が最新で、まだ正式版になっていません。

QwikはReactやVueと何が違いますか?

ReactやVueは、ハイドレーションによってブラウザ上でコンポーネントを再実行し、リスナーと状態を復元します。Qwikはこれらをサーバー側でHTMLへ書き出すため、再実行が不要です。書き方はJSXでReactに似ていますが、SSRから引き継ぐ状態はシリアライズ可能な値に限られ、Reactのコンポーネントライブラリはそのままでは使えません。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.27 テックブログ Brotli 脆弱性:CVE-2026-111497の影響確認とNode.js・Python・.NETの更新手順
  2. 2026.10.28 テックブログ リンツ不正アクセス:314万件の対象確認とパスワード強制リセット後の対処
  3. 2026.10.27 テックブログ Joshin(ジョーシン)不正アクセス:最大600万件の対象とサブスク初期パスワードの対処
  4. 2026.10.29 テックブログ So-net情報漏洩:258万件の対象確認とメールのパスワード再設定、窓口の番号
  5. 2026.10.23 テックブログ Renta!はいつ再開?不正アクセスでメールアドレス等が流出したおそれ・再開時のパスワード再設定と運営側の対応

RELATED POSTS 関連記事

目次