静的サイトでの動的なサイト内検索機能の実装を紹介

静的サイトでの動的なサイト内検索機能の実装を紹介
カテゴリ
技術
タグ
GatsbyJS
React

はじめに

本ブログは GatsbyJS を使用して静的サイトとして運用しています。

ただ、それでもサイト内検索などの動的な機能が必要であると考え、実装しました。 非常に軽快で満足できる実装になりました。ぜひこちらの検索ページをご覧いただき、お試しください。

実際のコードが見たい場合は、こちらをご覧ください。

全体仕様

  • このページは、検索機能を提供します。API コールを行わず、ブラウザの JavaScript で動作します。
    • モバイル環境でも軽快に動作しますが、検索対象の増加によって性能が低下する可能性があります。
  • 検索文字列の入力 1 文字ごとにインタラクティブに検索結果を表示します。
  • URL のクエリパラメータに検索文字列が含まれ、入力フォームと同期しているため、リロードしても同じ表示が行われ、検索結果の URL で共有もできます。
  • 検索対象は全ての記事であり、タイトルか記事内容に文字列が含まれるかどうかで判定します。複数ワード検索はスペースで区切って行えます。大文字と小文字は区別しません。
  • 検索結果には、ヒット件数と記事のタイトルと説明文が表示されます。

詳細説明

必要最小限のコードを抜粋しています。これに基づいて後述します。

1import React, { useEffect, useState } from "react"
2import { graphql, Link } from "gatsby"
3import { convertCategory, mergePosts } from "../utilFunction"
4
5const Search = ({ data, location }: { data: any; location: Location }) => {
6  const posts = mergePosts(data.allMarkdownRemark, data.allWpPost, data.allFile)
7  const initQuery = decodeURI(
8    location.href?.split("?q=")[1] || ""
9  ).toLowerCase()
10  const [state, setState] = useState({
11    filteredData: filterByQuery(initQuery.split(/\s+/)),
12    query: initQuery,
13  })
14  const { filteredData, query } = state
15
16  function filterByQuery(queryWords: string[]) {
17    return posts.filter(post => {
18      for (const word of queryWords) {
19        if (
20          !post.title.toLowerCase().includes(word) &&
21          !post.description?.toLowerCase().includes(word)
22        ) {
23          return false
24        }
25      }
26      return true
27    })
28  }
29  function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
30    const queryWords = event.target.value.toLowerCase().split(/\s+/)
31
32    setState(prevState => ({
33      ...prevState,
34      filteredData: filterByQuery(queryWords),
35      query: queryWords.join(" "),
36    }))
37  }
38
39  useEffect(() => {
40    // ユーザーの入力があるたびにURLのクエリパラメータを更新
41    const params = new URLSearchParams()
42    if (query) {
43      params.append("q", query)
44    } else {
45      params.delete("q")
46    }
47    window.history.replaceState(
48      "",
49      "",
50      location.href.split("?")[0] +
51        (params.size > 0 ? "?" + params.toString() : "")
52    )
53  }, [state.query])
54
55  return (
56    <>
57      <input
58        type="text"
59        aria-label="Search"
60        placeholder="検索ワードを入力..."
61        onChange={handleInputChange}
62        value={query}
63      />
64      <div className="result-inner__res">
65        {query !== ""
66          ? query + " の検索結果: " + filteredData.length + "件"
67          : filteredData.length + "件の記事があります"}
68      </div>
69      <h1>サイト内検索</h1>
70      <p>{filteredData.length} 記事あります</p>
71      {filteredData.map(post => {
72        return (
73          <li key={post.slug}>
74            <Link to={`/${convertCategory(post.category)}/${post.slug}`}>
75              <h2>
76                <span>{post.title}</span>
77              </h2>
78              <section>
79                <p dangerouslySetInnerHTML={{ __html: post.excerpt }} />
80              </section>
81            </Link>
82          </li>
83        )
84      })}
85    </>
86  )
87}
88
  1. 検索対象となる全記事データを取得します。これはほぼ GatsbyJS の機能を利用しています。"mergePosts"関数は単にpageQueryで取得した data を使いやすい形に整形しているだけです。
  2. "useState"フックを使用して、フィルタリングされたデータとクエリを保持する"state"と"setState"を定義します。初期値として、URL のクエリパラメータに基づいて設定します。
  3. "filterByQuery"関数を定義しています。この関数は、クエリワードに基づいて記事をフィルタリングします。各記事のタイトルと説明をクエリワードと比較し、一致する記事のみを返します。
  4. "handleInputChange"関数も定義しています。ユーザーの入力に応じてクエリを更新し、フィルタリングされたデータを更新します。
  5. "useEffect"フックは、コンポーネントのレンダリング後に実行されます。ユーザーが入力したクエリで URL のクエリパラメータを更新します。つまり、同期させます。
  6. 最後に、レイアウトと検索結果を表示する JSX が返されます。入力欄、検索結果の表示、およびフィルタリングされた記事のリストが表示されます。

おわりに

以上です。少ない記述でやりたいことができるので、ぜひ参考にしてみてください。
React の強力さを感じました。



関連記事

  1. MarkdownでもWordPressでも使えるGatsbyJSでの構文ハイライト

    featured/cording.webp

    概要 GatsbyJS での汎用的な構文ハイライト(syntax highlight)の実装方法を紹介します。 本ブログは Markdown ファイルと WordPress の両方のコンテンツを Gatsby…

  2. AWSにデプロイしていたWordPressサイトをGatsbyJSによるGitHub Pagesでの静的サイトへ移行

    featured/wordpress2gatsby.webp

    はじめに 本ブログは WordPress を AWS にデプロイして運用していましたが、GatsbyJS を使って GitHub Pages…