メタデータについて

メタデータは検索エンジンにサイト、ページの内容を伝える上で重要な役割を持っているデータです。HTMLのhead部分に記述し、具体的にはcharset, titleやdescriptionなどページに関する情報を持っています。またGatsbyはパフォーマンスを売りにしているので適切な検索ワードで上位表示させページ閲覧のためにクリックしてもらうためには正しいメタタグを設定する必要があります。

Gatsbyのデフォルトではページにはtitleやdescription情報も含まれていないので追加で設定を行う必要があります。Gatsbyにはメタデータに関するプラグインも存在するためプラグインを利用することで簡単に設定を行うことができます。

コードが複雑にならないようだれもが馴染み深いtitle, descriptionに焦点を当てて説明を行っていきます。他のメタタグ、メタデータも同様の方法で設定可能です。

プラグインのインストール

Gatsbyのヘッダーにメタデータを表示させるためには、React Helmetとreact helmet pluginのインストールを行います。


% npm install gatsby-plugin-react-helmet react-helmet

インストール完了後、gatsby-config.jsにインストールしたプラグインの情報を追加します。下記ではgatsby-plugin-react-helmetしか表示されていませんが各環境によって他のプラグインの情報もgatsby-config.jsの中で設定されているので削除しないように追加に注意してください。


module.exports = {
  /* Your site config here */
  plugins: [
    `gatsby-plugin-react-helmet`,
  ],
}

インストールしたreact-helmetを利用するためにimportを行います。

LayOutコンポーネントなどを利用している場合はその下にHelmetタグを追加ししてそのタグの中にメタデータを追加します。


import { Helmet } from "react-helmet"

