Gatsbyの検索エンジン最適化(SEO)の全まとめ

blog-hero-imgGatsbyサイトのSEO対策をすべてまとめます。オフィシャルサイトにほとんど書かれていない構造化データや、Netlifyでホストしている場合に見落としがちなポイントも紹介します。

pen-icon2020.11.10rewrite-icon2021.10.08

Profile Pic

この記事の筆者:三好アキ(エンジニア)


ウェブデザイナーから『エンジニア』『プログラマー』へ成長したい人、独学で進んでいきたい人を応援しています。 HTMLとCSSの知識だけでアプリ開発を始められる入門書を多数執筆中📕📗👇


ウェブ制作の教本『はじめてつくるReactアプリ』など複数冊を執筆。



React、Next.js、TypeScriptなどのお役立ち情報や実践的コンテンツを、ビギナー向けにかみ砕いて配信中。登録はこちらから → 無料メルマガ登録

Gatsbyの検索エンジン最適化

Gatsbyは当サイトでも使っている「爆速」の静的サイトジェネレーター(SSG)で、表示スピードといったパフォーマンスに関していうことはありません。

しかし、検索エンジン最適化のためにはいくつか設定しなければいけないことがあります。

今回はGatsbyサイトの検索エンジン対策として設定すべきことをまとめます。

Googleアナリティクス導入といった基本的なところから、構造化データの設定、GatsbyサイトをNetlifyでホストする時に忘れがちなredirectの設定、そしてURLについてのNetlifyのUIのバグについても解説します。

サーチコンソールに登録する

まずはベーシックなところから始めますが、サーチコンソールに登録することで、検索エンジンへの登録状況や、何回ヒットが発生しているか、どのようなキーワードで検索されているかなど、多くのことがわかります。

あとで説明するsitemapをサーチコンソールから送信して、サイトのインデックスを促すこともできます。

サーチコンソールと次項のGoogleアナリティスについて、詳しくは「4つの指標だけ見ればOK ― Googleアナリティクスの簡単な使い方」を参考にしてください。

Googleアナリティクスを入れる

プラグインもありますが、私はNetlifyのSnippet Injectionを使ってアナリティクスを読み込んでいます。

なお、2020年10月にリリースされた新しいGoogleAnalytics 4は、上のプラグインでは読み込めないケースがあるようです。

その場合は、後で紹介するSEOコンポーネント内のReact Helmetで下記のようにすれば読み込めます。

GoogleAnalytics 4のトラッキングID(測定ID)の入手はこちらを参考にしてください。

// seo.js/components/src

<Helmet>
  ...
  <script async src="https://www.googletagmanager.com/gtag/js?id=YOUR-ID"></script>
  <script>{`
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());

      gtag('config', 'YOUR-ID');
  `}</script>
</Helmet>

sitemapの設置

クローラーがサイト構造を読み取るのを助けます。

オフィシャルのプラグイン、gatsby-plugin-sitemapが用意されているので、これをインストールします。

npm install gatsby-plugin-sitemap

インストールをしたら、gatsby-config.jsの中に置くのを忘れないようにしましょう。

# gatsby-config.js

module.exports = {
    plugins: [
        `gatsby-plugin-sitemap`,
    ]
}

なお、sitemapはSearch ConsoleでもGoogleに提出できますが、下のURLからでも可能です。

https://google.com/ping?sitemap="ウェブサイトのサイトマップURL"

