Gatsbyブログサイト移行物語~投稿テンプレにカテゴリやらキービジュアル(アイキャッチ)追加~

Gatsbyブログサイト移行物語~投稿テンプレにカテゴリやらキービジュアル(アイキャッチ)追加~

GatsbyReact

ブログのファーストビューって大切ですよね。キービジュアル次第(記事の最初に目を引く画像)で読むか読まないか考えますもん。 ということで記事のmarkdownファイルの設定でカテゴリ、タグ、記事のキービジュアルを追加できるようにしました。

主にはgatsby-plugin-imageを使って、キービジュアル画像などを追加する方法をご紹介します。

おまけで、「この記事を書いた人」的なパーツも改造しました。

※ 2021年12月28日v4対応のためリライトしました。

この記事を書いた人

かみーゆ/フロントエンドエンジニア

資金ゼロからフィリピンで起業した海外ノマドエンジニア。IT業界10年以上でテクニカルディレクター(技術責任者)・エンジニア講師・ブリッジSEを経てLenzTechnologies Inc.を設立し、代表を務める。CMS concreteCMSエバンジェリスト。テックブログ以外も「磨耗しない人生」や「海外ノマド」のライフスタイルについて発信。好きなものは肉とハイボール。

今までのGatsbyの記事と注意点

現在ここまで記載しています。
制作するまでを目標にUPしていくので順を追ったらGatsbyサイトが作れると思います。

  1. インストールからNetlifyデプロイまで
  2. ヘッダーとフッターを追加する
  3. 投稿テンプレにカテゴリやらキービジュアル(アイキャッチ)追加(←イマココ)
  4. ブログ記事、カテゴリ、タグ一覧の出力
  5. プラグインを利用して目次出力
  6. プラグインナシで一覧にページネーション実装
  7. 個別ページテンプレート作成
  8. プラグインHelmetでSEO調整
  9. CSSコンポーネントでオリジナルページを作ろう!!
  10. 関連記事一覧出力
  11. タグクラウドコンポーネントを作成する
  12. パンくずリストを追加する
  13. 記事内で独自タグ(コンポーネント)を使えるようにする
v5へのアップグレード方法はこちら。
Gatsby をアップグレード(v4→v5)して Netlify にデプロイ

Gatsbyのv4からv5へアップグレードしたのでそのやり方をメモしておきます。Netlify の Nodeバージョンの...

※ Gatsbyは2021月12月、v4にバージョンアップしています。随時リライトしています。

このシリーズはGithub・gatsby-blogに各内容ブランチごとで分けて格納しています。

今回のソースはadd-mainvisualブランチにあります。

このシリーズではテーマGatsby Starter Blogを改造します

この記事は一番メジャーなテンプレート、 Gatsby Starter Blogを改造しています。同じテーマでないと動かない可能性があります。

今回やりたいこと

ブログ記事のmarkdownファイルだけを取得し、投稿ごとにキービジュアル画像やカテゴリなどの属性を追加し、テンプレートに表示したい。

まずは普段更新する記事用のテンプレを作ります。長いですが、目次を活用しながら読み進めてください。

今回の完成目標です。

完成目標

おまけで、「この記事を書いた人」的なコンポーネントも改造しました。かなりブログっぽいページにGrade Upできます!

記事と記事中の画像などの格納フォルダーの設定

gatsby-node.jsのコメントから。

Explicitly define the siteMetadata {} object
This way those will always be defined even if removed from gatsby-config.js
Also explicitly define the Markdown frontmatter
This way the “MarkdownRemark” queries will return null even when no blog posts are stored inside “content/blog” instead of returning an error

siteMetadata 内のデータ(オブジェクト)を明示的に定義します。
gatsby-config.jsから削除された場合でも、これらは常に定義され。Markdown内のfrontmatterも明示的に定義します。 このように、「MarkdownRemark」クエリは、ブログの投稿は、エラーを返す代わりに「content/blog」内に保存されます。

つまり、content/blogに格納されたmarkdownファイルは記事として反映されます。

記事内本文に表示したい画像もマークダウンファイルと同じblogフォルダーに格納します。

私は今公開されているサイトにディレクトリー構造をできるだけ近づけるため、以下のように設置しました。

