# Gridsomeで生成したWebサイトにAlgolia検索を導入する

👇聴いてたらJAMstackがーみたいな話になってきてて(一応、このブログもGitHubにpushするとAWS Amplifyのビルドが動いてstaticなアレがアレで的な構成になっています。エンジンはVuePressです)、Gridsomeが良さげとのことだったので、Gridsome試すついでにAlgoliaも組み込んでみようかな、と。

# Gridsomeのgetting started

Gridsomeのドキュメントをみると一番最初に👇のように書いてあって、Vue.jsで早いっていうのが特徴なのかな、と。

Gridsome is a Vue.js powered Jamstack framework for building static generated websites & apps that are fast by default 🚀

Vue.jsのフロントエンド用で、どんなHeadless CMS、API、もしくはマークダウンファイルにも使えて、開発中はホットロードできて、グラニュラーなルーティングで、GraphQLでデータをホゲホゲできて〜とかってことが書いてありました。

# Headless CMS

Headless CMSはShopifyとかSalesfoce Commerce Cloud(SFCC)とかでも話題になってるホットなエリアなのかな?と。

裏側のCMSとフロントエンドを切り離して〜っていうのは時代の流れ感あるので、そのうち楽天とかもそんな風になっていくのですかね〜。

# How it works

それぞれのページごとに.htmlと.jsonが出来て、事前にjsonをフェッチしておいて次のページ用にロードしておくとかって早い動作のための工夫があって、jsはcode splittingしておいて、なんというか使うとこだけの小さいものにしておくことで早くしようぜ的な工夫がされている、と。 そしてJSバンドルはgzipで57KBっていう小さいサイズがデフォルト(vue.js, vue-router, vue-meta and some for image lazy loading)なのだそうです。

# 動かす環境

Vue.jsが動けば、GraphQLは使う場合だけのオプション。Node.jsのv8.3以上でYarnが推奨

# インストールしてプロジェクトを作っていきます

👇こんな感じでとてもお手軽感ある。

$ yarn global add @gridsome/cli
yarn global v1.19.1
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
〜略〜
success Installed "@gridsome/cli@0.3.3" with binaries:
      - gridsome
✨  Done in 7.44s.

$ gridsome create my-gridsome-site
❯ Clone https://github.com/gridsome/gridsome-starter-default.git 1.81s
❯ Update project package.json 0s
❯ Install dependencies 54.07s

  - Enter directory cd my-gridsome-site
  - Run gridsome develop to start local development
  - Run gridsome build to build for production

$ cd my-gridsome-site/
$ gridsome develop
Gridsome v0.7.17

Initializing plugins...
Load sources - 0s
Create GraphQL schema - 0.02s
Create pages and templates - 0.03s
Generate temporary code - 0.03s
Bootstrap finish - 7.67s
〜略〜
Site running at:
- Local: http://localhost:8080/
- Network: http://10.0.1.6:8080/

Explore GraphQL data at: http://localhost:8080___explore

👇こんなHello Worldになりましたよ、と。

Hello World

# とりあえずHoge

日本人ならまずはHogeでしょう、ということで、Hoge.vueを作っていきます。

Hoge.vue

中身は👇こんなヤツ…w

<template>
  <Layout>

    <!-- Learn how to use images here: https://gridsome.org/docs/images -->
    <g-image alt="Example image" src="~/favicon.png" width="135" />

    <h1>これはHogeです。</h1>

    <p>
      コレはホゲのパラグラフです。Algoliaで日本語検索できるかな?🔎
    </p>

    <p class="home-links">
      <a href="https://gridsome.org/docs/" target="_blank" rel="noopener">Gridsome Docs</a>
      <a href="https://github.com/gridsome/gridsome" target="_blank" rel="noopener">GitHub</a>
    </p>

  </Layout>
</template>

<script>
export default {
  metaInfo: {
    title: 'This is Hoge!'
  }
}
</script>

👇サーバーを再起動することなく、画面で確認できました。

Hoge

# Algolia検索を導入