(例: https://google.com/ping?sitemap=https://monotein.com/sitemap.xml

robot.txtの設置

クローラーに読み取ってほしいページと読み取ってほしくないページを伝えます。

こちらもgatsby-plugin-robots-txtというプラグインが用意されているので、これを使います。

npm install --save gatsby-plugin-robots-txt

そしてsitemapと同じくgatsby-config.jsに設置します。

// gatsby-config.js

module.exports = {
    plugins: [
      `gatsby-plugin-sitemap`,
      `gatsby-plugin-robots-txt`
    ]
}

正しく設定されているかはこちらでテストできます。

SEOコンポーネントの設置

サイトの各ページの<head>内にtitle、descriptionなどのmetaタグを設定します。

プラグイン、gatsby-plugin-react-helmetを使いますが、このプラグインにはReact Helmetが必要なので、2つ一緒にインストールします。

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

次にsrcのcomponentsフォルダーなどにSEOコンポーネントを作りましょう。

GatsbyのオフィシャルサイトにあるSEOコンポーネントはURL正規化など重要なことが書かれていませんが、ベースとしては使えるので、これを参考にします。

// seo.js/components/src

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

const SEO = ({ title, description, image, article }) => {
  const { pathname } = useLocation()
  const { site } = useStaticQuery(query)

  const {
    defaultTitle,
    titleTemplate,
    defaultDescription,
    siteUrl,
    defaultImage,
    twitterUsername,
  } = site.siteMetadata

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

  return (
    <Helmet title={seo.title} titleTemplate={titleTemplate}>
      <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

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

SEO.defaultProps = {
  title: null,
  description: null,
  image: null,
  article: false,
}

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

これを各ページ(もしくはLayout.jsなど)で読み込ませます。

SEOに渡すpropsは適宜、調節してください。

// blogTemplate.js/templates/src

const BlogTemplate =({ data })=>{
    const { title, description, keywords, featuredImage } = data.markdownRemark.frontmatter;
    return (
        <Layout>
        <Seo title={title} 
             description={description}
             keywords={keywords}  
             featuredImage={featuredImage.childImageSharp.resize.src} 
        />
        ...

なお、Gatsbyのサイトはテンプレートも含め英語で作られている場合が多く、SEOコンポーネント内のlangタグもenとなっているので、enからjaへ変更しておきましょう。

// seo.js/components/src

<Helmet>
    <html lang="ja"/>
    ...
</Helmet>

URL正規化

URL正規化の必要性についてはこちらの記事などを参考にしてください。

URL正規化のためのプラグインもありますが、当サイトのような静的サイトの場合は、SEOコンポーネントのuseLocation()でpathを取得して渡せば対応可能です。

// seo.js/components/src

const SEO = () => {
  const { pathname } = useLocation();
  
  const {
      siteUrl,
  } = site.siteMetadata

  const seo = {
      canonical: `${siteUrl}${pathname}`
  }

  return (
        <Helmet>
            <html lang="ja"/>
            {seo.canonical && <link rel="canonical" href={seo.canonical} />}
            ...
        </Helmet>

構造化データの設置

構造化データとは、JSON-LD(JSON for Linked Data)形式で書かれたもので、SEOコンポーネントで設定した各種metaタグよりも細かな情報をGoogleのクローラーに伝えます。

そのデータは「リッチリザルト」というものに使われ、検索結果のトップで次のように表示されます。

rich-result.jpg

Gatsbyオフィシャルページでは簡単にしか触れられていませんが、今回はこの構造化データ用のコンポーネントを上述のSEOコンポーネントとは別に作り、それをページに読み込ませます。

下のコードをコピーすれば、そのまま使えます。

参考1:"Article"分類について

参考2: コンテンツ分類について

// jsonLd.js/components/src

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

const JsonLd = ({ title, description, featuredImage, date, update }) => {
  const { pathname } = useLocation();
  const { site } = useStaticQuery(query);
  
  const {
    siteUrl,
    defaultImage,
  } = site.siteMetadata;

  const jsonLdStructuredData = {
      "@context": "https://schema.org/", 
      "@type": "BlogPosting",
      mainEntityOfPage: {
        "@type": "WebPage",
        "@id": siteUrl + "/blog",  // 適宜調節してください。
      },
      author: {
        "@type": "Person",
        name: "monotein",
        url:  siteUrl,
        image: "https://monotein.com/assets/index.png",  // 適宜調節してください。
      },
      publisher: {
        "@type": "Organization",
        name: "monotein",
        url: siteUrl,
        logo: {
            "@type": "ImageObject",
            url: "https://monotein.com/assets/index.png", // 適宜調節してください。
            width: 300,
            height: 300
        }
      },
      headline: title,
      image: featuredImage ? featuredImage : defaultImage,
      url: `${siteUrl}${pathname}`,
      description: description,
      datePublished: date,
      dateCreated: date,
      dateModified: update,
  }

  return (
      <Helmet>
        <script type="application/ld+json">{JSON.stringify(jsonLdStructuredData)}</script>
      </Helmet>
  );
};

export default JsonLd;

const query = graphql`
  query Metadata {
    site {
      siteMetadata {
        siteUrl
        defaultImage
      }
    }
  }
`

これをページで読み込ませます。

先ほどのSEOコンポーネントと同じく、ブログのテンプレートに読み込ませています。

// blogTemplate.js/templates/src

const BlogTemplate =({ data })=>{
    const { title, description, keywords, featuredImage } = data.markdownRemark.frontmatter;
    return (
        <Layout>
          <Seo
          ...
          />
          <JsonLd title={title} 
                  description={description}
                  featuredImage={featuredImage.childImageSharp.resize.src} 
                  date={date} 
                  update={update}
          />
          ...

この構造化データを使ったリッチリザルトは、下のGoogleのサイトでテストできます。

https://search.google.com/test/rich-results

redirectの設定(Netlify)

GatsbyサイトをNetlifyでデプロイする人は多いですが、もしカスタムドメインを使う場合にはredirectの設定が必要になります。

これを忘れている人は多いようで、私自身も初期の頃はredirectの設定をしておらず、検索ランキングで大きくペナルティを受けていました。

これはカスタムドメインを設定しても、Netlifyが用意してくれる元のURL(xxx.netlify.appなど)でもアクセスができるので、重複コンテンツと見なされる可能性が高まるからです。

この解決のためには、staticフォルダ(もし作っていない場合はルートディレクトリに作成してください)内に_redirectファイルを作り、次のように書きます。

# Netlify's Default domain / Custom domain / Status code
https://xxxxx.netlify.app/*  https://www.custom-domain.com/:splat 301!

これでbuild時、publicフォルダ内に_redirectファイルが作成されます。

_redirectファイルの書き方が正しいかはこちらでチェックできます。

テンプレートファイルの移動

ブログで使うテンプレート(blogTemplate.js)などがpages/src内にあると、build時にこれも一つのページ(例:https://xxx.com/blogTemplate)として書き出されます。

しかしこれはMarkdown等からのデータを書き込むテンプレートなので、このURLにアクセスしても中身は空です。

特にこれがSEOに悪影響を及ぼすことはないようですが、空のページがあるのは気持ちが良くないので削除します。

まずはblogTemplate.jsなどの不要なファイルがページとして書き出されていないかを、publicフォルダ内を見るか、もしくはsitemap.xmlを開いてチェックします。

もし不要なテンプレートがページとして設定されている場合、pages/srcからcomponents/srcなどにtemplateBlog.jsファイルを移しましょう。

こうすることでbuild時、不要なファイルからページが生成されなくなります。

URL末尾のスラッシュの統一

Gatsbyで作ったサイトのURL末尾のスラッシュ(trailing slash)が引きおこす問題について、英語だと様々な情報が出てきますが、日本語ではほとんど見つかりません。

GoogleはスラッシュのついているURL(...blog/gatsby-seo/)とついていないURL(...blog/gatsby-seo)を別のものとして扱うため、サイト内のpathやURLはスラッシュを付けるか付けないかを統一することが重要です。

まずはURLのスラッシュの状況を、publicフォルダ内のsitemap.xmlで確認してみましょう。

次にredirectが働いているかを調べます。

スラッシュ付きのURLと付いていないURLをブラウザに打ち込んでください。

スラッシュを付けているURLを入力したのに、スラッシュなしのURLに変わった場合はredirectが正常に働いています。

しかし、スラッシュを付けていないURLと付けているURL、それぞれでページが表示された場合は、同じページが違うURLで存在することになりSEO上問題なので、どちらかで統一するようにします。

末尾のスラッシュを取り除くgatsby-plugin-remove-trailing-slashesのようなプラグインもあります。

しかしながらこちらのブログで書かれているように、このプラグインも万能ではなく、また次で触れるように(Netlifyでホストしている場合に)末尾のスラッシュには多くの人が困っているようで、日本語のネット空間でなぜこの問題がほとんど出ていないのか不思議です。

参考記事: https://bloggingfordevs.com

サーチコンソールに出る「除外」への対応(NetlifyのUIのバグについて)

私が以前作ったあるGatsbyのサイトでは、上のスラッシュのせいでリダイレクトが発生し、サーチコンソールに大量の「除外」が出ていました。

サイトのpathを精査してスラッシュをすべて取り除いたり、gatsby-plugin-remove-trailing-slashesのプラグインを入れたりしたものの、なぜか効果はなく、ほとんどお手上げ状態となってしまったのですが、それでも色々と調べてみるとこちらのようなGitHubのissueを見つけ、真の問題はNetlifyにあったことがわかりました。

NetlifyのBuild & deplyにあるAsset optimizationの項目はUIが非常にわかりにくく(GitHubではbugとされています)、私はすべてオフにしていたのですが、実はオフになっていなかったようです。

わかりやすいよう下にスクリーンショットを載せます。


正しくないDisabled

Incorrect-Netlify-asset-optimization

正しいDisabled

Correct-Netlify-asset-optimization

Gatsbyに興味のある人は次の記事も参考にしてください。