プロジェクト/
  └ content/
    ├ src
    |  └ images/ 汎用で使う画像全般を格納
    |    └ thumbnail/ 記事のキービジュアル&サムネイル用の画像
    └ blog/
      ├ 個別のページ
      └ blogs/
        ├ images/ 記事用の画像
        └ 各ブログ記事

gatsby-config.jsを見ると /content/blog/src/images 以下に画像が格納できるようになっているのでこのまま使います。

gatsby-config.js
module.exports = {
  plugins: [
    `gatsby-plugin-image`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/blog`,        name: `blog`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,      },
    },
  ]
}

v4以降、ディレクトリー構造の変更方が分かっておらず、blogの中にblogsがあるというちょっと気持ち悪い構造になってしまったのですが、一旦はこれで運用することにしました。

frontmatter を設計し投稿する。

記事ごとに必要なタイトルなどの項目を設計します。

かみーゆ
かみーゆ

ここで適当に設計したら多分あとで泣きをみます。

後々何が出力したいかよく考えて作成します。今から以下のファイルを操作します。

プロジェクト/
  ├ gatsby-node.js(追記)
  ├ src/
  |  └ images/
  |    ├ common/dummy.png(追加)
  |    └ thumbnail/thumbnail/main-visual.png(追加)
  └ content/
    └ blog/
      └ blogs/entry1.md (追加)
mdファイル
---
title: テスト投稿
date: 2021-11-26
description: この記事はテスト投稿です
---

デフォルトではこんなものだと思いますので、キービジュアル(hero)、記事のタイプ(pagetypetype)を追加します。
ちなみに私のブログではcategoryは1つ、タグは複数OKというルールとしました。

かみーゆ
かみーゆ

ルールが煩雑だと記事も破綻しますからね。。。

entry1.md を新たに作り Frontmatter を編集する

entry1.md
---
title: 記事1
date: 2021-11-26
pagetype: blog
hero: thumbnail/main-visual.png
# ↑src/images/ディレクトリを参照している↑
description: この記事はテスト投稿です
cate: web-developer
tags: ['Gatsby', 'React']
---

複数の値は配列で。

['Gatsby', 'React']

今までブログをblogs/以下に公開していたので content/blog/blogs/ ディレクトリー内に格納します。

starter blog では記事はblog以下のディレクトリ構造がページ生成時に継承されます。

domainがhttp://example.comだった場合。

content/blog/blogs/entry1.md
↓↓↓
http://example.com/blogs/entry1

gatsby-node.js から frontmatter の設定変更

gatsby-node.js ファイルから exports.createSchemaCustomization 〜といいうコードを探します。

まだ何も触ってなければ、239行目あたり(結構下の方)にあるはずです。

createTypes内に設定された出力したいfrontmatterに応じて修正ます。

type Frontmattercatetags, heropagetype を追加します。

gatsby-node.js
//~ 省略
exports.createSchemaCustomization = ({ actions }) => {
  const { createTypes } = actions

  //~ 省略
  createTypes(`
    //~ 省略
    type Frontmatter {
      title: String
      description: String
      date: Date @dateformat
      pagetype: String      tags: [String]      cate: String      hero: String    }

    type Fields {
      slug: String
    }
  `)
}

GraghQLのコメントは#を頭に付与するだけ。

# コメント

GraphQLはクエリ言語なので、markdownファイルのfrontmatter(---で囲んだ部分)の設定もここから読み取ることができます。

取得するデータは result に格納設定されています。 frontmatterに、heropagetypeをgatsby-node.jsの上の方に追記します。

gatsby-node.js
// 省略

exports.createPages = async ({ graphQL, actions, reporter }) => {
  // 省略
  const result = await GraphQL(
    `
      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: ASC }
          limit: 1000
        ) {
          nodes {
            id
            fields {
              slug
            }
            frontmatter {              hero              pagetype            }          }
        }
      }
    `
  )
  if (result.errors) {
    // 省略

さらに、上記(変数 result)のコードの少し下のあたりも修正。

  • heroの値をテンプレート側で受け取れるようにします。万が一キービジュアルの設定がなかった時のために、ダミー画像用のコードもセットしておきます。
  • pagetype blogだけに絞り込んで出力
  • prev nextボタンもpagetype blogのみ動くように修正
gatsby-node.js
// 省略
if (posts.length > 0) {
  // filterでpagetypeがblogのものだけ抽出
  const blogPosts = posts.filter(post => post.frontmatter.pagetype === "blog")
  blogPosts.forEach((post, index) => {
    //書き換える
    const previousPostId = index === 0 ? null : blogPosts[index - 1].id    const nextPostId = index === blogPosts.length - 1 ? null : blogPosts[index + 1].id
    createPage({
      path: post.fields.slug,
      component: blogPost,
      context: {
        id: post.id,
        previousPostId,
        nextPostId,
        //↓追記
        hero: post.frontmatter.hero ? post.frontmatter.hero: "common/dummy.png",      },
    })
  })
}
// 省略

これでmd側に書かれている画像パスを参照できるようになります。

出力するテンプレートを改造し、画像を出力する。

出力するテンプレートのコードを編集します。

プロジェクト/
  └ src/
    └ templates/blog-post.js(編集)

このテンプレートでは blog/ 以下のmarkdownファイルから取得したコンテンツを出力します。

GraghQLで受け取るクエリを変更

pageQueryの設定を変更します。

$hero: String 追加し、markdownファイルから gatsby-node.js 経由で画像パスを取得します。

このように取得した値は、絞り込みなどに使えます。

blog-post.js
export const pageQuery = GraphQL`
  query BlogPostBySlug(
    $id: String!
    $previousPostId: String
    $nextPostId: String
    # ↓追加
    $hero: String  ) {
    //省略
  }

