Hexoでパンくずリストの構造化マークアップをする

前回はページにパンくずリストを表示させましたが、今回は構造化マークアップ編です。これを設置することによって、Googleにページの階層構造をより的確に伝えることができるようになります。

使用しているテンプレートエンジンはEJSです。

ソース

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@graph": [
{
"@type": "BreadcrumbList",
"itemListElement":
[
{
"@type": "ListItem",
"position": 1,
"item":
{
"@id": "/",
"name": "ホーム"
}
}
<% if (is_home()) { %>
<% if(page.current !== 1){ %>
,{
"@type": "ListItem",
"position": 2,
"item":
{
"@id": "/page/<%= page.current %>/",
"name": "ページ<%= page.current %>"
}
}
<% } %>
<% } else if (is_category()) { %>
<%
const category_slug = page.path.replace(config.category_dir + '/', '').replace(/\/page\/.+/,'').replace(/\/index.html/, '').split('/');
let category_url = '';
%>
<% category_slug.forEach(function(item, i){ %>
<%
i += 2;
category_url += '/' + item;

const category_name = Object.keys(config.category_map).filter( (key) => {
return config.category_map[key] === item;
});
%>
,{
"@type": "ListItem",
"position": <%= i %>,
"item":
{
"@id": "/<%= config.category_dir %><%= category_url %>/",
"name": "カテゴリー: <% if(category_name.length === 0){ %><%= item %><%}else{%><%= category_name %><%}%>"
}
}
<% }) %>
<% if(page.current !== 1){ %>
,{
"@type": "ListItem",
"position": <%= category_slug.length + 2 %>,
"item":
{
"@id": "/<%= config.category_dir %><%= category_url %>/page/<%= page.current %>/",
"name": "ページ<%= page.current %>"
}
}
<% } %>
<% } else if (is_tag()) { %>
<%
const tag_slug = page.path.replace(config.tag_dir + '/', '').replace(/\/page\/.+/,'').replace(/\/index.html/, '').split('/');
let tag_url = '';
%>
<% tag_slug.forEach(function(item, i){ %>
<%
i += 2;
tag_url += '/' + item;

const tag_name = Object.keys(config.tag_map).filter( (key) => {
return config.tag_map[key] === item;
});
%>
,{
"@type": "ListItem",
"position": <%= i %>,
"item":
{
"@id": "/<%= config.tag_dir %><%= tag_url %>/",
"name": "タグ: <% if(tag_name.length === 0){ %><%= item %><%}else{%><%= tag_name %><%}%>"
}
}
<% }) %>
<% if(page.current !== 1){ %>
,{
"@type": "ListItem",
"position": <%= tag_slug.length + 2 %>,
"item":
{
"@id": "/<%= config.tag_dir %><%= tag_url %>/page/<%= page.current %>/",
"name": "ページ<%= page.current %>"
}
}
<% } %>
<% } else if (is_archive()) { %>
,{
"@type": "ListItem",
"position": 2,
"item":
{
"@id": "/<%= config.archive_dir %>/<%= page.year %>/",
"name": "<%= page.year %>年"
}
}
<% if(page.month){ %>
,{
"@type": "ListItem",
"position": 3,
"item":
{
"@id": "<%- url_for(path) %>",
"name": "<%= page.month %>月"
}
}
<% } %>
<% } else if(is_post()) { %>
<% page.categories.forEach(function(item, i){ %>
<%
i += 2;
%>
,{
"@type": "ListItem",
"position": <%= i %>,
"item":
{
"@id": "<%= url_for(item.path) %>",
"name": "<%= item.name %>"
}
}
<% }) %>
,{
"@type": "ListItem",
"position": <%= page.categories.length + 2 %>,
"item":
{
"@id": "<%- url_for(path) %>",
"name": "<%- page.title %>"
}
}
<% } else if (is_page()) { %>
,{
"@type": "ListItem",
"position": 2,
"item":
{
"@id": "<%- url_for(path) %>",
"name": "<%- page.title %>"
}
}
<% } %>
]
}
]
}
</script>

解説

HTMLの形が変わっているだけで、やっていることはパンくずリストとほぼ同じですので、詳しくは前回の記事をご覧ください。

構造化マークアップではpositionの値を入れる必要があるので少々面倒です。今回はループの回数や配列の長さなどで指定しています。

config.ymlのcategory_map tag_mapによるスラッグに対応しています。JSONを置く場所は<head>間でも<body>閉じタグ直前でもどちらでもOKです。

新版

あまりにもひどい仕様だったので書き直してみました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<%
// パンくずリスト
let breadcrumbs = {};

if(is_post()) {
page.categories.forEach(function(item){
breadcrumbs[item.name] = url_for(item.path);
})
breadcrumbs[page.title] = url_for(path);
} else if (is_page()) {
breadcrumbs[page.title] = url_for(path);
} else if (is_category()) {
cat = get_category();
for(var key in cat) {
breadcrumbs[key] = cat[key];
}
} else if (is_tag()) {
breadcrumbs['#'+page.tag] = url_for(path);
} else if (is_archive()) {
breadcrumbs['アーカイブ'] = '/' + config.archive_dir + '/';
if(page.year){
breadcrumbs[page.year+'年'] = '/' + config.archive_dir + '/' + page.year + '/';
}
if(page.month){
breadcrumbs[page.month+'月'] = url_for(path);
}
}
if(page.current > 1){
breadcrumbs['ページ'+page.current] = '/' + config.pagination_dir + '/' + page.current + '/';
}

function reverse_array(a) {
var key = [];
for (var i in a) {
key.push(i);
}
key.reverse();
var ret = [];
for (var i in key) {
ret[key[i]] = a[key[i]];
}
return ret;
}

function get_category(){
let arr = {};
arr[page.category] = url_for(page.path);
get_parent(page.category);

function get_parent(current){
const current_cat = site.categories.data.filter(x => x.name === current );
const parent_id = current_cat[0].parent;

if(parent_id) {
const parent_name = site.categories.data.filter(x => x._id === parent_id);
arr[parent_name[0].name] = url_for(parent_name[0].path);
get_parent(parent_name[0].name);
}
}
return reverse_array(arr);
}
%>

<script type="application/ld+json">
{
"@context": "http://schema.org",
"@graph": [
{
"@type": "BreadcrumbList",
"itemListElement":
[
{
"@type": "ListItem",
"position": 1,
"item":
{
"@id": "<%= url_for('/') %>",
"name": "ホーム"
}
}
<% let i = 1; %>
<% for(var key in breadcrumbs) { %>
<% i += 1 %>
,{
"@type": "ListItem",
"position": <%= i %>,
"item":
{
"@id": "<%= breadcrumbs[key] %>",
"name": "<%= key %>"
}
}
<% } %>
]
}
]
}
</script>

改善点

以前のものはループが複雑怪奇を極めていたので、まず最初にパンくずリストを配列でつくって、最後にまとめてHTMLに出すようにしました。

旧版ではカテゴリーページの場合に、URLからカテゴリーを取得するというトリッキーなことをやっていたのですが、カテゴリー名から親カテゴリーを取得する関数を書いたので、かなりシンプルになりました。

_config.ymlのカテゴリーのスラッグ、filename_caseの設定にも対応しています。旧版はスラッグの指定が必須でしたが、新版はスラッグを設定していなくても不具合は出ません。

前の記事

Hexoでプラグインを使わずパンくずリストを表示する