ブログにJavaScriptの全文検索ライブラリを導入したらブラウザクラッシャーになった。

3 min read読了の目安(約2900字

JamstackなブログにJavaScriptの全文検索ライブラリを導入したらブラウザクラッシャーになりました。
採用したライブラリが悪いわけではなく、自分の事前調査・動作確認不足により発生した問題のため、反省として記事にしておきます。

実現したかったもの

Gridsome製の自分のブログで全文検索したかったので、クライアントサイドで動作する全文検索ライブラリのlunr.jsを導入しました。

lunr.jsを採用したのは、npm trendsで調べて人気だったのと、日本語に対応していたからです。
elasticlunr vs flexsearch vs fuse.js vs lunr | npm trends

できあがったもの

なかなか良さそうに見えます。ビルド用のPCからは普通に使えるのでそのまま公開しました。
その後、なんとなく手元のダブレット(iPad mini 2 Retina)から動作確認したところ、OSごとブラウザがクラッシュしました。
このままだとどこかしらの県警のお世話になりかねないため、とりあえずChromeのDeveloper ToolでHeap snapshotを見てみます。

lunrのIndexで189MB近く使われているようです。

iPad mini 2 RetinaのRAMは1GBなので、当然クラッシュします。
それ以前に、単なるテキストサイトで断りもなしにメモリを200MB近く食ってしまうのは問題があります。

何が悪かったか

lunrは全文検索の精度がとても良いのですが、その分メモリ効率が犠牲になっているように見えます。

私のブログは記事数が200以上あり、lunerが記事をIndexingした結果をシリアライズすると2MB近くになるため、工夫無しでlunrを利用するのに向いていないようです。
なお、ブログではGridsomeでのビルド時にサーバサイドでindexをjsonにシリアライズし、beforeMountでシリアライズされたindexを読み込む実装にしていました。

// gridsome.server.js
[...]
    // indexをjsonにシリアライズ
    const docs = data.allBlogPost.edges;

    const index = lunr(function () {
      this.use(lunr.multiLanguage("en", "jp"));
      this.ref("id");
      this.field("text");
      this.field("title", { boost: 10 });
      docs.forEach(({ node }) => {
        this.add({ id: node.id, title: node.title, text: node.content.replace(/<\/?[^>]+>/gi, "") });
      }, this);
    });

    fs.writeJsonSync(CACHE_PATH, index,
      {
        encoding: 'utf-8',
        replacer: null,
        spaces: "  "
      },
      err => {
      });
[...]
// Search.vue
[...]
  const serialisedData = require("serialised.json");
  beforeMount() {
    this.lunrIndex = lunr.Index.load(serialisedData);
  },
[...]

ドキュメントとソースコードを読んで使い方を工夫すればよいのかもしれませんが、そこまで頑張る気はなかったので、lunrの採用を諦めました。

ブラウザクラッシャーのリリースを防ぐために何をすべきだったか

  • あらかじめDeveloper ToolでPerformanceを見ておくべきだった。
  • PCのみの動作確認で終わらせずに、モバイル端末を使ったりLighthouseでパフォーマンスチェックすべきだった。
  • 採用するライブラリの性能を調査・Issueをチェックしておくべきだった。

これに懲りずに自分のブログの改良はしていきたいなと思いました。