1. 記事一覧 >
  2. ブログ記事
category logo

Next.jsのブログにRSS/Atomフィード組み込み→Googleサチコに登録

(更新) (公開)

はじめに

Next.js のブログシステム(GitHub リポジトリ:itc-lab/itc-blog)を改良して、RSS/Atom フィードの XML を生成し、置くようにしました。
feed - npm(GitHub リポジトリ:jpmonette/feed)を使って、RSS/Atom フィードの仕様をほとんど知らないまま、作成できました。 今回、実装内容と、Google Search Console、Microsoft Bing Webmaster Tools に登録した結果について書きたいと思います。

タイトルの"Googleサチコ"とは、Google Search Consoleのことです。タイトルが長くなるため、サチコ表記です。

【Google Search Consoleのインデックス登録について】

2021年10月22日更新の記事「サイトマップ登録が「取得できませんでした」になる件の続報」で、サイトマップがやっと認識されて、喜んでいましたが、その時と、2021年 - 2022年年末年始に数件登録されただけで、新規記事が一向にインデックス登録されなくなりました。(2022/02現在)

RSS/Atom フィードのXMLを生成するようにしたのは、送信しておけば、少しでもインデックス登録促進の効果が有ると思ったからです。


XML 例

本ブログの XML を抜粋すると、以下のようになります。


feed.xml

<rss
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title>ITC Engineering Blog</title>
    <link>https://itc-engineering-blog.netlify.app/</link>
    <description>豊田市のシステム開発会社 株式会社アイティーシーの技術情報発信ブログです。過去の知見、新たな知見を公開しています。ブログ自体のソースコードも公開しています。ご意見、ご感想は、お気軽にツイッターアカウントまで!</description>
    <lastBuildDate>Tue, 01 Feb 2022 09:54:32 GMT</lastBuildDate>
    <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
    <generator>Feed for Node.js</generator>
    <image>
      <title>ITC Engineering Blog</title>
      <url>https://itc-engineering-blog.netlify.app/favicon-32x32.png</url>
      <link>https://itc-engineering-blog.netlify.app/</link>
    </image>
    <copyright>All rights reserved 2022, 株式会社アイティーシー</copyright>
    <item>
      <title>
        <![CDATA[ Ubuntu 20.04 LTSのオンプレにKubernetes環境構築からnginx Pod稼働まで ]]>
      </title>
      <link>https://itc-engineering-blog.netlify.app/blogs/ubuntu-kubernetes</link>
      <guid>https://itc-engineering-blog.netlify.app/blogs/ubuntu-kubernetes</guid>
      <pubDate>Mon, 31 Jan 2022 12:55:33 GMT</pubDate>
      <description>
        <![CDATA[ Ubuntu 20.04 LTS のオンプレミス環境に Kubernetes 環境を構築しました。マスターノード、ワーカーノードを作成し、ワーカーノードにNginx Podを追加しました。そこまでの全手順です。 ]]>
      </description>
      <content:encoded>
        <![CDATA[ Ubuntu 20.04 LTS のオンプレミス環境に Kubernetes 環境を構築しました。マスターノード、ワーカーノードを作成し、ワーカーノードにNginx Podを追加しました。そこまでの全手順です。 ]]>
      </content:encoded>
      <author>*****@itccorporation.jp (株式会社アイティーシー)</author>
    </item>
    以降、同じように、
    <item>
      ・・・
    </item>
    を全ブログ記事分
  </channel>
</rss>

atom.xml

