こんにちは、でぃーすけと申します。
今回はWebフレームワークAstroを使ってmicroCMSにおける検索機能を実装する方法をご紹介します。
はじめに
事前準備として以下の公式ブログを参考に「ブログサイト」を構築してください。
https://blog.microcms.io/astro-microcms-introduction/
今回は上記で作成したブログサイトをベースに検索機能を実装していきます。
また、UIフレームワークとしてPreact、データフェッチライブラリとしてSWRを使用しますので、以下の公式ブログを参考にそれぞれのライブラリを導入してください。
https://blog.microcms.io/astro-preview/#h00c1750207
実装の全体像
全体像としては先ほど挙げた「AstroとmicroCMSを使った画面プレビューを実装する」の記事でのやり方とかなり近いものになります。
- pagesディレクトリに検索ページを作成する
- クライアントJavaScriptで検索キーワードをURLクエリパラメータに追加する
- クライアントJavaScriptでURLクエリパラメータから検索キーワードを取得する
- クライアントJavaScriptでmicroCMSにリクエストを送り、取得したデータをレンダリングする
検索ページを作成する
まずは検索が行われるページを作成します。
今回は /search
で開かれるページを作成したいので、一旦最小限の形で src/pages/search.astro
を作成します。
---
import Layout from "../layouts/Layout.astro";
---
<Layout title="My first blog with Astro">
<main></main>
</Layout>
検索キーワードをURLクエリパラメータに追加する
「キーワードを入力して確定すると /search?q=<検索キーワード>
に遷移する」ようにPreactを使って実装していきます。
まずはUI部分を実装します。
const SearchButton = () => {
return (
<form
role="search"
>
<label for="blog_search">
記事を検索
</label>
<input
id="blog_search"
type="search"
/>
<button>検索</button>
</form>
);
};
export default SearchButton;
参考:https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/Roles/Search_role
次に、inputタグに入力された値をPreactで管理して制御されたコンポーネントとしてレンダリングするための実装を追加します。
(今回のような管理したステートを他に使用していない場合は、制御されていないコンポーネントとして扱っても複雑にならないかと思います)
import { useState } from "preact/hooks";
import type { JSXInternal } from "preact/src/jsx";
const SearchButton = () => {
const [value, setValue] = useState(
typeof window !== "undefined"
? new URLSearchParams(window.location.search).get("q") ?? ""
: ""
);
const handleChange: JSXInternal.GenericEventHandler<HTMLInputElement> = (
event
) => {
setValue((event.target as HTMLInputElement).value);
};
return (
<form
role="search"
>
<label for="blog_search">
記事を検索
</label>
<input
id="blog_search"
type="search"
value={value}
onChange={handleChange}
/>
<button>検索</button>
</form>
);
};
export default SearchButton;
※本来Preactでは(ReactでいうonChangeは)onInputを使用するのですが、preact/compat
を使用している場合は onChange
で問題ありません。
これでキーワードの入力がステートとして管理されるようになったので、formのsubmitイベントで検索ページに遷移するようにします。
import { useState } from "preact/hooks";
import type { JSXInternal } from "preact/src/jsx";
const SearchButton = () => {
const [value, setValue] = useState(
typeof window !== "undefined"
? new URLSearchParams(window.location.search).get("q") ?? ""
: ""
);
const handleChange: JSXInternal.GenericEventHandler<HTMLInputElement> = (
event
) => {
setValue((event.target as HTMLInputElement).value);
};
const handleSubmit: JSXInternal.GenericEventHandler<HTMLFormElement> = (
event
) => {
event.preventDefault();
window.location.href = `/search?q=${value}`;
};
return (
<form
role="search"
onSubmit={handleSubmit}
>
<label for="blog_search">
記事を検索
</label>
<input
id="blog_search"
type="search"
value={value}
onChange={handleChange}
/>
<button>検索</button>
</form>
);
};
export default SearchButton;
このコンポーネントをTOPページのAstroファイルに追記しましょう。
検索キーワードを入力後、エンターを押して、/search?q=<検索キーワード>
に遷移できれば成功です。
URLクエリパラメータから検索キーワードを取得する
それでは検索ページの実装に移りましょう。
このページでは先ほどURLクエリパラメータに追加した検索キーワードをもとに記事を検索します。
そのための準備として、q
パラメータを取得しましょう。
const BlogSearch = () => {
const params = new URLSearchParams(window.location.search);
const q = params.get("q");
console.log(q);
return (
<div></div>
);
};
export default BlogSearch;
このようなコンポーネントを作成し、src/pages/search.astro
内に追加します。
---
import BlogSearch from "../components/BlogSearch";
import SearchButton from "../components/SearchButton";
import Layout from "../layouts/Layout.astro";
---
<Layout title="My first blog with Astro">
<main>
<SearchButton client:visible />
<BlogSearch client:only="preact" />
</main>
</Layout>
その上で先ほどの入力UIから検索ページに移動し、コンソールの表示を確認してみましょう。
入力したキーワードが表示されていれば成功です。
microCMSにリクエストを送り、取得したデータをレンダリングする
次は実際にmicroCMSにリクエストを送ります。
先ほど取得した検索キーワードをGET APIのパラメータに指定することで全文検索ができます。
import useSWR from "swr";
import { getBlogs } from "../library/microcms";
const BlogSearch = () => {
const params = new URLSearchParams(window.location.search);
const q = params.get("q");
const { data } = useSWR(
q === null ? null : ["/search", q],
([, q]) =>
getBlogs({
fields: ["id", "title"],
q,
})
);
console.log(data);
return (
<div></div>
);
};
export default BlogSearch;
※useSWRの使い方等はドキュメントサイトをご覧いただくか、前回のブログでも軽くご紹介させていただいておりますのでそちらをご確認ください。
コンソールを確認してみて、undefined
から microCMSのデータに変われば成功です。
TOPページと同様に記事のリンクを表示しましょう。
import useSWR from "swr";
import { getBlogs } from "../library/microcms";
const BlogSearch = () => {
const params = new URLSearchParams(window.location.search);
const q = params.get("q");
const { data, error, isLoading } = useSWR(
q === null ? null : ["/search", q],
([, q]) =>
getBlogs({
fields: ["id", "title"],
q,
})
);
if (error) return <div>エラーが発生しました</div>;
if (isLoading) return <div>読み込み中...</div>;
return (
<div>
{data?.contents.length !== 0 ? (
<ul>
{data?.contents.map(({ id, title }) => (
<li key={id}>
<a href={id}>{title}</a>
</li>
))}
</ul>
) : (
<div>検索結果はありません</div>
)}
</div>
);
};
export default BlogSearch;
この実装でも10件まで検索結果は表示できますが、11件以上検索結果があった場合に表示ができないので簡単にページングを実装していきましょう。
import useSWR from "swr";
import { useState } from "preact/hooks";
import { getBlogs } from "../library/microcms";
const LIMIT = 10;
const BlogSearch = () => {
const params = new URLSearchParams(window.location.search);
const q = params.get("q");
const [page, setPage] = useState(1);
const { data, error, isLoading } = useSWR(
q === null ? null : ["/search", q, page],
([, q, page]) =>
getBlogs({
fields: ["id", "title"],
q,
limit: LIMIT,
offset: (page - 1) * LIMIT,
})
);
if (error) return <div>エラーが発生しました</div>;
if (isLoading) return <div>読み込み中...</div>;
return (
<div>
{data?.contents.length !== 0 ? (
<>
<ul>
{data?.contents.map(({ id, title }) => (
<li key={id}>
<a href={id}>{title}</a>
</li>
))}
</ul>
{data?.totalCount !== undefined && (
<nav>
<ul style={{ display: "flex", gap: "8px" }}>
{Array.from({
length: Math.ceil(data.totalCount / LIMIT),
}).map((_, i) => (
<li key={i} style={{ listStyle: "none" }}>
<button
onClick={() => setPage(i + 1)}
disabled={page === i + 1}
>
{i + 1}
</button>
</li>
))}
</ul>
</nav>
)}
</>
) : (
<div>検索結果はありません</div>
)}
</div>
);
};
export default BlogSearch;
page
というステートを持たせて、これをもとに表示されている検索結果を切り替えています。
そして、microCMSのパラメータとしては limit
と offset
を指定しています。(limit
に関してはデフォルトと同じ10にしているので指定しなくても構いません。)
これによって page
の値によって取得される記事が変化し、ページごとに適切な記事を取得できます。
ページを切り替えてみて記事リンクが適切に切り替われば成功です。
おわりに
今回はかなり最小限の形で検索機能を実装しました。
これをベースにして、以下のような実装をすることでさらにユーザビリティを上げることができると思います!
- ページネーション:1…5 6 7…10 のようなUIを作る
- ページネーション:A11Yに必要な属性等を追加する(参考:https://a11y-style-guide.com/style-guide/section-navigation.html#kssref-navigation-pagination)
- History APIを使って検索ページ内ではSWRのキャッシュを最大限利用する(参考:https://developer.mozilla.org/ja/docs/Web/API/History)
page
ステートをURLに含めてリンクとして共有できるようにする