Qwik CityとmicroCMSでつくるブログアプリケーション
今回は、まだv0.19.2(2023年2月現在)ではありますが、従来のフレームワークとは異なったあたらしい思想のQwikとmicroCMSを利用したブログ作成の第一歩を紹介します。 また、Qwik CityのStatic Site Adapterを利用してSSG(静的に生成された)サイトをCloudflare Pagesにデプロイするまでゴールとしています。

はじめに
こんにちは、森茂です。
今回は、まだv0.19.2(2023年2月現在)ではありますが、従来のフレームワークとは異なったあたらしい思想のQwikとmicroCMSを利用したブログ作成の第一歩を紹介します。
また今回は、Qwik CityのStatic Site Adapterを利用してSSG(静的に生成された)サイトをCloudflare Pagesにデプロイするまでゴールとしています。
環境について
※ Qwikは2023年2月現在の最新版。今後のバージョンアップによっては実装が変更になる可能性がある点あらかじめご了承ください。
Qwikとは
QwikはBuilder.io社が作成したWebアプリケーションを構築するためのWebフレームワークです。昨今のWebフレームワーク同様、パフォーマンスを重要視していますがそのアプローチは他のフレームワークと異なり、とてもユニークなものとなっています。QwikはDelay Executionという考えのもとJavaScriptの実行をできるだけ遅らせることを前提としており、最初にはHTMLと約1kb程度のJSを配信し、必要なときだけJSを段階的にロードすることで、高いパフォーマンスを実現しています。
Delay Execution、Resumability & Serializationという考えについては下記公式のドキュメントも参照ください。
Qwik Cityとは
Qwikがコアフレームワークとして位置づけられているの対して、Qwik Cityはメタフレームワークとして位置づけられています。公式ドキュメントにも記載されている通り、Qwik CityはQwikにとって、ReactにとってのNext.js、VueにとってのNuxt、SvelteにとってのSvelteKitと考えるのがよさそうです。
Qwik CityではNext.jsと同様にファイルシステムベースのルーティングやネスト化できるレイアウト、リソースルートなど多くの機能が利用できます。
Qwik Cityのセットアップ
まずはQwik Cityのセットアップを行います。CLI実行の途中starterテンプレートの選択がありますが今回はBasic App(QwikCity)を選択します。
npm create qwik@latest
セットアップができたところで一度開発サーバーを起動してみましょう。
cd qwik-app
npm run dev
VITE v4.1.1 ready in 570 ms
➜ Local: http://127.0.0.1:5173/
➜ Network: use --host to expose
➜ press h to show help
問題なく起動できたらhttp://localhost:5173/にテストページを確認しておきましょう。
microCMSの準備
microCMSでAPIを用意します。今回はmicroCMSのブログテンプレートを利用して進めていきます。
アカウント登録がまだの方はこちらのドキュメントを参考に、登録を行ってください。
また、APIへの接続を簡単に行えるようmicrocms-js-sdkをインストールします。
npm install microcms-js-sdk
APIキーとサービスドメイン
あわせてAPIのエンドポイントと、APIキーも用意しておきましょう。ブログ一覧画面のAPIプレビューから確認するのがレスポンスの中身も確認できるので手軽で便利です。
取得したAPIキーとAPIのサービスドメイン名は環境変数ファイル.env
に記載しておきます。QwikはVite環境で実行されるためVite環境で利用する目には変数名にVITE_
を付与する必要があります。
VITE_MICROCMS_SERVICE_DOMAIN=サービスドメイン名
VITE_MICROCMS_API_KEY=APIキ
コンテンツ取得用クライアントの作成
microCMSの準備ができたところでQwik CityからmicroCMSのコンテンツを取得するためのクライアントを作成します。
src/libs/microcms.ts
ファイルとして型定義、クライアント、ブログ一覧の取得、ブログ詳細の取得を用意しておきます。
microcms-js-sdkにはMicroCMSQueries
、MicroCMSImage
やMicroCMSDate
などAPIで利用する定形の型があらかじめ用意されています。こちらもぜひご活用ください。
// src/libs/microcms.ts
import { createClient } from "microcms-js-sdk";
import type {
MicroCMSQueries,
MicroCMSImage,
MicroCMSDate,
} from "microcms-js-sdk";
//ブログの型定義
export type Blog = {
id: string;
title: string;
content: string;
eyecatch?: MicroCMSImage;
} & MicroCMSDate;
if (!import.meta.env.VITE_MICROCMS_SERVICE_DOMAIN) {
throw new Error("MICROCMS_SERVICE_DOMAIN is required");
}
if (!import.meta.env.VITE_MICROCMS_API_KEY) {
throw new Error("MICROCMS_API_KEY is required");
}
// Initialize Client SDK.
export const client = createClient({
serviceDomain: import.meta.env.VITE_MICROCMS_SERVICE_DOMAIN,
apiKey: import.meta.env.VITE_MICROCMS_API_KEY,
});
// ブログ一覧を取得
export const getList = async (queries?: MicroCMSQueries) => {
const listData = await client.getList<Blog>({
endpoint: "blogs",
queries,
});
return listData;
};
// ブログの詳細を取得
export const getDetail = async (
contentId: string,
queries?: MicroCMSQueries
) => {
const detailData = await client.getListDetail<Blog>({
endpoint: "blogs",
contentId,
queries,
});
return detailData;
};
記事一覧ページの作成
準備ができたところで早速記事一覧のページを作成しましょう。
Qwik CityはNext.jsなどと同様にファイルシステムベースのルーティングになっており、src/routes
ディレクトリが対象となります。まずは記事一覧ページ/blog/
を用意するために、新規にsrc/routes/blog/index.tsx
を用意します。
データの取得
Qwik CityではData Loaderと呼ばれる機能があり、サーバーサイドでのデータ処理を追加できます。Next.jsのgetServerProps()、Remixのloadersと同じようなイメージです。また1つのコンポーネントに対して複数のData Loaderを利用できる点など特殊な部分ももっています。(loader$はv0.17.0以降で利用可能です)
また、Data Loaderの関数loader$()
ではリアクティブな状態を管理するためにSignalの概念を利用しています。
記事一覧ページ
記事一覧を表示するページコンポーネントを作成します。
// src/routes/blog/index.tsx
import { component$ } from "@builder.io/qwik";
import { routeLoader$ } from "@builder.io/qwik-city";
import { getList } from "~/libs/microcms";
import { useServerTimeLoader } from "~/routes/layout";
// microCMSから記事一覧を取得する
export const useListLoader = routeLoader$(async () => {
const { contents } = await getList();
return contents;
});
export default component$(() => {
// 外部のLoaderも利用できる
const serverTime = useServerTimeLoader();
const list = useListLoader();
return (
<div>
<h1>Server time: {serverTime.value.date}</h1>
<ul>
{list.value.map((item) => (
<li>
<a href={`/blog/${item.id}`}>{item.title}</a>
</li>
))}
</ul>
</div>
);
});
http://localhost:5173/blog/にアクセスしてみるとlayoutコンポーネントの中に記事一覧ページが配置されます。
記事詳細ページの作成
次に記事詳細ページを用意していきます。新規にsrc/routes/blog/[postId]/index.tsx
を作成します。Next.jsと同じように[postId]
の部分がparams
としてページコンポーネントへ渡され利用できます。
またDocumentHead
の中でheadタグを動的に扱うことができ、その中でもData Loaderが利用できます。
// src/routes/blog/[postId]/index.tsx
import { component$ } from "@builder.io/qwik";
import type { DocumentHead } from '@builder.io/qwik-city';
import { routeLoader$ } from "@builder.io/qwik-city";
import { getDetail, getList } from "~/libs/microcms";
import { useServerTimeLoader } from "~/routes/layout";
// microCMSから記事詳細を取得する
export const usePostLoader = routeLoader$(async ({ params, status }) => {
try {
const post = await getDetail(params.postId);
return post;
} catch {
// 記事がない場合はStatus Code 404を返す
status(404);
}
});
export default component$(() => {
const serverTime = useServerTimeLoader();
const post = usePostLoader();
if (!post.value) {
return <h1>Not Found.</h1>;
}
return (
<div>
<h1>Server time: {serverTime.value.date}</h1>
<h2>{post.value.title}</h2>
<div dangerouslySetInnerHTML={post.value.content}></div>
</div>
);
});
// 動的にheadを書き換える
export const head: DocumentHead = ({ resolveValue }) => {
const post = resolveValue(usePostLoader);
return {
title: post?.title || "Welcome to Qwik",
meta: [
{
name: "description",
content: post?.title || "Qwik site description",
},
],
};
};
SSGの設定
開発サーバー上ではSSRで動作しているためページ内に用意しているServer timeは常に生成された時間に変化します。今回はCloudflare Pagesに静的ページとしてデプロイしたいため、ビルド時に静的なページを生成するSSGの設定を追加します。(Cloudflare Pages Adapterを利用することでSSRを利用したサイトをデプロイすることも可能です。)
Qwikにはいろいろなサービスへ接続するためのアダプターやスタイリング、テストツールなどを統合できるインテグレーション機能が組み込まれています。
Static Site Adapterのインストール
Static Site Adapterを利用することでSSGを実現できます。今回はnpm run qwik add
を実行してAdapter: Static site (.html files)を選択します。
npm run qwik add
🦋 Add Integration
? What integration would you like to add? › (use ↓↑ arrows, hit enter)
↑ Adapter: Azure Static Web Apps
Adapter: Netlify Edge
Adapter: Vercel Edge
Adapter: Google Cloud Run server
Adapter: Nodejs Express Server
❯ Adapter: Static site (.html files)
Framework: React
Integration: Auth.js (authentication)
Integration: Playwright (E2E Test)
↓ Integration: Styled-Vanilla-Extract (styling)
SSG用のルート設定
SSGのルートを動的に生成するために[postId]/index.tsx
へ下記を追記します。
// src/routes/blog/[postId]/index.tsx
// ...
import type {
DocumentHead,
StaticGenerateHandler, // 追加
} from "@builder.io/qwik-city";
// ...
// SSGのために動的パスを返す
export const onStaticGenerate: StaticGenerateHandler = async () => {
const { contents } = await getList();
const paths = contents.map((post) => {
return post.id;
});
return {
params: paths.map((postId) => {
return { postId };
}),
};
};
Cloudflare Pagesへのデプロイ
ここまででブログの一覧画面と詳細画面を作ることができました。
あとは作りたいブログのデザインに合わせてスタイルを調整してみたり、microCMSのAPIスキーマをいじって項目を増やしてみたりしてみてください。
最後の仕上げとして作成したものをデプロイします。
今回はホスティングサービスとしてCloudflare Pagesを利用します。
まずはここまでの作業内容をGitHubに上げます。
GitHubのリポジトリを作成後、下記コマンドでプッシュしていきます。
git init
git add .
git commit -m 'initial commit'
git remote add origin your-repository // 自分のリポジトリを入力
git push -u origin main
そしてCloudflare Pagesにログイン後、「プロジェクトを作成 > Gitに接続」を選択して先ほど作成したリポジトリと連携します。
ビルド設定ではフレームワークとしてQwikを選択し、環境変数のセットを行います。(.env
の内容)
Webhookの設定
現状ではGitHubにソースコードをPushするたびにビルド&デプロイがされますが、microCMSの記事を追加・更新してもビルドはされません。
そこで、microCMSのWebhook機能を使って、記事の追加・更新のたびにCloudflare Pagesでビルド&デプロイがされるように設定しましょう。
設定方法は下記の記事で紹介しているので参考にしてみてください。
https://blog.microcms.io/webhook-cloudflare-vercel/
設定後に新しく記事を公開してみて、Cloudflare Pagesのビルドが動き出したら成功です。
おわりに
Qwik CityとmicroCMSを利用したブログアプリケーションの第一歩について紹介させていただきました。Qwikはまだ発展途上のためプロダクション環境での利用は難しいかもしれませんが、ぜひ素振りがてらmicroCMSとの連携を試してみてください。

柴田 和祈
株式会社microCMS 共同創業者 / デザイナー兼フロントエンドエンジニア / ex Yahoo / 2児の父 / 著書「React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで 」