Gatsby×Contentfulで爆速な検索フォームを実装 | Gatsbyブログカスタマイズ
Webサイト高速化のための 静的サイトジェネレーター活用入門を通じて作成したこのブログのカスタマイズとして、サイト内検索機能を加えました。
ほとんど参考サイトのおかげで実装が可能でした。
フロントのみで検索機能が完結しているので、ほとんどリアルタイムに検索結果が表示される、爆速な点がとても気に入っています。
サイト内検索機能の仕様
仕様は参考サイトに則る形です。
バックエンドにContentfulを使用
検索フォームはモーダルで表示
記事を全件表示
全件表示から検索ワードでフィルタリングする
プラグイン(react-modal)をインストール
モーダルウインドウを簡単に実装できるReactのライブラリ、react-modalをインストールします。
yarn add react-modal
npmの場合は以下の通り。
npm install react-modal
modalsearch.jsの作成
src/components/modalsearch.jsを新規作成します。
モーダルウインドウの実装と、検索コンポーネントの呼び出しを記述します。
検索の虫眼鏡アイコンや、モーダルウインドウを閉じるための×アイコンを追記しています。
検索コンポーネントは後程作成するので、この時点ではビルドしてもエラーとなります。
import React from "react";
import Modal from "react-modal";
import Search from "./search";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faSearch, faTimes } from "@fortawesome/free-solid-svg-icons"
Modal.setAppElement("#___gatsby") //public/htmlのid参照
class ModalWindow extends React.Component {
constructor() {
super();
this.state = {
modalIsOpen: false
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
this.setState({modalIsOpen: true});
}
closeModal() {
this.setState({modalIsOpen: false});
}
render() {
return (
<div className="modalWrapper">
<button className="searchFor" onClick={this.openModal}><FontAwesomeIcon icon={faSearch} />SEARCH</button>
<Modal
isOpen={this.state.modalIsOpen}
onRequestClose={this.closeModal}
contentLabel="Search Modal"
className="modalSearchWindow"
overlayClassName="modalSearchOverlay"
>
<Search />
<button className="searchModalClose" onClick={this.closeModal}><FontAwesomeIcon icon={faTimes} />CLOSE</button>
</Modal>
</div>
);
}
}
export default ModalWindow;
search.jsの作成
src/components/search.jsを新規作成します。
検索コンポーネントを実装します。
年月表示を日本語にしたり、内部リンクの記述をLinkにしたりと、変更を加えています。
import React, { useState } from "react"
import { useStaticQuery, graphql, Link } from "gatsby"
const SearchResult = props => {
const tempData = useStaticQuery(graphql`
query SearchData {
allContentfulBlogPostForSeach: allContentfulBlogPost( sort: {fields: publishDate, order: DESC}) {
edges {
node {
title
slug
publishDateJP:publishDate(formatString: "YYYY年MM月DD日")
publishDate
}
}
}
}
`)
const className = useState("")
const allPosts = tempData.allContentfulBlogPostForSeach.edges
const emptyQuery = ""
const [state, setState] = useState({
filteredData: [],
query: emptyQuery,
})
const handleInputChange = event => {
const query = event.target.value
const posts = tempData.allContentfulBlogPostForSeach.edges || []
const filteredData = posts.filter(post => {
const title = post.node.title
return (
title.toLowerCase().includes(query.toLowerCase())
)
})
setState({
query,
filteredData,
})
}
const { filteredData, query } = state
const hasSearchResults = filteredData && query !== emptyQuery
const result = hasSearchResults ? filteredData : allPosts
return (
<div className={className}>
<div className="result-inner">
<div className="result-content">
<input
type="text"
aria-label="Search"
placeholder="検索ワードを入力..."
onChange={handleInputChange}
/>
<p className="result-inner__res">
{query !== "" ?
query + " の検索結果: " + result.length + "件"
: result.length + "件の記事があります"
}
</p>
<ul className="result-inner__search">
{result && result.map(({ node: post }) => {
return (
<li key={post.slug}>
<Link to={`/blog/post/${post.slug}/`}>
<span className="result-inner__date">{post.publishDateJP}:</span><span className="result-inner__title">{post.title}</span>
</Link>
</li>
)
})}
</ul>
</div>
</div>
</div>
)
}
export default SearchResult
ModalSeachの呼び出し
任意の箇所で検索機能を呼び出します。
このブログの場合は、src/components/header.jsで呼び出しています。
...
import ModalSeach from "./modalsearch"
...
<ModalSeach />
スタイル調整
CSSでスタイルを調整します。
SCSSではありますが、参考までに以下に示します。
.searchFor,
.searchModalClose {
background-color: transparent;
border: none;
cursor: pointer;
padding: 0;
appearance: none;
font-size: inherit;
font-family: inherit;
.svg-inline--fa {
padding-right: 0.25em;
}
}
.modalSearchOverlay {
background-color: rgba(0, 0, 0, 0.75);
position: fixed;
display: flex;
align-items: center;
justify-content: center;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 11;
}
.modalSearchWindow {
z-index: 12;
background: #fff;
outline: none;
position: relative;
}
.result-inner {
width: 80vw;
max-width: 800px;
&__search {
overflow: auto;
max-height: 350px;
min-height: 350px;
li {
padding: 1em 0;
border-bottom: 1px solid #808080;
&:first-child {
border-top: 1px solid #808080;
}
}
a {
display: inline-flex;
flex-direction: column;
@media (min-width: 768px) {
flex-direction: row;
}
.result-inner__date {
white-space: nowrap;
}
}
}
&__res {
padding: 1em 0;
}
}
.result-content {
padding: 1.5em;
display: flex;
flex-direction: column;
box-sizing: border-box;
@media (min-width: 768px) {
padding: 3em;
}
input {
padding: 1em;
font-size: inherit;
}
}
.searchModalClose {
position: absolute;
background-color: #fff;
padding: 0.5em 0.75em;
top: -2em;
right: 0;
@media (min-width: 768px) {
top: 0;
background: none;
box-sizing: border-box;
}
}
今後の展望
現時点では検索対象が記事タイトルのみとなっていますので、ゆくゆくは改修して本文も検索対象にしたいと思います。
参考サイト
▼Gatsby.js + Contentfulに爆速な検索フォームを実装する! | DevelopersIO
https://dev.classmethod.jp/articles/gatsby-js-contentful-search/