<feed
  xmlns="http://www.w3.org/2005/Atom">
  <id>https://itc-engineering-blog.netlify.app/</id>
  <title>ITC Engineering Blog</title>
  <updated>2022-02-01T09:54:32.395Z</updated>
  <generator>Feed for Node.js</generator>
  <author>
    <name>株式会社アイティーシー</name>
    <email>*****@itccorporation.jp</email>
    <uri>https://twitter.com/blog_itc</uri>
  </author>
  <link rel="alternate" href="https://itc-engineering-blog.netlify.app/"/>
  <link rel="self" href="https://itc-engineering-blog.netlify.app/rss/atom.xml"/>
  <subtitle>豊田市のシステム開発会社 株式会社アイティーシーの技術情報発信ブログです。過去の知見、新たな知見を公開しています。ブログ自体のソースコードも公開しています。ご意見、ご感想は、お気軽にツイッターアカウントまで!</subtitle>
  <logo>https://itc-engineering-blog.netlify.app/favicon-32x32.png</logo>
  <icon>https://itc-engineering-blog.netlify.app/favicon-32x32.png</icon>
  <rights>All rights reserved 2022, 株式会社アイティーシー</rights>
  <entry>
    <title type="html">
      <![CDATA[ Ubuntu 20.04 LTSのオンプレにKubernetes環境構築からnginx Pod稼働まで ]]>
    </title>
    <id>https://itc-engineering-blog.netlify.app/blogs/ubuntu-kubernetes</id>
    <link href="https://itc-engineering-blog.netlify.app/blogs/ubuntu-kubernetes"/>
    <updated>2022-01-31T12:55:33.946Z</updated>
    <summary type="html">
      <![CDATA[ Ubuntu 20.04 LTS のオンプレミス環境に Kubernetes 環境を構築しました。マスターノード、ワーカーノードを作成し、ワーカーノードにNginx Podを追加しました。そこまでの全手順です。 ]]>
    </summary>
    <content type="html">
      <![CDATA[ Ubuntu 20.04 LTS のオンプレミス環境に Kubernetes 環境を構築しました。マスターノード、ワーカーノードを作成し、ワーカーノードにNginx Podを追加しました。そこまでの全手順です。 ]]>
    </content>
    <author>
      <name>株式会社アイティーシー</name>
      <email>*****@itccorporation.jp</email>
      <uri>https://twitter.com/blog_itc</uri>
    </author>
    <contributor>
      <name>株式会社アイティーシー</name>
      <email>*****@itccorporation.jp</email>
      <uri>https://twitter.com/blog_itc</uri>
    </contributor>
  </entry>
  以降、同じように、
  <entry>
    ・・・
  </entry>
  を全ブログ記事分
</feed>

実装方法

まず、feednpm installが必要です。

# npm install feed

yarn の場合:

# yarn add feed

データの取れ方は、それぞれだと思いますので、あくまで一例です。
components/RSS.tsx を 新規追加して、
以下のように、実装しました。


component/RSS.tsx

import fs from 'fs';

import { Feed } from 'feed';

import { IBlogService, BlogService } from '@utils/BlogService'; //既存実装 ブログデータ取得用
import { IBlog, MicroCmsResponse } from '@/types/interface'; //既存実装 ブログデータ取得用
import settings from '@settings.yml'; //既存実装 設定取得用
//import { renderToString } from 'react-dom/server';//HTMLの出力は中止。("試みてやめたこと"で後述)
//import { Markdown } from '@components/Markdown';//HTMLの出力は中止。("試みてやめたこと"で後述)
//import React from 'react';//HTMLの出力は中止。("試みてやめたこと"で後述)

