otsunekoの日常

Gatsbyブログの改良(サイドバーへのタグ一覧表示、ブログタイトルの表示など)

夏休みなのでブログの更新が多くなっております。

せっかく時間があるしブログデザインの改良でもするかと、これまで必要性を感じつつ放置していたサイドバーへのタグ一覧表示機能追加と、ブログタイトル『otsunekoの日常』の表示、記事投稿日付の表示場所変更などを行いました。

サイドバーへのタグ一覧表示機能追加

参考にしたのはGatsbyの公式ドキュメントです…と言いたいところですが実際のところほぼ参考にしていません。

と言うのも上記公式ドキュメントで紹介されているのは、各自が自分のGatsbyブログで使用しているタグ一覧を表示するための/tagsページを作成するための手順であり、僕が使っているGatsbyテンプレートのgatsby-starter-lumenだと既にお目当てのものが実装されていたからです。

つまり、実際にはその内容をSidebar.jsにコピペするだけで事足りました(CSSは要調整)。

具体的には、以下のtags-list-template.jsのうちハイライトした箇所をSidebar.jsにコピペしました。<ul>タグの部分を表示させたい場所に貼り付ければOKです。

tags-list-template.js
// @flow strict
import React from 'react';
import { Link } from 'gatsby';import kebabCase from 'lodash/kebabCase';import Layout from '../components/Layout';
import Sidebar from '../components/Sidebar';
import Page from '../components/Page';
import { useSiteMetadata, useTagsList } from '../hooks';
const TagsListTemplate = () => {
  const { title, subtitle } = useSiteMetadata();
  const tags = useTagsList();
  return (
    <Layout title={`Tags - ${title}`} description={subtitle}>
      <Sidebar />
      <Page title="Tags">
        <ul>          {tags.map((tag) => (            <li key={tag.fieldValue}>              <Link to={`/tag/${kebabCase(tag.fieldValue)}/`}>                {tag.fieldValue} ({tag.totalCount})              </Link>            </li>          ))}        </ul>      </Page>
    </Layout>
  );
};

export default TagsListTemplate;

これでサイドバーへのタグ一覧表示は完了ですが、サイトのレイアウトが変わったのでconfig.jsのpostsPerPageを4→5に変更して1ページに表示される記事数を増やすことで縦のバランスを調整したりしました。

併せて、この機会にこれまで投稿した記事に付けていたタグを一気に見直しました。

ブログタイトルの表示

めっちゃ今更ですけど表示するようにしました。gatsby-starter-lumenのデフォルトだとブログタイトルがブラウザのページタイトルとしてしか現れません。「まぁえぇか…」と見て見ぬ振りでここまで来ましたが、それも今日までです。Layout.jsに<Link>タグでブログタイトルを追加しました。

せっかくだしフォントもいじってみるかと、Gatsby.jsでのWebフォントの読み込み方を参考に、FontSourceを使ってGoogleフォントを追加してみました。

採用したのはDotGothic16というフォントで、タイトルとbioに適用しています。

また、以前のデザイン(下図)だとbioの写真(実家のわんこ)或いは僕の名前(otsuneko)をクリックするとトップページに戻るという個人的に「?」な挙動(プロフィールページに飛ぶにはbioの下のAbout Meリンクをクリック)だったので、今回追加したブログタイトルでトップページに戻り、bioの写真及び僕の名前をプロフィールへのリンクに変更して、About Meリンクは消しました。この方が直感的だと思っています。

before

記事投稿日付の表示場所変更

gatsby-starter-lumenのデフォルト設定だと、各記事の投稿日付が記事の最後(一番下)に表示されます。これだと検索エンジン等から各記事に飛んできた人が、「この記事は新しいの?古いの?」というのを最初に判断できず不便なので、記事タイトルの右下に表示するようレイアウト変更しました。

具体的にはPost.js内でContentにdateプロパティを渡すように変更し、更にContent.js側でも以下変更を加えました。Content.module.scssにも、date用のスタイルを追加してます(ほぼcontent__titleから流用しましたけどね)。