Pluginを探そうと思ったら、GridsomeのWebサイトの検索がAlgoliaで出来てるのね、と。

Algolia

そして、プラグインとしては、Gridsome plugin Algoliaというのがあって、このGridsome plugin Algoliaは gatsby-plugin-algolia のポーティングということなのだそうです。

仕掛けとしては gridsome build というコマンドで指定した情報をオブジェクトの配列としてAlgoliaに連携してくれるものとのこと。

まずは yarn でgridsome-plugin-algoliaを入れていきます。

👇一瞬だった。

$ yarn add gridsome-plugin-algolia
yarn add v1.19.1
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved lockfile.
success Saved 13 new dependencies.
info Direct dependencies
└─ gridsome-plugin-algolia@2.1.3
info All dependencies
├─ agentkeepalive@2.2.0
├─ algoliasearch@3.35.1
├─ dom-walk@0.1.2
├─ envify@4.1.0
├─ es6-promise@4.2.8
├─ events@1.1.1
├─ foreach@2.0.5
├─ global@4.4.0
├─ gridsome-plugin-algolia@2.1.3
├─ load-script@1.0.0
├─ lodash.chunk@4.2.0
├─ min-document@2.19.0
└─ reduce@1.0.2
✨  Done in 3.14s.

続いて.envファイルにAlgoliaの情報を書いていきます。

今回は.env.productionというファイルに👇を記載しました。

ALGOLIA_APP_ID=<<AlaoliaのアプリケーションID>>
ALGOLIA_ADMIN_KEY=<<データを投入するのでAdminキー。取り扱いに注意>>
ALGOLIA_INDEX_NAME=HOGEHOGEHOGE

続いて gridsome.config.js でAlgoliaにPostの内容を送信する設定。idとtitleとslugとmodifiedなので、ページのパラグラフの内容はこのままだと登録されなさそうですねぇ。

そして、gridsome-config.jsじゃなくて、gridsome.config.jsだよねぇとか、indexNameの前の行にカンマが無いとか、インデックス名は.envファイルに書いたよね?とかはありそうですが、とりあえずこれで。

const collections = [
  {
    query: `{
      allBlogPost {
        edges {
          node {
            id
            title
            slug
            modified
          }
        }
      }
    }`,
    transformer: ({ data }) => data.allBlogPost.edges.map(({ node }) => node),
    indexName: process.env.ALGOLIA_INDEX_NAME,
    itemFormatter: (item) => {
      return {
        objectID: item.id,
        title: item.title,
        slug: item.slug,
        modified: String(item.modified)
      }
    },
  },
];

module.exports = {
  plugins: [
    {
      use: `gridsome-plugin-algolia`,
      options: {
        appId: process.env.ALGOLIA_APP_ID,
        apiKey: process.env.ALGOLIA_ADMIN_KEY,
        collections,
        chunkSize: 10000, // default: 1000
        enablePartialUpdates: true, // default: false
      },
    },
  ],
};

で、 gridsome build を叩いたら、、

Algolia collection #0: Executing query
Error: Algolia failed: report is not defined
    at /Users/eijishinohara/gridsome/my-gridsome-site/node_modules/gridsome-plugin-algolia/index.js:184:13

なんか落ちてるけどreportってなんだか分からないので、node_modulesの下のソース見てみます、、

該当箇所はtry-catchしてキャッチしたメッセージを出してるだけなので、わからず、とりあえずそれっぽいところに👇を入れたら、インデックス名は正しく取得できている模様

console.log("indexName: " + indexName);

で、コード追いかけたら👇で落ちてた。

const result = await graphql(query);

if (result.errors) {
  report.panic(`failed to index to Algolia`, result.errors);
}

で、クエリってどんなんだろうと思って👇を見てみたら、、allBlogPostってスキーマにそんなの無い気がするのねん。。

query: {
      allBlogPost {
        edges {
          node {
            id
            title
            slug
            modified
          }
        }
      }
    }

スキーマ定義のあるmetadataでクエリは出来るけど、

