検索機能が追加されました。右側のやつです。

実装方法

このブログページ自体は nuxt/contentで動いているため、nuxt/content の全文検索機能を用いて実装しました。全文検索自体はドキュメントを読めば簡単にできるので、content の良さを実感しています。

工夫点

もちろんタグ検索ですねー。元々ブログ記事にはタグを埋め込むようにしていたので、それで検索をかけています。タグの埋め込みイメージは下のような感じです。yml のため配列で格納するのも考えたのですが、こちらの形式の方が、全文検索のさいに扱いやすかったので、こちらで保存しています。

tags: Golang CloudRun

全てのタグ自体は、非同期に記事を全検索書けてマップでタグの値を数えて、DOM の値を更新するという風にしています。Vue では Map の変更が上手く反映されなかったので、このように最後に配列に push していくという形式を取っています。そんなに大量に記事が増えることもないと思うので、サーバーレンダリングでも良い気はしますが、一応非同期に行っています。手間がかかった割に地味な機能なんですよね。。。

const tags = reactive<TagCount[]>([]);

onMounted(() => {
  context.root
    .$content(queryName)
    .only("tags")
    .fetch<Promise<{ tags: string }[]>>()
    .then((resps) => {
      const tagMap = new Map<string, number>();
      resps.map((resp) => {
        _ = resp.tags?.split(" ").forEach((tag) => {
          tagMap.set(tag, (tagMap.get(tag) || 0) + 1);
        });
      });

      tagMap.forEach((num, tag) => {
        tags.push({
          name: tag,
          count: num,
        });
      });
    });
});

はまったこと

はまったことは、クエリが変わった際に発火するようにした asyncData の検索結果が、サーバーレンダリング時とクライント実行時で結果が変わったことです。

async asyncData({ $content, query }: Context) {
  const text = query.text ?? '';
  const resp = await searchContent($content(queryName), text);
  return {
    contents: resp,
  };
},

watchQuery: ['text'],

具体的には時刻文字列のソートがクライント実行時では一切効いていませんでした。検索自体はどちらも nodejs が行っているはずなんですが、ソートが効く時と効かないときがあるのは謎でした。とりあえず、クライント側で受け取る度にソートを行うことで解決しました。謎すぎます。

return result.then((resp: Article[]) => {
  return resp
    .sort((l, r) => {
      const lDate = new Date(l.created);
      const rDate = new Date(r.created);
      if (lDate === rDate) {
        return 0;
      }
      if (lDate > rDate) {
        return -1;
      }
      return 1;
    })
  ...

因みに、nuxt/content の createdAt プロパティを使わない理由としては、cloud run を使っているからです。cloud run の場合は、全てのファイルの作成日が同じになってしまうため、自身で独自のプロパティを設定してそちらで、作成日を管理しています。(これも改善できそうな気がしますが、取り敢えずって感じです。)

感想

検索機能ぐらい nuxt/content の機能でありますし、ぱっぱと実装できるかと思いましたが、結構かかりました。ただ、使い回せるコンポーネントが出来たので、他の検索機能を実装する際は、少し楽をできるかなって思っています。