export async function generateRssFeed(): Promise<void> {
  //asyncかつ、何も返さないため、型は、Promise<void>
  if (process.env.NODE_ENV === 'development') {
    //npm run devで発動しないようにする。
    return;
  }

  const service: IBlogService = new BlogService(); //既存実装 ブログデータ取得用
  const posts: MicroCmsResponse<IBlog> = await service.getBlogs(9999, 1); //ヘッドレスCMSから全コンテンツ取得
  const baseUrl = process.env.NEXT_PUBLIC_BASE_URL
    ? `${process.env.NEXT_PUBLIC_BASE_URL}/`
    : 'https://itc-engineering-blog.netlify.app/'; //URLの末尾に/が無いと、W3C Feed Validation Serviceで警告になるため、/付き
  const date = new Date();
  const author = {
    name: settings.rss.author.name, //Authorの値決め。YAMLの設定で可変。
    email: settings.rss.author.email,
    link: settings.rss.author.link, //link = ツイッターのURL(例:https://twitter.com/blog_itc)にした。
  };

  const feed = new Feed({
    //feedインスタンス初期化
    title: settings.general.name, //ブログタイトル。「ITC Engineering Blog」が設定してある。
    description: settings.general.description, //ブログ説明文。「ITC Engineering Blog」の紹介文が設定してある。
    id: baseUrl, //一意になれば良いと思うので、トップ画面のURL
    link: baseUrl, //トップ画面のURL
    image: `${baseUrl}favicon-32x32.png`, //適当。(目的が本気でフィードする目的ではないため。)
    favicon: `${baseUrl}favicon-32x32.png`, //適当。ただし、favicon.icoは、NGとの情報有り。
    copyright: `All rights reserved ${date.getFullYear()}, ${
      settings.rss.author.name
    }`, //例に書いてあった通り。
    updated: date, //現在日時。Next.jsの場合、ビルド日時。
    generator: 'Feed for Node.js', //指定しない場合は、'Feed for Node.js'のため、書いても書かなくても同じ。
    feedLinks: {
      rss2: `${baseUrl}rss/feed.xml`,
      json: `${baseUrl}rss/feed.json`,
      atom: `${baseUrl}rss/atom.xml`,
    }, //どの種類のフィードをどこに配置するか。
    author, //上でセットしたauthor。author: author, の意味。
  });

  posts.contents.forEach((post) => {
    //posts.contetntsには全記事情報が入っている。一つずつループ。
    const url = `${baseUrl}blogs/${post.id}`; //記事タイトル。<title>タグや、metaタグのtitleと同じ。
    const update_timestamp =
      (post.reflect_updatedAt && post.updatedAt) ||
      (post.reflect_revisedAt && post.revisedAt) ||
      post.publishedAt; //reflect_*は、更新日時を反映するかのフラグ。反映しないなら、publishedAt(初回公開日時)
    feed.addItem({
      title: post.title, //記事タイトル。<title>タグや、metaタグのtitleと同じ。
      id: url, //一意になれば良いと思うので、記事のURL
      link: url, //記事のURL
      description: post.description, //記事説明。metaタグのdescriptionと同じ。
      content: post.description, //記事説明。metaタグのdescriptionと同じ。("試みてやめたこと"で後述)
      //content: renderToString(<Markdown source={post.content} />),//HTMLの出力は中止。("試みてやめたこと"で後述)
      author: [author], //上でセットしたauthor。
      contributor: [author], //上でセットしたauthor。
      date: new Date(update_timestamp), //上で決めた更新日時か初回公開日時
    });
  });

  fs.mkdirSync('./public/rss', { recursive: true }); //./public/rssディレクトリ作成
  fs.writeFileSync('./public/rss/feed.xml', feed.rss2()); //rss2ファイル出力
  fs.writeFileSync('./public/rss/atom.xml', feed.atom1()); //atom1ファイル出力
  fs.writeFileSync('./public/rss/feed.json', feed.json1()); //json形式ファイル出力(今回特に必要無い。)
}

上記generateRssFeed()を実行すると、
public/rss/feed.xml
public/rss/atom.xml
public/rss/feed.json
が書き込まれるので、
ビルド中に1回実行しないといけません。
index.tsx 等のトップ画面のgetStaticProps()内で実行するのが普通だと思いますが、本ブログの場合、以下のようにトップ画面(pages/index.ts)が一覧画面(list/[[...slug]])の表示と一体化してしまって、一覧画面でgetStaticProps()を実行しなければならなくなりました。


pages/index.ts

import Page, { getStaticProps } from './list/[[...slug]]';

export default Page;

export { getStaticProps };

一覧画面のgetStaticProps()では、"現在何ページ目"、"選択された関連技術"(カテゴリ)のパラメータが渡ってくるのですが、トップ画面は、どちらも渡って来ない仕様です。(URL にパス=slug が無いため。)何も渡ってこなかったら、generateRssFeed()を実行としました。そうしないと、何回もgenerateRssFeed()が実行されることになります。(何回も実行されても良いのですが、書き込まれるフィードファイルは毎回同じ内容で、時間とリソースの無駄です。)

