Gatsbyの検索エンジン最適化(SEO)の全まとめ
2020.11.102021.10.08
この記事は約5分で読めます
目次
• URL正規化
この記事の筆者:三好アキ
🔹 専門用語なしでプログラミングを教えるメソッドに定評があり、1200人以上のビギナーを、最新のフロントエンド開発入門に成功させる。
🔹 Amazonベストセラー1位を複数回獲得している『はじめてつくるReactアプリ with TypeScript』著者。
Amazon著者ページはこちら → amazon.co.jp/stores/author/B099Z51QF2
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のクローラーに伝えます。
そのデータは「リッチリザルト」というものに使われ、検索結果のトップで次のように表示されます。
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
正しいDisabled
Gatsbyに興味のある人は次の記事も参考にしてください。
メルマガ配信中
(from 三好アキ/エンジニア)
React、Next.js、TypeScriptなど最新のウェブ開発のお役立ち情報を、ビギナー向けにかみ砕いて無料配信中。
(*配信はいつでも停止できます)