Expressのサイトマップ生成用npmモジュール(express-sitemapとsitemap)を比較しよう
以前書いた 「Cloudfront+Lambda@Edgeのサーバレス構成で費用を抑えつつ、動的なWEBコンテンツを作ろう」 の記事で、Node.jsの express アプリケーションを作成しました。
大抵、webコンテンツを作る時にメインフレームワーク以外のその他のプラグインで何を使おうか迷ってしまいます。 今回はexpressアプリケーションのサイトマップ生成用npmモジュールを比較してみました。
モチベーション
サイトマップの作成どうしよう
きちんとGoogleにインデクシングしてほしいので、sitemap.xml
を作成する必要があります。
webページを作成するたびにサイトマップを自前で編集するのは苦行の極みなので、 大方、webフレームワークに対応しているサイトマップジェネレータのライブラリを使うことが多いです。
expressでwebコンテンツを作ったのが 以前の記事 が初めてだったので express に対応しているサイトマップ用のnode_moduleを探す必要がありました。
2つの有力候補
「express sitemap」 で検索したところ、2つのライブラリがnpmのサイトから名乗りを上げてきました。
どちらもDescriptionを読むと、expressに対応していることが分かります。 express-sitemap と sitemap のどちらが自分のコンテンツに適しているか調べる必要が出てきました。
サイトマップを導入だ!!
改めてやりたいことを確認
まず、改めてやりたいことを整理します。構成は 以前の記事 をベースに考えます。
前提として、
- CloudfrontがHTTPSのリクエストを受け付ける
- Lambda@edge上のNode 6.10環境でexpressが動作する
- Lambda@edgeはCloudfrontのOrigin Requestのタイミングで動作し、レスポンスを返す
3.
のため、S3 Originは使わない- S3を使わないので、サイトマップもexpressが動的なページとして返す
S3を使わない理由は 節約 です!!
アーキテクチャの概要は以下のようになります。
express-sitemap を試す
まずは、名前がそれっぽい express-sitemap から試しましょう。 基本的な使い方はnpmのドキュメントを読みましょう。
以下のようなコードサンプルを試します。
なお、URLパス中に言語を指定するタイプの i18n
対応を想定して書いてみます。
1const map = require('express-sitemap')
2const fs = require('fs')
3const i18n = require('i18n')
4const express = require('express')
5const app = express()
6
7app.get('/sitemap.xml', (req, res) => {
8 res.set('Content-Type', 'text/xml');
9 res.send( fs.readFileSync(require('path').resolve(__dirname, './sitemap.xml'), "utf8"));
10});
11
12app.get('/', (req, res) => {
13 res.redirect('/' + i18n.getLocale(req) + '/');
14})
15
16app.get(/^\/(ja|en)\/$/, function(req, res){
17 res.render('index.ejs');
18});
19
20app.get(/^\/(ja|en)\/alerm\/$/, function(req, res){
21 res.render('alerm/index.ejs');
22});
23
24app.get('/aaaa', function(req, res){
25 res.render('alerm/index.ejs');
26});
27
28const sitemap = map({
29 http: 'https',
30 url: 'www.tools.soudegesu.com',
31 generate: app,
32 route: {
33 '/': {
34 lastmod: '2018-04-01',
35 changefreq: 'always',
36 priority: 1.0,
37 alternatepages: [
38 {
39 rel: 'alternate',
40 hreflang: 'ja',
41 href: 'https://www.tools.soudegesu.com/ja/'
42 },
43 {
44 rel: 'alternate',
45 hreflang: 'en',
46 href: 'https://www.tools.soudegesu.com/en/'
47 }]
48 },
49 },
50}).toFile();
51
52module.exports = app
sitemap.xml
にアクセスすると以下のように出力されていることがわかります。
1curl http://localhost:3000/sitemap.xml
2
3<?xml version="1.0" encoding="UTF-8"?>
4<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
5 xmlns:xhtml="http://www.w3.org/1999/xhtml">
6 <url>
7 <loc>https://www.tools.soudegesu.com*</loc>
8 </url>
9 <url>
10 <loc>https://www.tools.soudegesu.com/sitemap.xml</loc>
11 </url>
12 <url>
13 <loc>https://www.tools.soudegesu.com/</loc>
14 <lastmod>2018-04-01</lastmod>
15 <changefreq>always</changefreq>
16 <priority>1</priority>
17 <xhtml:link rel="alternate" hreflang="ja" href="https://www.tools.soudegesu.com/ja/" />
18 <xhtml:link rel="alternate" hreflang="en" href="https://www.tools.soudegesu.com/en/" />
19 </url>
20 <url>
21 <loc>https://www.tools.soudegesu.com/^\/(ja|en)\/$/</loc>
22 </url>
23 <url>
24 <loc>https://www.tools.soudegesu.com/^\/(ja|en)\/alerm\/$/</loc>
25 </url>
26 <url>
27 <loc>http://www.tools.soudegesu.com/aaaa</loc>
28 </url>
29</urlset>
特徴としては 定義していないURL定義はexpress-sitemapがよしなにXMLに出力してくれます 。
一方で、expressがURLパターンに正規表現を使ってルーティングをしている場合には出力が微妙 ですね。
自分の記憶が正しければ、 loc
ブロックは単一のURLを指定する必要があった気がしますので、
うっかりインデクシングエラーになる可能性がありそうです。
具体的に言うと、以下の部分が該当します。
1 <url>
2 <loc>https://www.tools.soudegesu.com/^\/(ja|en)\/$/</loc>
3 </url>
4 <url>
5 <loc>https://www.tools.soudegesu.com/^\/(ja|en)\/alerm\/$/</loc>
6 </url>
7 <url>
8 <loc>http://www.tools.soudegesu.com/aaaa</loc>
9 </url>
sitemap を試す
次に sitemap を試しましょう。 コードサンプルは以下のような感じです。
1const sitemap = require('sitemap')
2const express = require('express')
3const i18n = require('i18n')
4const app = express()
5
6const sitemap = sm.createSitemap({
7 hostname: 'https://www.tools.soudegesu.com',
8 urls: [
9 {
10 url: 'https://www.tools.soudegesu.com/',
11 links: [
12 {
13 lang: 'en',
14 url: 'https://www.tools.soudegesu.com/en/'
15 },
16 {
17 lang: 'ja',
18 url: 'https://www.tools.soudegesu.com/ja/'
19 }
20 ]
21 },
22 {
23 url: 'https://www.tools.soudegesu.com/en/alerm/',
24 links: [
25 {
26 lang: 'en',
27 url: 'https://www.tools.soudegesu.com/en/alerm/'
28 },
29 {
30 lang: 'ja',
31 url: 'https://www.tools.soudegesu.com/ja/alerm/'
32 }
33 ]
34 }
35 ]
36});
37
38
39app.get('/sitemap.xml', (req, res) => {
40 map.tickle();
41 map.XMLtoWeb(res);
42});
43
44app.get('/', (req, res) => {
45 res.redirect('/' + i18n.getLocale(req) + '/');
46})
47
48app.get(/^\/(ja|en)\/$/, function(req, res){
49 res.render('index.ejs');
50});
51
52app.get(/^\/(ja|en)\/alerm\/$/, function(req, res){
53 res.render('alerm/index.ejs');
54});
55
56app.get('/aaaa', function(req, res){
57 res.render('alerm/index.ejs');
58});
59
60module.exports = app
以下のような sitemap.xml
が出力されました。
1curl http://localhost:3000/sitemap.xml
2
3<?xml version="1.0" encoding="UTF-8"?>
4<urlset
5 xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
6 xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
7 xmlns:xhtml="http://www.w3.org/1999/xhtml"
8 xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
9 <url>
10 <loc>https://www.tools.soudegesu.com/</loc>
11 <xhtml:link rel="alternate" hreflang="en" href="https://www.tools.soudegesu.com/en/" />
12 <xhtml:link rel="alternate" hreflang="ja" href="https://www.tools.soudegesu.com/ja/" />
13 </url>
14 <url>
15 <loc>https://www.tools.soudegesu.com/en/alerm/</loc>
16 <xhtml:link rel="alternate" hreflang="en" href="https://www.tools.soudegesu.com/en/alerm/" />
17 <xhtml:link rel="alternate" hreflang="ja" href="https://www.tools.soudegesu.com/ja/alerm/" />
18 </url>
19</urlset>
sitemapの場合は 定義されていないURLはよしなに出力してくれない 感じですね。
まとめ
2つのnpmモジュールを比較しました。使ってみた結論としては、
ライブラリ側でよしなにやってほしい、かつ、パスのルーティングルールがシンプルな場合には express-sitemap を使う
全て自前で定義したい場合には sitemap を使う
が良さそうです。
なお、 i18n(多言語)対応を行う場合は、いずれのモジュールでも、自前でURL定義の記載が必要でした 。
私のコンテンツはインデックスエラーが起きてほしくないため、 sitemap を使うことにしました。