export const getStaticProps: GetStaticProps<Props, Slug> = async ({
  params,
}) => {
  if (!params) await generateRssFeed();

試みてやめたこと

import { renderToString } from 'react-dom/server';
import { Markdown } from '@components/Markdown';
import React from 'react';
・・・
    feed.addItem({
・・・
      content: renderToString(<Markdown source={post.content} />),
・・・

とすると、ブログ本文の markdown → React DOM → HTML の文字列 となり、HTML を渡せるのですが、XML が巨大になりました。
加えて、W3C Feed Validation Service でチェックすると、エラーや警告が表示されて、つぶせそうになく、たとえ潰し切ったとしてもメリットは無さそうですので、content:には、description(記事の短い説明文)を適用することにしました。


React DOM → HTML について
React DOM → HTML 文字列変換は、react-dom/serverrenderToString()にて、実現できます。
ただ、それだけの場合、eslint(リンター、文法チェッカー)が以下のエラーを出力します。

'React' must be in scope when using JSX

import React from 'react';
を追加したら、収まりました。


Validation チェック

最初、記事本文を xml に載せていたため、W3C Feed Validation Service でのチェック結果で、いろいろ出力されました。
備忘録的に列挙しようと思います。

W3C Feed Validation Service XML parsing error

XML parsing error: :9131:28: not well-formed (invalid token)

記事本文の一か所、制御コードが書かれていました。(ターミナルのエラー内容をコピペしたため。)


W3C Feed Validation Service not in canonical form

Identifier "https://itc-engineering-blog.netlify.app" is not in canonical form (the canonical form would be "https://itc-engineering-blog.netlify.app/")

<id>https://itc-engineering-blog.netlify.app</id> と、末尾 / 無しの URL は警告になりました。


W3C Feed Validation Self reference doesn't match document location

Self reference doesn't match document location

デバッグ中の XML を検査したため、 <link rel="self" href="https://itc-engineering-blog.netlify.app/rss/atom.xml"/> の部分で、https://itc-engineering-blog.netlify.app/rss/atom.xmlがまだ存在していなく、警告になっていました。


W3C Feed Validation style attribute contains potentially dangerous content

style attribute contains potentially dangerous content: color:#f8f8f2;background:#272822;text-shadow:0 1px rgba(0, 0, 0, 0.3);font-family:Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;padding:1em;margin:.5em 0;overflow:auto;border-radius:0.3em

<content type="html"> 内で使ってはいけない CSS style を使っているため、警告になっていました。


他にも有りましたが、前述の通り、記事の短い説明文を適用することにしたため、以下のようになりました。

W3C Feed Validation atom.xml

atom.xml に関しては、完璧で、完璧であることの証明にバナーを掲載できるようです。


W3C Feed Validation feed.xml

feed.xml に関しては、一つだけ警告がありました。
Missing atom:link with rel="self"
<link rel="self" href="https://itc-engineering-blog.netlify.app/rss/atom.xml"/> が書かれていないからだと思いますが、合格はしていますし、そこまでこだわることないと見て、ここまでで登録作業に入りました。


Google Search Console

今まで認識されていた site-map.xml を削除して、sitemap.xmlatom.xmlfeed.xml を登録しました。

Google Search Console site-map.xmlを削除

...が、やはりというかなんというか、「取得できませんでした」、「サイトマップを読み込めませんでした」となり、数時間経っても認識されませんでした。認識されたら、ここの記述を更新しようと思います。

Google Search Console 取得できませんでした


Google Search Console サイトマップを読み込めませんでした


Bing Webmaster Tools

Microsoft Bing Webmaster Tools もsite-map.xml を削除して、sitemap.xmlatom.xmlfeed.xml を登録しました。
こちらは、「処理中」になり、数分後見たら、3つとも登録されていました。

Microsoft Bing Webmaster Tools 登録成功


ちなみに、Bing の方は、数日待つものもありましたが、既に全記事インデックス登録されていますので、インデックス登録促進の効果は分からないです。


【Bingでの事件について】

ブログ記事にはしていませんでしたが、一時期、インデックスが減少していき、ゼロになり、新規インデックス登録申請がブロックされるような挙動をしました。その時、心当たりが全く無く、英語で問い合わせてみました。

Microsoft Bing Webmaster Tools 登録成功

(2週間くらい音沙汰無しのため、再度問い合わせ)「いつになったら返事してもらえる?」

(数日後)MS「これ(Bing Webmaster Guidelines)をチェックして欲しい(定型文)」

「全てチェックした。何がダメなのかそちらで分からないのか?」

うんぬんと何回かやり取りしたら、人間らしき文章に代わり、何か解除されたらしく、わずか数日後、インデックス登録申請ができるようになりました。

あくまで推測ですが、MSの中の人でも明確な原因は分からないけれど、何らかの状態をリセットすることはできるようでした。

loading...