export default ({ data }) => (
  <Layout>
    <Helmet>
      <html lang="ja" />
      <title>Aboutページ</title>
      <meta name="description" content="このページはaboutページです。" />
    </Helmet>

設定後はデベロッパーツールを利用してElementタブを確認すると追加したtitleタグとmetaタグのdescriptionを確認することができます。またhtmlタグにはlangのjaが設定されていることが確認できます。titleはブラウザのタグにも表示されます。

react helmetの設定
react helmetの設定

html, title, metaのタグを設定しましたがそれ以外のタグも設定できるか気になるところです。React HelmetのGitHubページでは、title, base, meta, link, script, noscript, and styleタグとbody, html and title タグの属性をサポートしていると記述されています。

Gatsbyの公式ドキュメントにもAdding an SEO Componentページでメタデータに関するコードが記述されていますが、入門者の人にはわかりにくい箇所もあると思うので以下ではStep By Stepで設定を行い理解を深め、読み終えた時にはAddign an SEO Componentも理解することができます。

gatsby-config.jsからのメタデータ取得

プラグインの設定などGatsbyの各種設定を行うgatsby-config.jsファイルにメタデータを保存することができます。gatsby-config.jsファイルにメタデータを保存することでGraphQLを使いサイト内のどのページからも追加したメタデータを取得することができます。つまり一箇所で一元管理することができます。

gastby-config.jsファイルにサイトのtitleとdescriptionを追加します。


module.exports = {
  siteMetadata: {
    title: "はじめてのGatsby Site",
    description: "Gatsbyでメタデータを設定するための手順を公開しているサイトです。"
  },
// 略

先ほどはabout.jsページのHelmetタグの中でtitle, descriptionを入力していましたが今回はgatsby-config.jsファイルから取得した情報を設定します。

about.js内でGraphQLのpage queryでgatsby-config.jsファイルにアクセスします。queryの結果はdataから取得することができ、data.site.siteMetadata.titleではtitle, data.site.siteMetadata.descriptionではdescriptionを取り出しています。取り出した値をmetaタグに設定を行っています。


import React from "react"
import Layout from "../components/layout"
import { Helmet } from "react-helmet"
import { graphql } from "gatsby"

const About = ({ data }) => {
  return (
    <div>
      <Layout>
        <Helmet>
          <html lang="ja" />
          <title>{data.site.siteMetadata.title}</title>
          <meta
            name="description"
            content={data.site.siteMetadata.description}
          />
        </Helmet>
        <h1>About page</h1>
      </Layout>
    </div>
  )
}

export const query = graphql`
  query {
    site {
      siteMetadata {
        title
        description
      }
    }
  }
`
export default About

gatsby-config.jsを更新したらgatsby developの再実行を行ってください。ブラウザで確認するとgatsby-config.jsファイルから取得したsiteMetadataが設定されていることが確認できます。

sitemetadataから取得した値を設定
sitemetadataから取得した値を設定

gatsby-config.jsからsitemetadataを取得することができましたが下記の2つの疑問がすぐに思い浮かぶと思います。

  1. 他のページでtitleやdescriptionを設定したい場合は各ページでqueryのコードを記述するのですか?
  2. aboutページでtitleとdescriptionを設定していますが、それらの値はaboutページのものではありません。ページ毎に異なる値を設定できるのですか?

まず1の疑問に対応するためメタタグ用のコンポーネントを追加します。

メタタグ用のコンポーネントの追加

Gatsbyの公式ドキュメントを参考にメタタグ用のコンポーネントの名前はseo.jsとしてsrc¥compnentsの下に保存します。ファイル名はコンポート名は任意です。

about.jsのようにseo.jsファイルはページではなくコンポーネントファイルなのでpage queryではなくuseStaticQueryを利用します。分割代入を利用しているのでuseStaticQueryの結果はsiteから取得できます。これでSEOコンポーネントとしてメタタグ用のコードを分けることができました。


import React from "react"
import { Helmet } from "react-helmet"
import { graphql } from "gatsby"

const SEO = () => {
  const { site } = useStaticQuery(query)
  return (
    <Helmet>
      <html lang="ja" />
      <title>{site.siteMetadata.title}</title>
      <meta name="description" content={site.siteMetadata.description} />
    </Helmet>
  )
}

export default SEO

const query = graphql`
  query {
    site {
      siteMetadata {
        title
        description
      }
    }
  }
`

作成したSEOコンポーネントはabout.jsファイル上ではimportして利用します。page queryの役割はSEOコンポーネント内で行われるのでpage queryは削除できます。page queryを削除したので先ほどよりもabout.jsファイルはすっきりとしました。


import React from "react"
import Layout from "../components/layout"
import SEO from "../components/seo"

const About = () => {
  return (
    <div>
      <Layout>
        <SEO />
        <h1>About page</h1>
      </Layout>
    </div>
  )
}

export default About

ブラウザで確認しても結果は先ほどと同じです。

seo.jsについては結果は変わりませんが、下記のように書き換えを行います。queryから取得するtitleとdescriptionの名前をdefaultTitieとdefaultDescriptionに変更します。siteMetadataを分割代入を利用してdefaultTitleとdefaultDescription変数に入れます。

分割代入を利用は必須ではありませんが、他のコードでも各所で利用されているので慣れていない人は使用しながら慣れていきましょう。titleとdescripionの名前を変更したのは次の章で説明するpropsの名前と重複を避けるためです。名前を変更することも分割代入を利用することも必須ではありません。それらを利用しなくても設定は可能です。

import React from "react"
import { Helmet } from "react-helmet"
import { useStaticQuery,graphql } from "gatsby"

const SEO = () => {
  const { site } = useStaticQuery(query)

  const { defaultTitle, defaultDescription } = site.siteMetadata

  return (
    <Helmet>
      <html lang="ja" />
      <title>{defaultTitle}</title>
      <meta name="description" content={defaultDescription} />
    </Helmet>
  )
}

export default SEO

const query = graphql`
  query {
    site {
      siteMetadata {
        defaultTitle: title
        defaultDescription: description
      }
    }
  }
`

SEOコンポーネントは再利用可能なので他のページでも利用することができます。メタタグを利用したいページはseo.jsをimportすることでメタタグを設定することができます。

質問の一つである”他のページでtitleやdescriptionを設定したい場合各ページでqueryのコードを記述するのですか?”はSEOコンポーネントで解決することができました。

ページ毎に異なるメタデータを設定したい

ここでは2つ目の質問である”aboutページでtitleとdescriptionを設定していますが、それらの値はaboutページのものではありません。ページ毎に異なる値を設定できるのですか?”について対応していきます。

各ページで設定したい値についてはpropsを利用します。propsを利用することでSEOコンポーネントを利用している親コンポーネントから子コンポーネントにtitleやdescriptionの値を渡すことができます。

aboutページからtitleとdescriptionを渡したい場合は下記のように記述します。


<SEO
  title="aboutページ"
  description="これは初めてGatsby Siteのaboutページです。"
/>

渡したpropsの値がseo.jsコンポーネントで問題なく渡せているか確認を行います。SEOコンポーネントに渡されたpropsは分割代入を利用してtitleとdescriptionに設定されます。


import React from "react"
import { Helmet } from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"

const SEO = ({ title, description }) => {
  const { site } = useStaticQuery(query)
  const { defaultTitle, defaultDescription } = site.siteMetadata

  return (
    <Helmet>
      <html lang="ja" />
      <title>{title}</title>
      <meta name="description" content={description} />
    </Helmet>
  )
}

export default SEO

const query = graphql`
  query {
    site {
      siteMetadata {
        defaultTitle: title
        defaultDescription: description
      }
    }
  }
`
gatsby develeopのログにはXXX is assigned a value but never usersのWARNINGが複数表示されますが動作確認なので無視してください。

ブラウザで確認するとSEOコンポーネントに渡したtitleとdescriptionの値が設定されていることが確認できます。

descriptionのcontentには”これは初めてGatsby Siteのaboutページです。”と設定されています。これはabout.jsのSEOコンポーネントで渡した値です。

propsで渡したtitleとdescriptionが表示される
propsで渡したtitleとdescriptionが表示される

propsを利用することでページ毎にtitle, descriptionを設定できることが確認できました。

propsを使うのかgatsby-config.jsの値を利用するのかどうするのかと疑問が新たに湧くと思います。対処方法としてpropsが渡されている場合には渡されたpropsを利用し、propsが渡されない場合はgatsby-config.jsに設定されているsitemetadataをできるように論理演算子を利用してコードを作成します。

下記の論理演算子では、titleに値があればtitleが設定され、なければdefaultTitleがtitleに設定されます。


title: title || defaultTitle

論理演算子を利用したコードを活用することで下記のようにseo.jsファイルを更新することができます。新たにseoをいうオブジェクトの変数を追加しています。


import React from "react"
import { Helmet } from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"

const SEO = ({ title, description }) => {
  const { site } = useStaticQuery(query)
  const { defaultTitle, defaultDescription } = site.siteMetadata

  const seo = {
    title: title || defaultTitle,
    description: description || defaultDescription,
  }

  return (
    <Helmet>
      <html lang="ja" />
      <title>{seo.title}</title>
      <meta name="description" content={seo.description} />
    </Helmet>
  )
}

export default SEO

const query = graphql`
  query {
    site {
      siteMetadata {
        defaultTitle: title
        defaultDescription: description
      }
    }
  }
`

動作確認はabout.jsファイルでSEOコンポーネントにpropsを渡すか渡さないかで確認を行うことができます。

コメントアウトした場合としない場合はどのような変化があるかデベロッパーツールのElementsタグを利用して確認してください。


//コメントあり(propsを渡さない場合) siteMetadataの値が表示される。
<SEO
// title="aboutページ"
// description="これは初めてGatsby Siteのaboutページです。"
/>

//コメントない(propsを渡した場合) propsで渡した値が表示される。
<SEO
  title="aboutページ"
  description="これは初めてGatsby Siteのaboutページです。"
/>

ここまでの設定で2つ目の質問である”aboutページでtitleとdescriptionを設定していますが、それらの値はaboutページのものではありません。ページ毎に異なる値を設定できるのですか?”に対しての解決することができました。さらにページ毎に設定したい値がない場合はgatsby-config.jsのsiteMetadataの値をデフォルト値として利用することが可能となりました。

メタデータの設定(カスタマイズ編)

ここまでの説明でメタデータの設定方法については理解することができました。ここでは少しメタデータのカスタマイズを行っていきたいと思います。

タイトルのカスタマイズ

本ブログでもそうですが、タイトルは下記のように”|”を使ってページのタイトルとサイト名を使っているサイトも多いかと思います。

もう迷わないGatsby SEO メタタグの設定方法 | アールエフェクト

ページのtitleについてはprops、サイトの情報についてはgatsby-config.jsから取得すれば上記の設定を行うことができます。


const seo = {
  title: title ? `${title} | ${defaultTitle}` : defaultTitle,
  description: description || defaultDescription,
}

descriptionでは論理演算子を利用していますが、titleでは三項演算子を利用しています。titleが値を持っている場合は、${title} | ${defaultTitle}、値がない場合はdefaultTitleとなります。

titleの設定値が<title>aboutページ | はじめてのGatsby Site</title>になっていることが確認できます。

タイトルにサイト名を入れる
タイトルにサイト名を入れる

OGPの設定

OGPの設定方法について確認していきます。これまでに学んだことを利用するだけなのでOGPの項目に抜けがあった場合も自分で設定できるはずです。

gatsbyの公式ドキュメントを参考に設定を行い、最終的には下記のタグが設定されることになります。

OGP設定
OGP設定

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="note" content="environment=development">
    <title>aboutページ | はじめてのGatsby Site</title>
    <script src="/socket.io/socket.io.js"></script>
    <link type="text/css" rel="stylesheet" href="blob:http://localhost:8000/649eef07-012b-4f2f-89e6-68c3817e3120">
    <meta name="description" content="これは初めてGatsby Siteのaboutページです。" data-react-helmet="true">
    <meta name="image" content="https://www.reffect.co.jp/XXX.jpg" data-react-helmet="true">
    <meta property="og:url" content="https://www.reffect.co.jp/about/" data-react-helmet="true">
    <meta property="og:title" content="aboutページ | はじめてのGatsby Site" data-react-helmet="true">
    <meta property="og:description" content="これは初めてGatsby Siteのaboutページです。" data-react-helmet="true">
    <meta property="og:image" content="https://www.reffect.co.jp/XXX.jpg" data-react-helmet="true">
    <meta name="twitter:card" content="summary_large_image" data-react-helmet="true">
    <meta name="twitter:creator" content="reffect2020" data-react-helmet="true">
    <meta name="twitter:title" content="aboutページ | はじめてのGatsby Site" data-react-helmet="true">
    <meta name="twitter:description" content="これは初めてGatsby Siteのaboutページです。" data-react-helmet="true">
    <meta name="twitter:image" content="https://www.reffect.co.jp/XXX.jpg" data-react-helmet="true">
</head>

sitemetadataの追加

titleとdescription以外に新たににimage, url, twitterUsernameを追加します。すべての値について各自の環境に合わせて設定を行ってください。twitterUsernameなど必要のない項目は削除しても問題ありません。また必要なものがあれば追加を行ってください。


module.exports = {
  siteMetadata: {
    title: "はじめてのGatsby Site",
    description:
      "Gatsbyでメタデータを設定するための手順を公開しているサイトです。",
    image: "/XXX.jpg",
    url: "https://www.reffect.co.jp",
    twitterUsername: "reffect2020",
  },

gatsby-config.jsを更新したら忘れずにgatsby developを再実行してください。

seo.jsファイルの更新

gatsby-config.jsファイルで増やした項目についてはqueryの取得項目に追加を行う必要があります。


const query = graphql`
  query {
    site {
      siteMetadata {
        defaultTitle: title
        defaultDescription: description
        siteUrl: url
        defaultImage: image
        twitterUsername
      }
    }
  }
`

ユーザがアクセスしているページのurl情報を取得するためにuserLocationをインポートして、pathnameを取得します。about.jsファイルではpathnameは”/about”となります。

設定する項目が増えてのでpropsから渡される値も増えます。各変数は下記のように設定を行います。


import React from "react"
import { Helmet } from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"
import { useLocation } from "@reach/router"

const SEO = ({ title, description, image, article }) => {
  const { site } = useStaticQuery(query)
  const {
    defaultTitle,
    defaultDescription,
    siteUrl,
    defaultImage,
    twitterUsername,
  } = site.siteMetadata
  const { pathname } = useLocation()

  const seo = {
    title: title ? `${title} | ${defaultTitle}` : defaultTitle,
    description: description || defaultDescription,
    image: `${siteUrl}${image || defaultImage}`,
    url: `${siteUrl}${pathname}`,
  }
  //略

最後に追加した変数をmetaタグを使って設定を行います。ここでも論理演算子を利用します。

下記の論理演算子では、seo.titleに値があれば<meta property=”og:url” content={seo.url} />が返され、seo.titleが値がなければseo.titleを返します。しかしこの場合はseo.titleには何も値がないので何も返しません。


{seo.title && &lt;meta property="og:title" content={seo.title} /&gt;}

論理演算子を利用して下記のように設定を行います。


<Helmet>
    <html lang="ja" />
    <title>{seo.title}</title>
    <meta name="description" content={seo.description} />
    <meta name="image" content={seo.image} />
    {seo.url && <meta property="og:url" content={seo.url} />}
    {(article ? true : null) && <meta property="og:type" content="article" />}
    {seo.title && <meta property="og:title" content={seo.title} />}
    {seo.description && (
    <meta property="og:description" content={seo.description} />
    )}
    {seo.image && <meta property="og:image" content={seo.image} />}
    <meta name="twitter:card" content="summary_large_image" />
    {twitterUsername && (
    <meta name="twitter:creator" content={twitterUsername} />
    )}
    {seo.title && <meta name="twitter:title" content={seo.title} />}
    {seo.description && (
    <meta name="twitter:description" content={seo.description} />
    )}
    {seo.image && <meta name="twitter:image" content={seo.image} />}
</Helmet>

propsTypesの設定

propsで4つの値が渡されていますがこれを見ただけではarticleに何を入れればいいかわかりません。そのためにpropsで渡される型についての情報を追加します。


SEO.propTypes = {
  title: PropTypes.string,
  description: PropTypes.string,
  image: PropTypes.string,
  article: PropTypes.bool,
}

これでarticleにはbool値を設定しなければならないことがわかります。

propTypesを利用する場合は、propTypesをimportする必要があります。


import PropTypes from "prop-types"

about.jsファイルからarticleの値を設定して動作確認を行います。


<SEO
  title="aboutページ"
  description="これは初めてGatsby Siteのaboutページです。"
  article={true}
/>

articleはbool値なのでtrueかfalseを設定します。その時{}で設定を行います。もしarticle=”true”とすると文字列が渡されたと判断され、コンソールログにエラーメッセージが表示されます。propTypesでチェックが行われていることがわかります。

Warning: Failed prop type: Invalid prop `article` of type `string` supplied to `SEO`, expected `boolean`.

最終的なseo.jsファイルのコードは下記となります。


mport React from "react"
import { Helmet } from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"
import { useLocation } from "@reach/router"
import PropTypes from "prop-types"

const SEO = ({ title, description, image, article }) => {
  const { site } = useStaticQuery(query)
  const {
    defaultTitle,
    defaultDescription,
    siteUrl,
    defaultImage,
    twitterUsername,
  } = site.siteMetadata
  const { pathname } = useLocation()

  const seo = {
    title: title ? `${title} | ${defaultTitle}` : defaultTitle,
    description: description || defaultDescription,
    image: `${siteUrl}${image || defaultImage}`,
    url: `${siteUrl}${pathname}`,
  }

  return (
    <Helmet>
      <html lang="ja" />
      <title>{seo.title}</title>
      <meta name="description" content={seo.description} />
      <meta name="image" content={seo.image} />
      {seo.url && <meta property="og:url" content={seo.url} />}
      {(article ? true : null) && <meta property="og:type" content="article" />}
      {seo.title && <meta property="og:title" content={seo.title} />}
      {seo.description && (
        <meta property="og:description" content={seo.description} />
      )}
      {seo.image && <meta property="og:image" content={seo.image} />}
      <meta name="twitter:card" content="summary_large_image" />
      {twitterUsername && (
        <meta name="twitter:creator" content={twitterUsername} />
      )}
      {seo.title && <meta name="twitter:title" content={seo.title} />}
      {seo.description && (
        <meta name="twitter:description" content={seo.description} />
      )}
      {seo.image && <meta name="twitter:image" content={seo.image} />}
    </Helmet>
  )
}

export default SEO

const query = graphql`
  query {
    site {
      siteMetadata {
        defaultTitle: title
        defaultDescription: description
        siteUrl: url
        defaultImage: image
        twitterUsername
      }
    }
  }
`

SEO.propTypes = {
  title: PropTypes.string,
  description: PropTypes.string,
  image: PropTypes.string,
  article: PropTypes.bool,
}