WPからGatsbyへ移行時に気を付けたいSEO対策一覧と導入方法

9

目次

概要

WordPress から Gatby に移行するにあたり、重要なものの一つが SEO 対策です。通常、Gatsby では Helmet を導入せずにビルドするとtitleタグやmetaタグなどは書き出されません。

しかし、Gatsby にはHelmetという便利なプラグインが用意されており、インストールするだけでひとまずの SEO 対策ができちゃいます

もちろんそれだけでは足りないので本記事執筆に至ったわけですが……

まずは Helmet のインポート

多くの方が Gatsby ブログの構築に何らかの starter を使用しており、その場合は必ずと言っていいほど Helmet がすでにインストールされているかと思います。packages.jsonファイル内にてgatsby-plugin-react-helmetと検索してみてください。

ヒットしていたら Helmet が使えるので、blog の template ファイルや layout コンポーネント等、各ファイルでimportしておきましょう。

Layout.js
import Helmet from "react-helmet";

必要な SEO 対策一覧

SEO 対策として最低限必要なものを以下にまとめました。

  • 適切なマークアップ(h1 はひとつ)
  • title、meta タグ(description など)
  • canonical(正しい URL を検索エンジンに伝える)
  • 構造化データ(クローラーの最適化)
  • robot.txt の設置(クローラーの最適化)
  • Sitemap の生成(クローラーの最適化)
  • Search コンソールでの登録

国産 WordPress テーマではこれらを自動的に出力してくれるようにしているものが多いです(Sango や Affinger、Jin などなど)。

これらが SEO 的に効果があるだとかないだとか必須だとかはここでは言及しません。あくまで Audits での SEO 項目が 100 点になる+α のために必要なものを書き出しました。

あとは画像にはalt属性つけるだとかinputにはlabelだとか重箱の隅を突くようなものも多いですが、ここでは上で挙げた最低限のものをカバーできれば良しとしましょう 🙆

適切なマークアップ

トップページではタイトルにh1を、それ以外のページではその他の項目にh1を設定します(記事ページではタイトルなど)。トップページかどうかの判断にはlocationを使いましょう。

location について

Layout コンポーネントで header を呼び出す際に、

Layout.js
// ご自身の環境に合わせて適宜変更してください

class Layout extends React.Component {
  render() {
    const { title, location, children } = this.props;
    return (
      <div>
        <Header title={title} location={location} />        {children}
        <Footer />
      </div>
    );
  }
}

とし、header に location を渡してあげて、

Header.js
// ご自身の環境に合わせて適宜変更してください

const Header = ({ titile, location }) => {
  rootPath = `${__PATH_PREFIX__}/`;

  let headerTitle;
  if (location.path === rootPath) {
    headerTitle = <h1>{title}</h1>;
  } else {
    headerTitle = <h3>{title}</h3>;
  }

  return (
    <HeaderTag>
      <Link to="/">{headerTitle}</Link>
    </HeaderTag>
  );
};

こうすればロゴ(サイトタイトル)部分はトップページではh1で出力され、それ以外ではh3として出力されます 🎉

あとは記事テンプレート側でタイトルをh1として出力してあげましょう。

title、meta タグ

こちらは、src内に SEO コンポーネントを新しく作成するのがいいかと思います。

Seo.js
import React from "react";
import PropTypes from "prop-types";
import Helmet from "react-helmet";
import { StaticQuery, graphql } from "gatsby";

function SEO({ lang, title, meta, description, keywords }) {
  return (
    <StaticQuery
      query={detailsQuery}
      render={(data) => {
        const metaDescription =
          description || data.site.siteMetadata.description; //descriptionが設定されていなければサイトのdescriptionが表示される
        return (
          <Helmet
            htmlAttributes={{
              lang,
            }}
            title={title}
            titleTemplate={`%s | ${data.site.siteMetadata.title}`}
            meta={[
              {
                name: `description`,
                content: metaDescription,
              },
              {
                property: `og:title`,
                content: title,
              },
              {
                property: `og:description`,
                content: metaDescription,
              },
              {
                property: `og:type`,
                content: `website`,
              },
              {
                name: `twitter:card`,
                content: `summary`,
              },
              {
                name: `twitter:creator`,
                content: data.site.siteMetadata.author,
              },
              {
                name: `twitter:title`,
                content: title,
              },
              {
                name: `twitter:description`,
                content: metaDescription,
              },
            ]
              .concat(
                keywords.length > 0 //キーワードが設定されている時のみ表示
                  ? {
                      name: `keywords`,
                      content: keywords.join(`, `),
                    }
                  : []
              )
              .concat(meta)}
          />
        );
      }}
    />
  );
}

SEO.defaultProps = {
  lang: `ja`,
  meta: [],
  keywords: [],
};

SEO.propTypes = {
  description: PropTypes.string,
  lang: PropTypes.string,
  meta: PropTypes.array,
  keywords: PropTypes.arrayOf(PropTypes.string),
  title: PropTypes.string.isRequired,
};

export default SEO;

const detailsQuery = graphql`
  query DefaultSEOQuery {
    site {
      siteMetadata {
        title
        description
        author
      }
    }
  }
`;

siteMetadata 内に author がない場合は、gatsby-configファイルから追加できます。あとは記事のテンプレートやindex.jsなどから呼び出せば OK。Seo コンポーネントの import を忘れずに