Content.js
// @flow strict
import React from 'react';
import moment from 'moment';import styles from './Content.module.scss';

type Props = {
  body: string,
  title: string,
  date: string};

const Content = ({ body, title, date }: Props) => (  <div className={styles['content']}>
    <h1 className={styles['content__title']}>{title}</h1>
    <div className={styles['content__date']}>{moment(date).format('YYYY年M月D日')}</div>    <div className={styles['content__body']} dangerouslySetInnerHTML={{ __html: body }} />
  </div>
);

export default Content;

サイトのmax-widthの拡張

これまでは本ブログの表示幅を決めるmax-widthの値がかなり狭めに設定されており、スマホ等の縦長ディスプレイだとしっくり来るもののPC等横長のディスプレイだと下図のように左右がすかすかになるという問題がありました。

流石にスペースがもったいないし可読性も下がるので、max-widthを広げました。200pxぐらい。ついでに記事一覧ボタンも、ブログタイトルのリンクと機能が被るので消しました。

横長

以上が今回加えた変更になります。

重い腰を上げるまでは時間がかかるもののやり始めてみるとなんとかなるものですね。先延ばしヨクナイ。

2021/9/19追記:記事下部の「次の記事」「前の記事」リンク追加

こちらの記事を参考にさせて頂き、props.pageContextへnext、prevを渡してPostTemplateで取得できるようにしました(実際は「次の記事」「前の記事」リンクを表示させたかった場所の都合上、更にpageContextをPostに渡しています)。

ここでもCSSの設定に手こずりました。<ul>タグにdisplay:flexとjustify-content:space-betweenを設定したのですが、記事タイトルの長さに応じて「次の記事」「前の記事」領域の幅が変わってしまうので、こちらの記事を参考に、<li>タグでwidth:45%と固定した割合を設定することでなんとかうまくいきました。

と思ったのも束の間、Netlifyへのデプロイ後にサイトへアクセスして「次の記事」「前の記事」をクリックすると、リンク先が真っ白なページになりブラウザの「進む」「戻る」を押しても真っ白なままという事象が発生するようになりました(URLは正しく遷移できている)。ページをリロードしてやっと記事が表示されるのですが、これはサイトとして非常にまずい。Googleで類似事象を探したのですが見つからず、ソースの変更箇所をいじって試行錯誤しました。

具体的にはPost.js中でPropsにprevnext: PageContextを定義していないのが原因かと思い、ハイライト箇所を追加してみました。

Post.js
// @flow strict
import React from 'react';
import { Link } from 'gatsby';
import Author from './Author';
import Comments from './Comments';
import Content from './Content';
// import Meta from './Meta';
import Tags from './Tags';
import styles from './Post.module.scss';
import type { Node } from '../../types';
import type { PageContext } from '../../types';
type Props = {
  post: Node,
  prevnext: PageContext};

