Gatsby.jsにGoogleカスタム検索エンジンを設置する方法

April 25, 2020

Gatsby.jsにGoogleカスタム検索エンジンに指示されたコードを貼り付けると検索フォームが消えてしまう不具合がある。この問題に対応した。

検索窓が消える

検索窓が消える現象は少々不可解である。まず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タグにしてある。

これを貼り付けたいJSXに読み込んで使うと...

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>

上記コード通りClientOnlySearchを引き渡す事でマウント後に処理させる。

完成

2020 04 24 20h06 53

あとは挿入したい箇所にJSXを書いていくだけだ。

意外にオシャレ

Googleカスタム検索エンジンの表示結果は思ったよりスマートでオシャレだ。

Gatsby系のBLOGを調べるとどのサイトもGoogleカスタム検索は避けているように見える。

恐らくこれは昔の仕様で見た目が悪く使いにくかったかったため熟練のエンジニアは直感的に避けているのだろう。

次のように検索結果に混ざって広告は挿入されるがオーバーレイ表示の使用感が思ったより良い。

2020 04 26 00h12 06

所感

Custom Search Element Control API を通しては描画できるのだが、公式の出している簡易版コードではなぜ動作しないのかは残念ながら分かっていない。

しかしながらAPIの使い方の初期段階はこれでクリアしたことになるので、もう少し学習コストを払うことにより検索窓のカスタマイズも自由に行えるようになるのではないだろうか。

知っておいて損はない実装方法だと思う。

この記事をシェア:

author icon

仮想トイレ @CrypticToilet
プログラミングや仮想通貨のシステムトレードに関する情報を更新中!どんな情報を流しても詰まらないトイレ。