allFile にはすべてのファイル情報(mdや画像)が格納されています。 sitemarkdownRemark の間に $heroの値と一致するファイルを取得する

gatsby-image-plugin 出力するための値を取得しておきます。

blog-post.js
//省略
site {
  siteMetadata {
    title
  }
}
# ↓追加
allFile(
  filter: {
    # ↓ 画像パスが$heroと一緒のファイルを探す
    relativePath: { eq: $hero }    # ↓ 画像を格納してある場所がimages(gatsby-config.jsで指定した名)
    sourceInstanceName: { eq: "images" }  }
) {
  edges {
    node {
      relativePath
      childImageSharp {
        gatsbyImageData(
          width: 1000
          formats: [AUTO, WEBP, AVIF]
          placeholder: BLURRED
        )
      }
    }
  }
}
# ↑追加
markdownRemark(id: { eq: $id }) {
//省略

Gatsbyは WebP(ウェッピー)AVIF(エーブアイエフ) も対応しています。formats を指定しなければ、従来の画像形式(pngやjpgなど)とWebPのみで出力できます。

WebP(ウェッピー)という画像形式をご存知でしょうか? 長い間、webの静止画は大部分がJPEG/GIF/PNGのいずれかでした。WebPはこのすべてを置き換えることができる次世代のフォーマットです。2020年9月リリースのiOS 14がWebPをサポートしたことで、主要なモダンブラウザーの足並みがようやく揃いました。
次世代画像形式のWebP、そしてAVIFへ

WebPはSafariも対応したので、IEガン無視の人は十分使えます。

gatsby-image-plugin は loading Lazy 対応してあります。 placeholder は loading Lazy で画像が表示されるまでに、代わりに表示される画像です。DOMINANT_COLORがデフォルトですが、BLURRED にしておくと、表示したい画像のぼやけたものを表示してくれ、代替画像から画像実物にきりわかる時が自然です。
不要であれば NONE にしておきましょう。

placeholder: BLURRED

細かい設定(オプション)についてはこの記事の最後、おまけ・画像のオプションなどに書いてあるので、そちらも参考にしてください。

値が取れているか確認します。
必要なデータはchildImageSharpに格納されています。

blog-post.js
// 省略
const BlogPostTemplate = ({ data, location }) => {
  const post = data.markdownRemark
  const siteTitle = data.site.siteMetadata?.title || `Title`
  const { previous, next } = data
  const keyVisual = data.allFile.edges[0].node.childImageSharp;
  console.log(keyVisual) //デバッグ  // 省略
  return (
    <Layout location={location} title={siteTitle}>
  // 省略

帰ってくる値はこんな感じ。

{gatsbyImageData: {}}
gatsbyImageData:
height: 480
images:
fallback: {src: '/static/77c97667947347115d267c5dfe31556f/81bb2/eye-catch.png', srcSet: '/static/77c97667947347115d267c5dfe31556f/ce13a/dum…7667947347115d267c5dfe31556f/81bb2/eye-catch.png 640w', sizes: '(min-width: 640px) 640px, 100vw'}
sources: (2) [{}, {}]
[[Prototype]]: Object
layout: "constrained"
//省略

gatsby-plugin-imageでキービジュアルを出力

starter blog にデフォルトで入っている gatsby-plugin-image をインポートします。

blog-post.js
import * as React from "react"
import { Link, GraphQL } from "gatsby"

import Bio from "../components/bio"
import Layout from "../components/layout"
import Seo from "../components/seo"

// gatsby-plugin-image追加
import { GatsbyImage, getImage } from "gatsby-plugin-image"
// 省略

gatsby-imageからgatsby-plugin-imageへ

確かv3以降、gatsby-plugin-imageに画像の取り扱いが変わりました。出力方法も変わります。


今回、 <GatsbyImage/>でキービジュアル画像を出力するのですが、使い方は以下のように childImageSharpの値を丸ごと getImage に渡します。

<GatsbyImage
  image={getImage(childImageSharp)}
  alt={alt属性}
  key={キー}
  className={必要に応じて}
/>

では実際にテンプレートの header タグ内に実装してみます。

blog-post.js
  // 省略
  const { previous, next } = data
  const eyeCatchImg = data.allFile.edges[0].node.childImageSharp//追加
  // 省略
  return (
    // 省略
    <article
      className="blog-post"
      itemScope
      itemType="http://schema.org/Article"
    >
      <header>
        <h1 itemProp="headline">{post.frontmatter.title}</h1>
        <GatsbyImage          image={getImage(eyeCatchImg)}          alt={post.frontmatter.title}          key={post.frontmatter.title}        />        <p><time datetime={post.frontmatter.date}>{post.frontmatter.date}</time></p>
      </header>
    // 省略

出力コードはこんな感じ。何も設定しなくても、decoding="async"が付与されています。

<div data-gatsby-image-wrapper="" class="gatsby-image-wrapper gatsby-image-wrapper-constrained"><div style="max-width: 1200px; display: block;"><img alt="" role="presentation" aria-hidden="true" src="data:image/svg+xml;charset=utf-8,%3Csvg height='800' width='1200' xmlns='http://www.w3.org/2000/svg' version='1.1'%3E%3C/svg%3E" style="max-width: 100%; display: block; position: static;"></div><img aria-hidden="true" data-placeholder-image="" decoding="async" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABDCAIAAADlKTeNAAAACXBIWXMAAAsTAAALEwEAmpwYAAAgAElEQVR42mW6B5Q..." alt="" style="opacity: 0; transition: opacity 500ms linear 0s; object-fit: cover;"><picture><source type="image/avif" srcset="/static/734c25c8328e14e4d8df99abaea453a2/d0c69/eye-catch.avif 300w,
/static/734c25c8328e14e4d8df99abaea453a2/80f52/eye-catch.avif 600w,
/static/734c25c8328e14e4d8df99abaea453a2/410e0/eye-catch.avif 1200w" sizes="(min-width: 1200px) 1200px, 100vw"><source type="image/webp" srcset="/static/734c25c8328e14e4d8df99abaea453a2/9b21f/eye-catch.webp 300w,
/static/734c25c8328e14e4d8df99abaea453a2/9ff6b/eye-catch.webp 600w,
/static/734c25c8328e14e4d8df99abaea453a2/f2559/eye-catch.webp 1200w" sizes="(min-width: 1200px) 1200px, 100vw"><img width="1200" height="800" data-main-image="" sizes="(min-width: 1200px) 1200px, 100vw" decoding="async" src="/static/734c25c8328e14e4d8df99abaea453a2/ba986/eye-catch.png" srcset="/static/734c25c8328e14e4d8df99abaea453a2/f4be1/eye-catch.png 300w,
/static/734c25c8328e14e4d8df99abaea453a2/b444b/eye-catch.png 600w,
/static/734c25c8328e14e4d8df99abaea453a2/ba986/eye-catch.png 1200w" alt="テスト投稿" style="object-fit: cover; opacity: 1;"></picture><noscript></noscript></div>

ついでに記事の公開日時もtimeタグで書き直します。timeタグを使うと検索エンジンが「時」を表しているコンテンツであることを認識してくれます。

<p><time datetime={post.frontmatter.date}>{post.frontmatter.date}</time></p>
CSSを追加してないのでちょっとダサいけど出力できました。 画像

カテゴリやタグを出力

テンプレートにカテゴリを追加します。

タグやカテゴリも取得できるようにする

markdownRemarkcatetags、を追加します。

blog-post.js
//省略
markdownRemark(id: { eq: $id }) {
  id
  excerpt(pruneLength: 160)
  html
  frontmatter {
    title
    date(formatString: "MMMM DD, YYYY")
    description
    cate    tags  }
}

お好みで日付フォーマットも変更しておきます。

date(formatString: "YYYY-MM-DD")

データが取れるか確認。

blog-post.js
// 省略
const BlogPostTemplate = ({ data, location }) => {
  // 省略
  const { previous, next } = data
  const eyeCatchImg = data.allFile.edges[0].node.childImageSharp
  const { cate, tag } = data.markdownRemark.frontmatter//追記  console.log(cate, tag)//デバッグ
  return (
    <Layout location={location} title={siteTitle}>
  // 省略

出力結果は以下の通り。ちゃんとmarkdownファイルからfrontmatterのデータが取れました。

{cate: "web-developer", tags: (2) ['Gatsby', 'React']}

カテゴリの出力

まずはカテゴリのみ出力します。

blog-post.js
  //省略
  const { cate } = data.markdownRemark.frontmatter

  return (
    <Layout location={location} title={siteTitle}>
    //省略
    <header>
      <h1 itemProp="headline">{post.frontmatter.title}</h1>
      <GatsbyImage
        image={getImage(eyeCatchImg)}
        alt={post.frontmatter.title}
        key={post.frontmatter.title}
      />
      <p><time datetime={post.frontmatter.date}>{post.frontmatter.date}</p>
    </header>
    {/* カテゴリ追加 */}
    <dl>      <dt>カテゴリ</dt>      <dd>{cate}</dd>    </dl>    <section
      dangerouslySetInnerHTML={{ __html: post.html }}
      itemProp="articleBody"
    />
    //省略

タグも追加。タグは複数あるので map で出力します。

blog-post.js
  const { cate, tags } = data.markdownRemark.frontmatter
  //省略
  return (
    <Layout location={location} title={siteTitle}>
    {/* カテゴリ追加 */}
    <dl>
      <dt>カテゴリ</dt>
      <dd>{cate}</dd>
    </dl>
    {/* タグ追加 */}
    <dl>      <dt>タグ</dt>      {tags.map((tag, index) => {        return <dd key={`tag${index}`}>{tag}</dd>      })}    </dl>    {/* 省略 */}

Reactではmapのようなループ系の処理で key がないと怒られるので、必ずセットしてください。

react.development.js:220 Warning: Each child in a list should have a unique "key" prop.

今は単に文字だけを表示してますが、次のブログ記事、カテゴリ、タグ一覧の出力でカテゴリやタグの一覧ができるのでその時にリンクを付与します。

next(次の記事へ) と previous(前の記事へ)の挙動

最初から実装されている next(次の記事へ) と previous(前の記事へ)のボタンが動くか確かめます。

ユーザーには基本記事のみ(pagetype: blog)を行き来して欲しいので、ちゃんと動いているか確かめます。

entry1.mdを複製してentry2.mdを作ります。

entry2.md
---
title: 記事2
date: 2021-12-22
pagetype: blog
hero: thumbnail/eye-catch.png
description: この記事はテスト投稿です
cate: web-developer
tags: ['Gatsby', 'React']
---

元々あった「hello,world」などへのリンクがnext(次の記事へ) と previous(前の記事へ)に表示されなくなっていればOKです。

全体のスタイルをstyled-componentsで整える

前回同様、styled-componentsを使います。

インストール方法はヘッダーとフッターを追加する - せっかくなのでstyled-componentsでスタイルを入れてみるを参考にしてください。

blog-post.js
// 省略
import { GatsbyImage, getImage } from "gatsby-plugin-image"

import styled from "styled-components"// 追加
const BlogPostTemplate = ({ data, location }) => {
// 省略

styled-components用にタグを書き直します。

  1. articleからArticle
  2. 画像のラッパーを増やす
  3. 日付用のクラス追加と少しコード修正
  4. カテゴリ、タグのdlDl
  5. 記事の本文出力を内包しているsectionBlogEntry
  6. ページ送りのnavBlogPostNavにし、ulのインラインスタイルを削除
blog-post.js
  // 省略
  return (
      {/* 1 articleからArticleへ */}
      <Article
        className="blog-post"
        itemScope
        itemType="http://schema.org/Article"
      >
        <header>
          <h1 itemProp="headline">{post.frontmatter.title}</h1>
          {/* 2 画像のラッパーを増やす */}
          <div className="keyvisual">
            <GatsbyImage
              image={getImage(keyVisual)}
              alt={post.frontmatter.title}
              key={post.frontmatter.title}
            />
          </div>
          {/* 3 日付用のクラス追加 */}
          <p className="date">
            更新日:
            <time datetime={post.frontmatter.date}>
              {post.frontmatter.date}
            </time>
          </p>
        </header>
        {/* 4 dlからDlへ */}
        <Dl>
          <dt>カテゴリ</dt>
          <dd>{cate}</dd>
        </Dl>
        <Dl>
          <dt>タグ</dt>
          {tags.map((tag, index) => {
            return <dd key={`tag${index}`}>{tag}</dd>
          })}
        </Dl>
        {/* 5 sectionからBlogEntryへ */}
        <BlogEntry
          dangerouslySetInnerHTML={{ __html: post.html }}
          itemProp="articleBody"
        />
        <footer>
          <Bio />
        </footer>
      </Article>
      {/* 6 navからBlogPostNavへ*/}
      <BlogPostNav>
        <ul>
          {/*省略*/}
        </ul>
      </BlogPostNav>
    {/*省略*/}

デザインを整えます。ファイルの下の方にスタイルを追記します。

blog-post.js
// 省略
const Article = styled.article`
  max-width: 750px;
  margin: 0 auto;

  .date {
    font-weight: 700;

    time {
      font-size: 1.4rem;
    }
  }

  .keyvisual {
    text-align: center;
  }
`
const BlogEntry = styled.section`
  margin: 15px 0 30px;
  border-top: 1px solid #ccc;
  border-bottom: 1px solid #ccc;
`
const BlogPostNav = styled.nav`
  max-width: 750px;
  margin: 0 auto;

  ul {
    display: flex;
    justify-content: space-between;
    list-style: none;
  }
`
const Dl = styled.dl`
  display: flex;
  margin: 0;

  dt {
    width: 80px;
    font-weight: 700;
  }

  dd {
    font-size: 14px;
    margin-left: 0;
    padding-left: 0;

    & + dd {
      margin-left: 15px;
      margin-bottom: 5px;
    }
  }
`
少しましになった

プロフィール(bio.js)のコンポーネントを改造してみよう

プロフィール用のコンポーネントをbio.jsを改造しましょう!

プロジェクト/
  ├ gatsby-config.js(必要に応じて修正)
  └  src/
     ├ components/
     | └ bio.js(修正)
     └ images/
       └ common/profile-pic.jpg(追加)

その前に、gatsby-config.jsの基本情報が設定されている siteMetadata を確認し、必要があれば編集、追記してください。

SNSアカウント情報を足してみました。

gatsby-config.js
module.exports = {
  siteMetadata: {
    title: `銀ねこアトリエ`,
    author: {
      name: `かみーゆ`,
      summary: `セブ島に住むフロントエンドエンジニアです。`,
    },
    description: `セブ島に住む女性フロントエンドエンジニアのライフログ。フロント技術を中心とした「ウェブ制作に関するチップス」、「磨耗しない人生の選択」や「海外ノマド」のライフスタイルについて発信しています。`,
    siteUrl: `https://ginneko-atelier.com`,
    social: {
      twitter: `lirioL`,
      instagram: `yurico.k`,      youtube: `https://www.youtube.com/channel/UCbSgjkCIPucux8cFTuQcdcw`,    },
  },
}

gatsby-config.js の siteMetadata にはどのコンポネントやテンプレートからでも、アクセスできます。

アクセス方法はいろいろありますがbio.jsでは、 useStaticQuery を使って取得しています。

<StaticImage /> を使ってプロフィール画像を表示してあるので今回差し替えます。

bio.js
import * as React from "react"
import { useStaticQuery, graphql } from "gatsby"
import { StaticImage } from "gatsby-plugin-image"

import styled from "styled-components" //追加

const Bio = () => {
  const data = useStaticQuery(graphql`
    query BioQuery {
      site {
        siteMetadata {
          author {
            name
            summary
          }
          social {
            twitter
            instagram            youtube          }
        }
      }
    }
  `)

  // Set these values by editing "siteMetadata" in gatsby-config.js
  const author = data.site.siteMetadata?.author
  const social = data.site.siteMetadata?.social

  return (
    <BioWrapper>
      <h2>この記事を書いた人</h2>
      <StaticImage
        className="bio-avatar"
        layout="fixed"
        formats={["auto", "webp", "avif"]}
        {/* 画像サイズやパスを変更 */}
        src="../images/common/profile-pic.jpg"        width={100}        height={100}        quality={95}        alt="Profile picture"
      />
      {author?.name && (
        <>
          <p>
            <strong>{author.name}</strong>/{author?.summary || null}
          </p>
          <ul>
            <li>
              <a
                href={`https://twitter.com/${social?.twitter || ``}`}
                target="_blank"
                rel="noopener"
              >
                Twitter
              </a>
            </li>
            {/* insta追加 */}
            <li>              <a                href={`https://www.instagram.com/${social?.instagram || ``}`}                target="_blank"                rel="noopener"              >                Instagram              </a>            </li>            {/* youtube追加 */}
            <li>              <a                href={`${social?.youtube || ``}`}                target="_blank"                rel="noopener"              >                YouTube              </a>            </li>          </ul>
        </>
      )}
    </BioWrapper>
  )
}

export default Bio

gatsby-plugin-imageには <GatsbyImage /><StaticImage /> の画像の取得方法があります。

<StaticImage /> コンポーネントを使うとGraphQLのコードを書かずに直接画像を指定できます。

<StaticImage
  layout="fixed"
  formats={["auto", "webp", "avif"]}
  src="../images/common/camille-pic.jpg"
  width={100}
  height={100}
  quality={95}
  alt={author.name}
/>

難点は相対パス画像しか使えません。変数も使えないのでたまに不便。

// NG
const src = '../images/common/camille-pic.jpg'

return <StaticImage src={src}/>

@fortawesome/react-fontawesomeをインストールしてSNSリンクにアイコンを付与

せっかくなのでfontAwesomeを使えるようにします。

コマンド
npm i @fortawesome/free-brands-svg-icons @fortawesome/free-brands-svg-icons

以下コードを追加。

bio.js
import { StaticImage } from "gatsby-plugin-image"

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"import {  faTwitter,  faInstagram,  faYoutube,} from "@fortawesome/free-brands-svg-icons"

アイコン用のタグを追加します。

bio.js
<li>
  <a
    href={`https://twitter.com/${social?.twitter || ``}`}
    target="_blank"
    rel="noopener"
  >
    <FontAwesomeIcon icon={faTwitter} />    Twitter
  </a>
</li>
<li>
  <a
    href={`https://www.instagram.com/${social?.instagram || ``}`}
    target="_blank"
    rel="noopener"
  >
    <FontAwesomeIcon icon={faInstagram} /> Instagram  </a>
</li>
<li>
  <a
    href={`${social?.youtube || ``}`}
    target="_blank"
    rel="noopener"
  >
    <FontAwesomeIcon icon={faYoutube} />    YouTube
  </a>
</li>

styled-components でスタイルを整える

styled-componentsを使うためにタグを書き換えます。

bio.js
return (
    {/*変更*/}
    <BioWrapper>
      <h2>この記事を書いた人</h2>
      <StaticImage
        className="bio-avatar"
        layout="fixed"
        formats={["auto", "webp", "avif"]}
        src="../images/common/profile-pic.jpg"
        width={100}
        height={100}
        quality={95}
        alt="Profile picture"
      />
      {author?.name && (
        <>
          <p>
            <strong>{author.name}</strong>/{author?.summary || null}
          </p>
          {/*変更*/}
          <Sns>
            <li>
              <a
                href={`https://twitter.com/${social?.twitter || ``}`}
                target="_blank"
                rel="noopener"
                {/*クラス追加*/}
                className="tw"
              >
                <FontAwesomeIcon icon={faTwitter} />
                Twitter
              </a>
            </li>
            <li>
              <a
                href={`https://www.instagram.com/${social?.instagram || ``}`}
                target="_blank"
                rel="noopener"
                {/*クラス追加*/}
                className="insta"
              >
                <FontAwesomeIcon icon={faInstagram} /> Instagram
              </a>
            </li>
            <li>
              <a
                href={`${social?.youtube || ``}`}
                target="_blank"
                rel="noopener"
                {/*クラス追加*/}
                className="yt"
              >
                <FontAwesomeIcon icon={faYoutube} />
                YouTube
              </a>
            </li>
          </Sns>
        </>
      )}
    </BioWrapper>
  )
}

export default Bio

スタイルは下の方に追記します。

bio.js
const BioWrapper = styled.div`
  text-align:center;

  .bio-avatar {
    display: block;
    border-radius: 50%;
    margin: 0 auto;
  }
  h2 {
      font-size: 18px;
  }
`
const Sns = styled.ul`
  list-style: none;
  display: flex;
  margin: 0 0 15px;
  padding: 0;
  justify-content: center;

  li {
      margin: 0 5px;
  }
  a {
      text-decoration: none;
      display: flex;
      height: 40px;
      justify-content: center;
      align-items: center;
      border: solid 2px;
      font-weight: 700;
      border-radius: 10px;
      color: #fff;
      padding: 0 20px;;

      &.tw {
          background: #04A0F6;
      }
      &.insta {
          background: #CF2E92;
      }
      &.yt {
          background:#C4302B ;
      }
  }
  svg {
      margin-right: 10px;
  }
`

完成はこんな感じ。

完成目標

まとめ

記事詳細ページはアレンジできるようになりました!

次回は「ブログ記事、カテゴリ、タグ一覧の出力」です。

皆さんのコーディングライフの一助となれば幸いです。

最後までお読みいただきありがとうございました。

おまけ。画像のオプションなど

オプションがめちゃくちゃ細かいのですが、大まかなものだけ表にまとめました。全部書けないのでもっと詳しくみたい人は公式サイトのイメージオプションを参考にしてください。

すべてのオプション

オプションデフォ値説明
ayout“constrained” /
CONSTRAINED
リサイズの振る舞い
width/height画像サイズ
aspectRatioアスペクト比。通常は画像のアスペクト比に依存
placeholder“dominantColor”/
DOMINANT_COLOR
画像がアップされるまでの一時的な画像
formats[“auto”,“webp”]/
[AUTO,WEBP]
画像のフォーマット
transformOptions{fit: “cover”,
cropFocus: “attention”}
グレースケールなどへ変換可能
sizes自動作成フルサイズの指定が欲しい時だけ使う
quality50画像のクオリティ0 ~ 100
outputPixelDensitiesFor fixed images:[1, 2]
For constrained:[0.25, 0.5, 1, 2]
生成する画像のピクセル密度。
breakpoints[750, 1080,1366, 1920]デフォで、一般的なデバイス解像度の幅が生成。ソース画像よりも大きな画像が生成されることはない。ブラウザに応じて自動かつ適切な画像サイズに切り替える。
blurredOptionsなしプレースホルダがぼやけてない限り無視される
tracedSVGOptionsなしSVG用のプレースホルダーオプション。
jpgOptionsなしjpegを生成するときシャープにするオプション
pngOptionsなしpngを生成するときシャープにするオプション
webpOptionsなしwebpを生成するときシャープにするオプション
avifOptionsなしavifを生成するときシャープにするオプション