const Post = ({ post, prevnext }: Props) => {
  const { html } = post;
  const { tagSlugs, slug } = post.fields;
  const { prev, next } = prevnext;
  const { tags, title, date } = post.frontmatter;

以降私の環境では問題事象が治まったように見えるのですが、どうでしょう…
もし同様の事象に遭遇された方がおられましたら、ご指摘頂けるとありがたいです。

2021/9/21追記:記事下部のShareボタン追加

こちらの記事こちらの記事を参考にさせて頂き、以下のようなコンポーネントを作成して記事の下部にSHAREボタンを表示してみました。なおSHAREされん模様

現在のページURLを取得するうまい方法が見つからず四苦八苦していましたが、こちらの英語記事で紹介されているMethod 1を参考にした書き方でいけました(ハイライト箇所)。ありがたや。

Share.js
import React from 'react';
import {
  TwitterShareButton,
  TwitterIcon,
  FacebookShareButton,
  FacebookIcon,
  LineShareButton,
  LineIcon,
  HatenaShareButton,
  HatenaIcon
} from 'react-share';
import styles from './Share.module.scss'

const config = {
  size: 36
}

type Props = {
  title: string,
  size?: number,
};

const Share = ({ title, size }: Props) => {
  const url = typeof window !== 'undefined' ? window.location.href : '';
  return (
    <div className={styles['share']}>
        <h2 className={styles['share__title']}>SHARE</h2>
        <div>
            <TwitterShareButton className={styles['share__button']} url={url} title={title}>
                <TwitterIcon size={size ? size : config.size} round />
            </TwitterShareButton>
            <FacebookShareButton className={styles['share__button']} url={url}>
                <FacebookIcon size={size ? size : config.size} round />
            </FacebookShareButton>
            <LineShareButton className={styles['share__button']} url={url}>
                <LineIcon size={size ? size : config.size} round />
            </LineShareButton>
            <HatenaShareButton className={styles['share__button']} url={url}>
                <HatenaIcon size={size ? size : config.size} round />
            </HatenaShareButton>
        </div>
    </div>
  )
};

export default Share;

なお、記事にも書いてありますが、現在のページURLを単純に以下のやり方で取得しようとすると、Netlifyでのビルド時にwindowが解決できずエラーが出てしまうため、windowの存在確認が必要になります。僕はこれで苦しみました。

error_code
const url = window.location.href;
error_message
failed Building static HTML for pages - 0.814s
error "window" is not available during server side rendering.

2021/9/21追記:記事下部の関連記事追加

こちらの記事を参考に実装しました。記事中で既に言及されている注意事項をちゃんと読まなかったために全く同じ失敗をしてしまいましたが、勉強になったので良しとします(ポジティブ)。

上記記事の再掲になってしまうんですが、引っかかりポイントは以下2点です。

  • GatsbyのGraph QLにはPage QueryとStatic Queryの2種類があり、Page Queryはその名の通りページを作る時だけ使えるクエリ(gatsby-starter-lumenを例にとると、基本的にページテンプレート(templatesフォルダ配下のindex-template.js、post-template.js等)が対象)。各ページ内のコンポーネントを新たに作るときには、Static Queryしか使えない。
  • Static Queryはその名の通り静的クエリなので、クエリ文中で検索結果を動的に処理することができない(filter、sort等)。つまり一旦生の検索結果を受け取った後にJavascriptの関数を用いて加工処理を行う必要あり。

試行錯誤の末以下のようなコードで、同じカテゴリの記事を最新のものから5件表示させるコンポーネントができました。

RelatedPosts.js
import React from "react"
import { graphql, useStaticQuery, Link } from "gatsby"
const siteConfig = require('../../../config.js');
import styles from './RelatedPosts.module.scss'

type Props = {
  category: String
};

const RelatedPosts = ({ title, category }: Props) => {
  const data = useStaticQuery(
    graphql`
      query RelatedPosts{
        allMarkdownRemark{
          edges {
            node {
              fields {
                slug
                categorySlug
              }
              frontmatter {
                title
                date
                category
                description
              }
            }
          }
        }
      }
    `
  );

  const { postsPerPage } = siteConfig; //最大何個の関連記事を表示するか

  // 同じカテゴリの関連記事を最新のものからpostsPerPage件取得
  const relatedPosts = data.allMarkdownRemark.edges.filter(
    (edge) =>
      edge.node.frontmatter.category === category &&
      edge.node.frontmatter.title !== title
  ).reverse().slice(0,postsPerPage);

  // 関連記事がない場合は表示しない
  if (!relatedPosts.length) {
    return null;
  }

  return (
    <div className={styles['related-posts']}>
        <h3 className={styles['related-posts__title']} >関連記事</h3>
        <ul className={styles['related-posts__items']}>
            {relatedPosts.map((edge) => (
                <li key={edge.node.fields.slug}>
                <Link className={styles['related-posts__article']} to={`${edge.node.fields.slug}/`}>{edge.node.frontmatter.title}</Link>
                </li>
            ))}
        </ul>
    </div>
  )
};

export default RelatedPosts;

今後やりたいこととして、より関連度を考慮した関連記事の抽出、表示がありますね。いい自然言語処理サービスとか無いかなー。