2021/12/05:サイトの再デザインに伴い、この方法よりよい方法を色々探したが結果的にこの方法が最善であることを確認(HTMLでフォームからデザインするのはおすすめしない)
また、現在ではGoogleの検索に自動で入ってくるCSSを上書きする形でデザインを変更している。
当然ではあるが下記の記事に書いてある内容は2021/12/05現在でも使用可能。
検索窓が消える現象は少々不可解である。まずGatsby develop
モードで初め表示されないのでリロードしてみるとなぜか表示される。
「キャッシュのせいか?」と思い内部リンクをクリックしてページを遷移すると何故かまた消える。
こんどはビルドした本番をNetlify側で確認すると一瞬表示されるが直ぐに消えてしまうという状況になる。
なぜなのだろうか。
script
タグをHelmet
に移動しただけではダメGatsby.jsを使っているクラスのエンジニアであればGoogleから提示されるコードのscrip
タグはJSX内では動作しないことは直感的に分かると思う。
そこでscript
タグをHelmetに移動すれば動作する・・・のだが結局上記のエラーが出てしまうのだ。
一度表示されてすぐ消えるというのはDOMがマウントされたあとでGatsby側に何かしらの処理があり消去されているのではないかと考えた。
デバッグで見ると検索フォーム用のdiv
が丸っと削除されているからだ。
セキュリティ的理由だろうか?
普段はできるだけ簡単な設置方法にフォーカスしているが今回は経緯を記述させてもらった。
ここから私の環境で無事に動作する方法を紹介するが、そもそも公式のコードではなぜ動作しないのかという理由までは分かっていない。ご了承願いたい。
まず参考にしたサイトを先にリンクさせて頂く。次のサイトは何れも非常に参考になった。この場を借りてお礼を申し上げたい。
[日本語①] Googleカスタム検索エンジンの設置方法
Custom Search Element Control API という設置方法の仕組みを利用した設置方法(Gatsby専用の解説ではない)
どうやら自前で検索窓をカスタマイズしたい場合のAPIのようだ。こちらは日本語情報が非常に少ないため大変助かる記事だ。
[英語フォーラム②] Google Custom Search Box doesn't appear on first load in my Gatsby project, only appears second load
こちらのサイトに「私はこう解決した」という例が幾つか乗っているが何れも私の環境では動作しなかった。しかしAnnieTaylor氏の回答は上のAPIと組み合わせれば使えるのでは、と考えた。
Components
フォルダ配下に GoogleCustomSearch.js
というファイルを新規作成した。名称はなんでもいいだろう。
import React from "react"
const search = () => {
const cx = '数字:アルファベット'; //ここに貼り付け用コードの?cx=以下を入力
let gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = 'https://cse.google.com/cse.js?cx=' + cx;
let s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
return (
<div className="gcse-search"></div>
)
}
export default search
これは①のサイトより引用させてもらったコードをGatsby.js用に改変している。
識別コードのような箇所(cx)の記述を忘れないように。
余談:①のサイトでは窓を配置するためのdiv
タグにアレンジが加えられているがアレンジしてしまうとGatsbyでは動作しなかった。よって公式コードと同じdiv
タグにしてある。
build時にエラーが出る。
WebpackError: ReferenceError: document is not defined
エラーは let gcse = document.createElement('script');
で発生している。
私はDOM操作にあまり明るくないがMDNを見ると
https://developer.mozilla.org/ja/docs/Web/API/Document
Document インターフェイスはブラウザーに読み込まれたウェブページを表し、 DOM ツリーであるウェブページのコンテンツへのエントリーポイントとして働きます。
DOMの親である。
これは以前のVue.jsの記事(mountedの項)でほんの少しだけ触れたがReactのフック(Reactではフック、Vue.jsではライフサイクルフックと呼ぶ)でマウント後に処理するのかマウント前に処理するのかという問題だ。
つまりDOMのdocument
が配置される前に処理が実行されたためにエラーとなっている。
余談:実は今この部分を執筆するまでに英語サイトをゴリゴリ読んでから書いているのだが「React フック」で調べたら公式に日本語ページがあった。少し損した。
先程のエラーの内容から「document
が見つからない」と言っているが正にこの内容が公式の副作用フックの箇所に記述されている。
つまりDOMがマウントされた後でなければdocument
の要素にはアクセスできないのだ。
ここで参考サイト②のコードが出てくる。DOMがマウントされた後で描画するコードを追加する。
components
フォルダにhooks
というフォルダを作ってClientOnly.js
というファイルを作成した。フォルダは整理のため。
import React, { useState, useEffect } from 'react'
function ClientOnly({ children, ...delegated }) {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
return (
<div {...delegated}>
{children}
</div>
);
}
export default ClientOnly
これは「マウントされた後で引数で渡された引数のタグを挿入する」というコードだ。
実際に描画させたいJSXのページに次の上記2つをimport。
// google カスタム検索用
import Search from '../components/GoogleCustomSearch'
import ClientOnly from '../hooks/ClientOnly'
描画するためには次のタグにて読み込む。
{/* google検索 */}
<ClientOnly>
<Search />
</ClientOnly>
上記コード通りClientOnly
にSearch
を引き渡す事でマウント後に処理させる。
あとは挿入したい箇所にJSXを書いていくだけだ。
Googleカスタム検索エンジンの表示結果は思ったよりスマートでオシャレだ。
Gatsby系のBLOGを調べるとどのサイトもGoogleカスタム検索は避けているように見える。
恐らくこれは昔の仕様で見た目が悪く使いにくかったかったため熟練のエンジニアは直感的に避けているのだろう。
次のように検索結果に混ざって広告は挿入されるがオーバーレイ表示の使用感が思ったより良い。
Custom Search Element Control API を通しては描画できるのだが、公式の出している簡易版コードではなぜ動作しないのかは残念ながら分かっていない。
しかしながらAPIの使い方の初期段階はこれでクリアしたことになるので、もう少し学習コストを払うことにより検索窓のカスタマイズも自由に行えるようになるのではないだろうか。
知っておいて損はない実装方法だと思う。