React Static のサイトマップ( sitemap.xml )の loc をサイトURL始まりで出力させる

React Static でサンプルページをビルドした際に、 sitemap.xml に記述されている loc の値がルートディレクトリからの絶対パスで記述されていたのに気付いたので、対処療法的に対処しました。

現象確認

現象を確認するために React Static をセットアップします。

> npm init -y
> yarn add react-static

ローカルインストール。

> react-static create  

? What should we name this project? rsps_l
? Select a template below... basic
Creating new react-static project...

## 略

[✓] Project "rsps_l" created (192s)

  To get started:

    cd "rsps_l"

    yarn start - Start the development server
    yarn build - Build for production
    yarn serve - Test a production build locally

react-static create でサイトを生成します。

import path from 'path'
import axios from 'axios'

export default {
  getRoutes: async () => {
    const { data: posts } = await axios.get(
      'https://jsonplaceholder.typicode.com/posts'
    )

    return [
      {
        path: '/blog',
        getData: () => ({
          posts,
        }),
        children: posts.map(post => ({
          path: `/post/${post.id}`,
          template: 'src/containers/Post',
          getData: () => ({
            post,
          }),
        })),
      },
    ]
  },
  plugins: [
    [
      require.resolve('react-static-plugin-source-filesystem'),
      {
        location: path.resolve('./src/pages'),
      },
    ],
    require.resolve('react-static-plugin-reach-router'),
    require.resolve('react-static-plugin-sitemap'),
  ],
}

ちなみに、デフォルトの状態で static.config.js は以上のような状態です。

> yarn build

まずはデフォルトの状態でビルドします。

デフォルトの状態でビルドした sitemap.xml
デフォルトの状態でビルドした sitemap.xml

図のように、 locタグ の値がルートディレクトリからの絶対パスになっています。

サイトマップの構造について

ここでサイトマップの構造について確認します。

<loc> required URL of the page. This URL must begin with the protocol (such as http) and end with a trailing slash, if your web server requires it. This value must be less than 2,048 characters.

sitemaps.org – Protocol

「ページのURL。このURLはプロトコル( http のような)から始まるURLでなければならない、また、Webサーバが必要とするならばスラッシュ / 終わりにしなければならない。この値は2,048文字未満にすること。」というところでしょうか。

……ということは、デフォルトの状態ではよろしくなさそうですね。

余談

ちなみに sitemap.org の日本語訳ページでは

<loc> 必須 ページの URL です。 ウェブ サーバーによっては、http などのプロトコルから始め、末尾にスラッシュを含める必要があります。 この値は 2,048 文字以下で指定する必要があります。

sitemaps.org – プロトコル

となっていて、 if your web server requires it の部分が先頭に来てしまっているため、個人的には分かりづらいと感じました……。

対処

まずはどうやってカスタマイズできるか確認。

import path from 'path'
import axios from 'axios'

export default {
  getRoutes: async () => {
    const { data: posts } = await axios.get(
      'https://jsonplaceholder.typicode.com/posts'
    )

    return [
      {
        path: '/blog',
        getData: () => ({
          posts,
        }),
        children: posts.map(post => ({
          path: `/post/${post.id}`,
          template: 'src/containers/Post',
          getData: () => ({
            post,
          }),
        })),
      },
    ]
  },
  plugins: [
    [
      require.resolve('react-static-plugin-source-filesystem'),
      {
        location: path.resolve('./src/pages'),
      },
    ],
    require.resolve('react-static-plugin-reach-router'),
    [
        // react-static-plugin-sitemap にオプションを追加
        require.resolve('react-static-plugin-sitemap'),
        {
            getAttributes: route => {
                // とりあえず route に何が渡ってきているか確認
                console.log(route)
            }
        }
    ]
  ],
}

react-static-plugin-sitemap の部分にコールバックで console.log を仕込んで、 route の中身を確認します。

> yarn build

## 略

{
  path: 'blog/post/98',
  template: '__react_static_root__/src/containers/Post',
  getData: [Function: getData],
  sitemap: { noindex: false },
  data: {
    post: {
      userId: 10,
      id: 98,
      title: 'laboriosam dolor voluptates',
      body: 'doloremque ex facilis sit sint culpa\n' +
        'soluta assumenda eligendi non ut eius\n' +
        'sequi ducimus vel quasi\n' +
        'veritatis est dolores'
    }
  },
  sharedHashesByProp: {},
  sharedData: {}
}
{
  path: 'blog/post/99',
  template: '__react_static_root__/src/containers/Post',
  getData: [Function: getData],
  sitemap: { noindex: false },
  data: {
    post: {
      userId: 10,
      id: 99,
      title: 'temporibus sit alias delectus eligendi possimus magni',
      body: 'quo deleniti praesentium dicta non quod\n' +
        'aut est molestias\n' +
        'molestias et officia quis nihil\n' +
        'itaque dolorem quia'
    }
  },
  sharedHashesByProp: {},
  sharedData: {}
}
{
  path: 'blog/post/100',
  template: '__react_static_root__/src/containers/Post',
  getData: [Function: getData],
  sitemap: { noindex: false },
  data: {
    post: {
      userId: 10,
      id: 100,
      title: 'at nam consequatur ea labore ea harum',
      body: 'cupiditate quo est a modi nesciunt soluta\n' +
        'ipsa voluptas error itaque dicta in\n' +
        'autem qui minus magnam et distinctio eum\n' +
        'accusamus ratione error aut'
    }
  },
  sharedHashesByProp: {},
  sharedData: {}
}

出力結果から、 pathloc に出力されていそうなことが分かりました。

今回は諸事情により時間をあまり割くことができないので、手っ取り早く定数でサイトURLを定義してそれを渡してURLを組み立てることにしました。

import path from 'path'
import axios from 'axios'

// オリジン情報を定義
const originSiteURL = 'https://example.com/'

export default {
  getRoutes: async () => {
    const { data: posts } = await axios.get(
      'https://jsonplaceholder.typicode.com/posts'
    )

    return [
      {
        path: '/blog',
        getData: () => ({
          posts,
        }),
        children: posts.map(post => ({
          path: `/post/${post.id}`,
          template: 'src/containers/Post',
          getData: () => ({
            post,
          }),
        })),
      },
    ]
  },
  plugins: [
    [
      require.resolve('react-static-plugin-source-filesystem'),
      {
        location: path.resolve('./src/pages'),
      },
    ],
    require.resolve('react-static-plugin-reach-router'),
    [
        // react-static-plugin-sitemap にオプションを追加
        require.resolve('react-static-plugin-sitemap'),
        {
            getAttributes: route => ({
                loc: new URL(route.path, originSiteURL).href
            })
        }
    ]
  ],
}

コールバックの戻り値に loc を指定してデフォルトを上書きします。

これで yarn build

カスタマイズした状態でビルドした sitemap.xml
カスタマイズした状態でビルドした sitemap.xml

結果、意図通りプロトコル始まりのサイトURLに書き換えることができました。

参考

react-static-plugin-sitemap

sitemap.xml

参考

この記事で「プロトコル始まりではない絶対パスではまずいのでは?」と気付き。

URL組み立て

ここで path モジュールではなく ECMAScript の new URL() を使うと良い、というレスを見て採用。

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。