コツコツと

[GatsbyJS] パンくずリストを作成する(試作)

September 12, 2020

GatsbyJS で生成したサイトに、試作ですがパンくずリスト(位置型)を組み込んでみました。
力技で作った感があるので、実装例の一つとして見てもらえればと思います。
今後、もっとスマートな実装になるよう改良するつもりです。
以下、実装のポイントについて紹介していきます。

パンくずリストとは

パンくずリストとは、このページの上部にある下図で示す部分です。

パンくずリスト

これは、「 > 」区切りでサイトの階層構造を表しています。
また、top をクリックするとトップページへ、テックをクリックするとテック記事一覧ページへ遷移します。

まとめるとパンくずリストは次の機能を持っています。

  • 閲覧中のページが、サイトのどの階層に位置するか伝える
  • サイトのナビゲーションをする(親階層へのリンクを提供する)

パンくずリストの実装

ソースコードは、こちらのコミットを参照ください。
パンくずリスト用の React コンポーネントの作成がメインです。

以下、実装のポイントのみ説明します。

  • ページのパス(URL からドメインを除いた部分)から階層構造を生成する
  • 生成した階層がページとして存在するか検証する
  • パンくずリストの階層名として表示するラベルを取得する
  • パンくずリストコンポーネントを作成する
ページのパス(URL からドメインを除いた部分)から階層構造を生成する

そもそも URL 自体がサイトの階層構造を表しているので、 URL をパースしてサイトの階層構造を生成していきます。
具体例を示します。

例えば、この記事のパス(ドメイン部分を除いた URL)は次の通りです。
/tech/2020-09-12-gatsbyjs-パンくずリストを作成する(試作)

そして、この記事の階層構造は、最下層から示すと次の通りです。
/tech/2020-09-12-gatsbyjs-パンくずリストを作成する(試作):本記事のパス
/tech:テック記事一覧ページのパス
/:トップページのパス

要するに、開いたページのパスの末尾の方から/以降を除去していくと、 基本的に親階層のパスになります。

このロジックを利用するためには、input として開いたページのパスが必要となります。
GatsbyJS では、コンポーネント1の props から開いたページのパスを参照できます。

// ページのパスの参照方法

// ページを生成するコンポーネント
const BlogPostTemplate = ({ data, pageContext, location }) => {
  // location.pathnameで開いたページのパスを参照できます
  const path = location.pathname
  ・・・
}
パスの検証

パスをパースすれば、階層構造を生成できますが、その階層が存在しているか検証が必要です。
検証のためには、サイト内の全てのパスが必要となりますが、 GatsbyJS では、サイト内の全てのパスを GraphQL で取得できます。
そして、次のような実装でサイト内のパスリストを生成できます。

import { useStaticQuery, graphql } from "gatsby";
export default function BredcrumbList(props) {
  const { allSitePage } = useStaticQuery(graphql`
    query {
      allSitePage {
        nodes {
          path
        }
      }
    }
  `);
  // サイト内のパスリストを生成する
  const pathList = allSitePage.nodes.map(({ path }) => encodeURI(path));
  ...
}

パスリストが作成できたら、パスの検証は容易です。
パースして生成した親階層のパスがパスリストに存在するか確認するだけです。

const parentPath = /** 親階層のパスを生成する処理 */
if(pathList.indexOf(parentPath)!==-1){
  // 親階層のパスがサイト内に存在する場合の処理
}else{
  // 親階層のパスがサイト内に存在しない場合の処理
}

ちなみに、今回の実装ではパスが存在しない場合は、さらに上の階層のパスを検証し、検証結果が OK となった階層のパスを直近の親階層としています。

例)/hoge/fuga の階層構造を生成するが、/hoge のページが存在しない場合
トップページ > fuga という階層構造にする

パンくずリストの階層名として表示するラベルを取得する

パンくずリストの階層名として表示するラベルが必要となります。
これも GraphQL で取得できるように考えました。
トップページとそれ以外でラベルの取得先を分ける方法しか思い浮かびませんでした。

トップページ
gatsby-config.js で設定する siteMetadata の topPage プロパティを階層名として利用することにしました。

それ以外のページ
gatsby-node.js でページを生成する際に、context にページのタイトルを設定する方法を取りました。そして、そのタイトルを階層名としました。
context に設定すると、allSitePage から参照可能になります。
次のような実装になります。

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions;

  /** 省略 */

  // ページ生成部
  createPage({
    path: post.node.fields.slug,
    component: blogPost,
    context: {
      slug: post.node.fields.slug,
      // context中に階層名のラベルを設定する
      pageTitle: title,
      updatedate: updatedate,
    },
  });
};

各ページの階層名を取得するための GraphQL のクエリは次のようになります。

{
  allSitePage {
    nodes {
      path
      context {
        pageTitle
      }
    }
  }
  site {
    siteMetadata {
      topPage
    }
  }
}
パンくずリストコンポーネントを作成する

上記のポイントを踏まえて、パンくずリストコンポーネントを作成しました。
親コンポーネントから次のように location を渡して呼び出せるようにしています。

<BredcrumbList location={location}></BredcrumbList>

改良したいところ

  • 各ページの階層構造を build(サイト生成) 時に演算しておく
    現状、階層構造をページ表示時に演算しているので、このままではページ表示が遅くなってしまいます。
    これを防ぐため、階層構造は build 時に演算しておきたいです。 allSitePage は全てのページが生成されてからでないと利用できないと思うので、 pageContext や Data Layer に階層構造を格納するのは無理そうな気がしています。
    となると、Gatsby SSR APIs を利用して、<head>に構造化データのパンくずリストを持たせて、ページのパンくずリストは構造化データを参照して作るという方法が良さげな気がしています。

今回は、GatsbyJS で作成したサイトにパンくずリストを組み込む方法について紹介しました。
まだ、試作なのでもう少し検討 & 改良したいと思います。


  1. 厳密にはページを生成するコンポーネントです。src/pages 配下のコンポーネントか、createPage メソッドの component に設定したコンポーネントのことです。

© 2020 jiri3