blogTemplate.js
import Seo from 'src/components/Seo';
// 略
class BlogPostTemplate extends React.Component {
  render() {
    const { markdownRemark } = this.props.data;
    const { frontmatter, html, parent } = markdownRemark;
    const siteTitle = this.props.data.site.siteMetadata.title;
    const siteUrl = this.props.data.site.siteMetadata.siteUrl;

    return (
      <Layout title={ siteTitle } location={ this.props.location }>        <Seo           title={ frontmatter.title }           keywords={ frontmatter.keywords }           description={ frontmatter.metaDescription }
        />
// 略
    );
  };
};

ちなみに僕は keyword をいちいち設定するのが面倒なのでタグとして登録したものを keywords としてしまっています。こんな感じ(keywords={ frontmatter.tags })ですね。

あとは記事を書くときに、

hoge.md
---
template: BlogPost
path: /hoge
date: 2020-04-22T00:00:00.000Z
title: 記事タイトル。h1として出力されます
metaDescription: metadescriptionに使用されます
tags: ["僕の場合は", "ここを", "keywords", "として", "使っています"]
keywords: ["その他の人は", "こんな感じ", "ですね"]
---

こんな感じで書き出せば OK ですね。

canonical の設定

これはHelmetを使用します。

index.js
// 略
    return(
      <Layout title={ siteTitle } location={ this.props.location }>
        <Seo
           title="新着記事一覧"
           keywords={["meta keywordです"]}
        />
        <Helmet>          <link rel="canonical" href={ siteUrl } />        </Helmet>

記事テンプレ側でも設定しましょう!こちらでも Helmet の import をお忘れなくー!

blogTemplate.js
// 略
    return (
      <Layout title={ siteTitle } location={ this.props.location }>
        <Seo
           title={ frontmatter.title }
           keywords={ frontmatter.tags }
           description={ frontmatter.metaDescription }
        />
        <Helmet>          <link            rel="canonical"            href={ `${ siteUrl + this.props.location.pathname }` }
          />
        </Helmet>

構造化データ

これに関しても新しいコンポーネントを作成したほうがいいでしょう。

Json.js
import React from "react";
import Helmet from "react-helmet";
import { StaticQuery, graphql } from "gatsby";

const JsonLD = ({ title, description, date, url }) => {
  return (
    <StaticQuery
      query={JsonLdPostQuery}
      render={(data) => {
        const { siteUrl, author, categories } = data.site.siteMetadata;

        const publisher = {
          "@type": "Organization",
          name: author,
          logo: {
            "@type": "ImageObject",
            url: `${siteUrl}/assets/logo.png`, // 適宜変更してください
            height: 150,
            width: 150,
          },
        };
        const authorData = {
          "@type": "Person",
          name: author,
          image: `${siteUrl}/assets/avatar.png`, // 適宜変更してください
        };
        const jsonLd = {
          "@context": "http://schema.org",
          "@type": "BlogPosting",
          mainEntityOfPage: url,
          headline: title,
          image: `${siteUrl}/assets/ogp.png`, // 適宜変更してください
          editor: author,
          url: url,
          datePublished: date,
          dateCreated: date,
          dateModified: date,
          description: description,
          author: authorData,
          publisher,
        };
        return (
          <Helmet>
            <script type="application/ls+json">{JSON.stringify(jsonLd)}</script>
          </Helmet>
        );
      }}
    />
  );
};

export default JsonLD;

const JsonLdPostQuery = graphql`
  query JsonLdPostQuery {
    site {
      siteMetadata {
        siteUrl
        author
      }
    }
  }
`;

コンポーネントが作成できたら、記事テンプレート側から呼び出します。

blogTemplate.js
class BlogPostTemplate extends React.Component {
  render() {
    const { markdownRemark } = this.props.data;
    const { frontmatter, html, parent } = markdownRemark;
    const siteTitle = this.props.data.site.siteMetadata.title;

    return (
      <Layout title={ siteTitle } location={ this.props.location }>
        <Seo
           title={ frontmatter.title }
           keywords={ frontmatter.tags }
           description={ frontmatter.metaDescription }
        />
        <Helmet>
          <link
            rel="canonical"
            href={ `${ siteUrl + this.props.location.pathname }` }
          />
        </Helmet>
        <PostJsonLD          title={ frontmatter.title }          description={ frontmatter.description }          date={ frontmatter.date }          modifiedDate={ frontmatter.date }          url={ this.props.location.href }        />

構造化データをうまく出力できているかどうかはこちらのツールで確認できます。

robot.txt の設置

こちらにつきましてはgatsby-plugin-robots-txtというプラグインにて簡単に行えます。

CommandLine
$ yarn add gatsby-plugin-robots-txt

OR

$ npm i gatsby-plugin-robots-txt

gatsby-config ファイルへの追記も忘れずに。

うまく robot.txt を設置できたかどうかのチェックツールはこちらのページの青いボタンから使用できますし、普通に domain.com/robots.txt にアクセスする手もあります。

参考: Gatsby 製のサイトに robots.txt を配置して検索サイトのクロールを制御する

サイトマップの設置

こちらについてもgatsby-plugin-sitemapというプラグインがあるので利用していきましょう。

CommandLine
$ yarn add gatsby-plugin-sitemap

OR

$ npm i gatsby-plugin-sitemap

gatsby-configに明記。ビルドすれば public 直下に sitemap.xml が生成されます。

参考: Gatsby.js でサイトマップを作成する

Search コンソールでの登録

これは Gatsby に限らずどのサイトでも手順は同じなので参考リンクだけで詳細は割愛。

まとめ

とりあえず WP と同じ状態を再現できて大満足。

  • SNSでシェアしよう
  • Twitterでシェア
  • FaceBookでシェア
  • Lineでシェア
  • 記事タイトルとURLをコピー
トップへ戻るボタン

\ HOME /

トップへ戻る