meta

allBlogPostなんてのはあらへん。

schema

コレはアレか、、ローカルのコンテンツをそのままGraqhQLでクエリしてくれるわけではなくて、それっぽいことをしなきゃいかんのだな、と。。

# @ririliさんの『Gridsomeでブログを作る』という記事

ググってたらGridsomeでブログを作るという数少ない日本語のGridsomeに関する日本語の情報にたどり着いて👇あー、そういうことなのね、と。

今回は簡単のために外部サービスは利用せずにfilesystemをデータソースとする

そして、この通りにやれば、allBlogPostでクエリできるようになるのか!という、なんというかハードル高かったというか、なんというか。。

ということで、やっていきます。source-filesystemを入れて、

$ yarn add @gridsome/source-filesystem
yarn add v1.19.1
[1/4] 🔍  Resolving packages...
warning @gridsome/source-filesystem > chokidar@2.1.8: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ @gridsome/source-filesystem@0.6.2
info All dependencies
└─ @gridsome/source-filesystem@0.6.2
✨  Done in 4.12s.

gridsome.config.jsに👇を追加。

module.exports = {
  siteName: 'Gridsome',
  siteDescription: 'Gridsome Sample',
  plugins: [
    {
      use: '@gridsome/source-filesystem',
      options: {
        typeName: 'Post',
        path: 'blog/*.md',
      }
    },

blogディレクトリ配下に1.mdと2.mdっていうファイルを作って👇こんな感じの適当なマークダウンファイルを置いてあげます、と。

---
title: テストタイトル1
date: 2020-06-01 01:00:00+09:00
description: "1つ目のマークダウンのディスクリプション"
tags: "hoge"
---

1つ目のマークダウンのブログコンテンツです

そして、Index.vue。一番最初に表示されるページなので、GraphQLで取ってきたコンテンツをループで回してやる感じ。

<div v-for="item in $page.allPost.edges" :key="item.path" class="post">
  <h2>
    <g-link :to="item.node.path">{{ item.node.title }}</g-link>
  </h2>
  <p>{{ item.node.date }}</p>
  <g-link :to="item.node.path" class="continue-link">続きを読む ></g-link>
</div>

👇こんな感じでpage-queryには全部のマークダウンを取ってくるように。(ファイルが膨大になったらページネーションどうするとか出てくるんだと思うけど)

<page-query>
  query {
    allPost {
      edges {
        node {
          id
          title
          date (format: "YYYY年MM月DD日 HH:mm:ss")
          path
        }
      }
    }
  }
</page-query>

これでマークダウンのリストが表示できるようになりました。

list

続いて、リンクがクリックされた個別のページの表示の実装に入っていきます。

GridsomeではGraphQLコレクション用のテンプレート置き場があって、今回はここにPost.vueというファイルを置いていきます。

ファイルの中身はシンプルに、タイトルがh1で日付とコンテンツを表示するだけのこざっぱりしたもの。

<template>
  <Layout>
      <h1>{{ $page.post.title }}</h1>
      <dl>
        <dt>{{ $page.post.date }}</dt><dd></dd>
      </dl>
      <div class="content" v-html="$page.post.content" />
  </Layout>
</template>

GraphQLクエリもとてもシンプルな感じになりました。

<page-query>
query Post ($path: String!) {
  post: post (path: $path) {
      id
      title
      content
      date (format: "YYYY年MM月DD日 HH:mm:ss")
  }
}
</page-query>

これで詳細ページもできたので、めでたしめでたしかな、と。

page2

# 気持ちを新たにAlgoliaのインデクシングの実装にトライ

gridsome.config.jsのクエリを👇こんな感じで書き換えてあげて、

  query: `{
    allPost {
      edges {
        node {
          id
          title
          content
          date
        }
      }
    }
  }`

AlgoliaのIndex用にアレしてあげます👇

  transformer: ({ data }) => data.allPost.edges.map(({ node }) => node),
  indexName: process.env.ALGOLIA_INDEX_NAME,
  itemFormatter: (item) => {
    return {
      objectID: item.id,
      title: item.title,
      content: item.content,
      date: String(item.date)
    }
  },

この状態でgridsome buildを叩くと👇こんな感じでAlgoliaのindexingが動いて、

Algolia collection #0: Executing query
Algolia collection #0: items in collection 2
Algolia collection #0: starting Partial updates
Algolia collection #0: found 0 existing items
Algolia collection #0: Partial updates – [insert/update: 2, total: 2]
Algolia collection #0: splitting in 1 jobs
Finished indexing to Algolia in 2836ms

👇ちゃんとAlgoliaにデータが登録されていますね!

index

# Algoliaの検索バーを付けていく

まずはVue InstantSearchをyarnでaddしていくところから。

$ yarn add vue-instantsearch
yarn add v1.19.1
[1/4] 🔍  Resolving packages...
warning vue-instantsearch > instantsearch.js > hogan.js > mkdirp@0.3.0: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
warning "vue-instantsearch > algoliasearch-helper@3.1.2" has unmet peer dependency "algoliasearch@>= 3.1 < 5".
warning " > vue-instantsearch@3.0.3" has unmet peer dependency "algoliasearch@>= 3.32.0 < 5".
warning " > vue-instantsearch@3.0.3" has unmet peer dependency "vue@^2.6.0".
warning " > vue-instantsearch@3.0.3" has unmet peer dependency "vue-server-renderer@^2.6.11".
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 10 new dependencies.
info Direct dependencies
└─ vue-instantsearch@3.0.3
info All dependencies
├─ abbrev@1.1.1
├─ algoliasearch-helper@3.1.2
├─ classnames@2.2.6
├─ hogan.js@3.0.2
├─ instantsearch.js@4.6.0
├─ nopt@1.0.10
├─ preact@10.4.4
├─ prop-types@15.7.2
├─ react-is@16.13.1
└─ vue-instantsearch@3.0.3
✨  Done in 5.16s.

あと、algoliasearchとinstantsearch.cssもついでに入れていきます。

でもって、👇こんな感じでmain.jsでVue InstantSearchをインクルードしてあげます。

import Vue from 'vue';
import InstantSearch from 'vue-instantsearch';
import DefaultLayout from '~/layouts/Default.vue'

Vue.use(InstantSearch);

次にSearch.vueというファイルを作って、templateは検索してタイトルを表示するだけ。

<template>
  <ais-instant-search :search-client="searchClient" index-name="インデックス">
    <ais-search-box />
    <ais-hits>
      <div slot="item" slot-scope="{ item }">
        <h2>{{ item.title }}</h2>
      </div>
    </ais-hits>
  </ais-instant-search>
</template>

scriptの中は空クエリを発生させないようにしたりして👇こんな風に。

<script>
import algoliasearch from 'algoliasearch/lite';
import 'instantsearch.css/themes/algolia-min.css';

const algoliaClient = algoliasearch(
  アプリケーションID,
  検索オンリーAPI KEY
);

const searchClient = {
  search(requests) {
    if (requests.every(({ params }) => !params.query)) {
      return Promise.resolve({
        results: requests.map(() => ({
          hits: [],
          nbHits: 0,
          nbPages: 0,
          processingTimeMS: 0,
        })),
      });    
    }
    return algoliaClient.search(requests);
  }
}

export default {
  data() {
    return {
      searchClient,
    };
  },
};
</script>

# Index.vueにAlgoliaの検索を組み込む

今回はIndex.vueにAlgoliaの検索を導入したいと思います。scriptの中でSearch.vueをimportしてexport defaultで指定してあげて、

<script>
import Search from "../Search"
export default {
  components: { Search },

templateの中でHello Worldの下にSearchコンポーネントを突っ込んであげるだけ。お手軽。

<h1>Hello, world!</h1>

<Search />
<br/>

そうすると👇のように検索できるようになりました。めでたしめでたし。

search

このエントリーをはてなブックマークに追加

Algolia検索からの流入のみConversionボタン表示