Repository navigation
Expand file tree
/
Copy pathstructured-data-with-jekyll.html
More file actions
562 lines (487 loc) · 30.8 KB
/
Copy pathstructured-data-with-jekyll.html
File metadata and controls
562 lines (487 loc) · 30.8 KB
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="manifest" href="/manifest.json" />
<link rel="icon" href="/images/icons/icon-152x152.png" />
<!-- theme-color defines the top bar color-->
<meta name="theme-color" content="#575757" />
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'report-sample' 'self' https://app.posthog.com/static/array.js https://www.googletagmanager.com/gtag/js; style-src 'report-sample' 'self' https://fonts.googleapis.com; object-src 'none'; base-uri 'self'; connect-src 'self' https://app.posthog.com; font-src 'self'; frame-src 'self'; img-src 'self'; manifest-src 'self'; media-src 'self'; report-uri https://pauldambra.report-uri.com/r/d/csp/enforce; worker-src 'self';"
/>
<!-- Add to home screen for Safari on iOS-->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="MiRaNo" />
<link rel="apple-touch-icon" href="/images/icons/icon-152x152.png" />
<!-- Add to home screen for Windows-->
<meta
name="msapplication-TileImage"
content="/images/icons/icon-152x152.png"
/>
<meta name="msapplication-TileColor" content="#575757" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>Adding Structured Data to a Jekyll site</title>
<link rel="canonical" href="https://pauldambra.dev/structured-data-with-jekyll.html" />
<meta property="og:url" content="https://pauldambra.dev/structured-data-with-jekyll.html" />
<meta property="og:type" content="article" />
<meta property="og:title" content="Adding Structured Data to a Jekyll site" />
<meta
property="og:description"
content="how to add structured data to a jekyll site"
/>
<meta
property="og:image"
content="https://pauldambra.dev/images/cardboard.jpg"
/>
<meta name="twitter:creator" content="@pauldambra" />
<meta property="fb:app_id" content="1029758320473951" />
<meta name="viewport" content="width=device-width" />
<meta
name="description"
content="how to add structured data to a jekyll site"
/>
<meta property="fb:pages" content="1029758320473951" />
<link
rel="alternate"
type="application/rss+xml"
title="Mindless Rambling Nonsense"
href="https://pauldambra.dev/feed.xml"
/>
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="prefetch" href="/images/cardboard.jpg" />
<link rel="stylesheet" href="/assets/css/main.css">
<link rel="stylesheet" href="/assets/css/syntax.css">
<meta name="msvalidate.01" content="54691C3C7B863CEE60F0305D6EDFF7A8" />
<meta
name="google-site-verification"
content="hLKEdujpXNQ9PSZWEcQkwxCgL2z1tWxVedeaUmttH7c"
/>
<script>
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group identify setPersonProperties setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags resetGroups onFeatureFlags addFeatureFlagsHandler onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
posthog.init('phc_YwZdW7eDzRlzm3YiNvZpAR5oGhmc9nYeVwuUq5mef3b', {
api_host: 'https://ph.pauldambra.dev',
defaults: '2026-01-30'
})
</script>
</head>
<body class="flex flex-col h-screen">
<header class="flex flex-col px-8 pt-4 text-white text-xl" role="banner">
<div class="flex flex-col sm:flex-row">
<div class="no-underline hover:underline">
<a href="/">Mindless Rambling Nonsense</a>
</div>
<div class="flex-grow my-2 sm:m-0"></div>
<div class="flex justify-start items-end flex-col text-sm">
<div class="flex items-center">
<div class="mr-4">Paul D'Ambra</div>
<a href="https://github.com/pauldambra" rel="noopener">
<img
src="/images/GitHub-Mark-Light-32px.png"
alt="pauldambra on github"
width="32"
height="32"
/>
</a>
</div>
<div class="flex items-center">
<div class="mr-4">Fangler</div>
<a href="https://bsky.app/profile/pauldambra.dev" rel="noopener">
<img
src="/images/bluesky-32.svg"
alt="pauldambra.dev on Bluesky"
width="32"
height="32"
/>
</a>
</div>
<div class="flex items-center">
<div class="mr-4"></div>
<a rel="me" href="https://mastodon.me.uk/@pauldambra">
</a>
</div>
</div>
</div>
<div class="flex-grow"></div>
<div
class="flex align-middle items-start px-2 py-4 text-white space-x-4 text-lg"
>
<nav role="navigation">
<a class="underline" href="/">Blog Posts</a>
<a class="underline ml-5" href="/weeknotes.html">Week Notes</a>
<a class="underline ml-5" href="/kids-games.html">Kids games</a>
</nav>
</div>
</header>
<main role="main" class="bg-white p-4 w-11/12 m-auto flex-auto flex-grow">
<script type="application/ld+json">
{
"@context":"http://schema.org",
"@type":"BlogPosting",
"headline":"Adding Structured Data to a Jekyll site",
"genre":"",
"keywords":"",
"wordCount":"1111",
"url":"https://pauldambra.dev/structured-data-with-jekyll.html",
"datePublished":"2016-09-20",
"author":{
"@type":"Person",
"name":"Paul D'Ambra",
"sameAs":[
"https://twitter.com/pauldambra",
"https://github.com/pauldambra",
"https://plus.google.com/u/0/+PaulDAmbraPlus"
]
},
"publisher":{
"@type": "Organization",
"name": "Paul D'Ambra",
"sameAs": [
"https://twitter.com/pauldambra",
"https://github.com/pauldambra",
"https://plus.google.com/u/0/+PaulDAmbraPlus"
],
"logo": {
"@type": "ImageObject",
"contentUrl": "https://pauldambra.dev/images/logo.png",
"url": "https://pauldambra.dev"
}
},
"image":{
"@type":"ImageObject",
"contentUrl":"https://pauldambra.dev/images/cardboard.jpg",
"url":"https://pauldambra.dev",
"height":"450",
"width":"1000"
},
"mainEntityOfPage":{
"@type":"WebPage",
"@id":"https://pauldambra.dev/structured-data-with-jekyll.html"
},
"articleBody":"\n\t\n\t\tThis post is part of a series on improving this blog #recursion\n\t\n\t\n\t\t\n\t\t\tPrevious Post\n\t\t\n\t\t\n Next Post\n\t\t\n\t\n\n\nStructured Data is a way of adding context to files served on the web so that computers (primarily but not only search engines) can respond to what your content means.\n\nGoogle, for example, will alter and improve how your site appears in search results based on the context you give your data. And if your site is considered authoritative can use the data to build the knowledge cards it sits alongside other search results.\n\nThis blog is only authoritative for being unread but I've not worked with structured data and thought I'd investigate.\n\n\n\nThere are several formats with which you can add structured data which typically use the schema.org vocabulary. Google prefers JSON-LD which is added to the page in a script tag as opposed to, for example, Microdata which decorates the HTML.\n\nHere is an (example taken from schema.org):\n\n<div itemscope itemtype=\"http://schema.org/ScreeningEvent\">\n <h1 itemprop=\"name\">Jaws 3-D\"</h1>\n <div itemprop=\"description\">Jaws 3-D shown in 3D.</div>\n <p>Location: <div itemprop=\"location\" itemscope itemtype=\"http://schema.org/MovieTheater\">\n <span itemprop=\"name\">ACME Cinemas 10</span>\n <span itemprop=\"screenCount\">10</span>\n </div>\n </p>\n <div itemprop=\"workPresented\" itemscope itemtype=\"http://schema.org/Movie\">\n <span itemprop=\"name\">Jaws 3-D</span>\n <link itemprop=\"sameAs\" href=\"www.imdb.com/title/tt0085750/\"/>\n </div>\n <p>Language: <span itemprop=\"inLanguage\" content=\"en\">English</span></p>\n <p>Film format: <span itemprop=\"videoFormat\">3D</span></p>\n</div>\n\n\nThe same markup in JSON-LD:\n\n<script type=\"application/ld+json\">\n{\n \"@context:\": \"http://schema.org\",\n \"@type\": \"ScreeningEvent\",\n \"name\": \"Jaws 3-D\",\n \"description\": \"Jaws 3-D shown in 3D.\"\n \"location\": {\n \"@type\": \"MovieTheater\",\n \"name\": \"ACME Cinemas 10\",\n \"screenCount\": 10\n },\n \"workPresented\": {\n \"@type\": \"Movie\",\n \"name\": \"Jaws 3-D\",\n \"sameAs\": \"www.imdb.com/title/tt0085750/\"\n },\n \"inLanguage\": \"en\",\n \"videoFormat\": \"3D\"\n}\n</script>\n\n\n\nSince the aim is to add this data to a Jekyll site where the HTML is generated from markdown adding Microdata would most likely be a massive faff. But adding a script tag should be trivial.\n\nFirst, the home page.\n\n<script type=\"application/ld+json\">\n{ \"@context\": \"http://schema.org\",\n \"@type\": \"Blog\",\n \"keywords\": \"software engineering agile refactoring c# ruby javascript\",\n \"url\": \"https://pauldambra.github.io\",\n \"mainEntityOfPage\": {\n \"@type\": \"WebPage\",\n \"@id\": \"https://pauldambra.github.io\"\n },\n \"author\": {\n \"@type\": \"Person\",\n \"name\": \"Paul D'Ambra\",\n \"sameAs\": [\n \"https://twitter.com/pauldambra\",\n \"https://github.com/pauldambra\",\n \"https://plus.google.com/u/0/+PaulDAmbraPlus\"\n ]\n }\n }\n</script>\n\n\nThe above can be dropped straight into the index.html that the home page is generated from.\n\nIt says that it use schema.org, is describing a blog, which has a bunch of keywords and is at a particular URL. It advertises that the main thing about this webpage is the page itself. And lists that I'm the author and that I can be identified at a set of other locations (twitter, github, and google+)\n\nPretty straightforward since it is totally static information.\n\nNext, each blog post\n\nThere's more to grok here…\n\n\n{% assign wordcount = include.content | number_of_words %}\n\n<script type=\"application/ld+json\">\n{\n \"@context\":\"http://schema.org\",\n \"@type\":\"BlogPosting\",\n \"headline\":\"{{ include.headline }}\",\n \"genre\":\"{{ include.category }}\",\n \"keywords\":\"{{ include.keywords }}\",\n \"wordCount\":\"{{ wordcount }}\",\n \"url\":\"https://pauldambra.github.io{{ include.link }}\",\n \"datePublished\":\"{{ include.date | | date: '%Y-%m-%d' }}\",\n \"author\":{\n \"@type\":\"Person\",\n \"name\":\"Paul D'Ambra\",\n \"sameAs\":[\n \"https://twitter.com/pauldambra\",\n \"https://github.com/pauldambra\",\n \"https://plus.google.com/u/0/+PaulDAmbraPlus\"\n ]\n },\n \"publisher\":{\n \"@type\":\"Person\",\n \"name\":\"Paul D'Ambra\",\n \"sameAs\":[\n \"https://twitter.com/pauldambra\",\n \"https://github.com/pauldambra\",\n \"https://plus.google.com/u/0/+PaulDAmbraPlus\"\n ],\n \"logo\": {\n \t\t\"@type\": \"ImageObject\",\n \t\t\"contentUrl\": \"https://pauldambra.github.io/images/logo.png\",\n \t\t \"url\": \"https://pauldambra.github.io\"\n }\n },\n \"image\":{\n \"@type\":\"ImageObject\",\n \"contentUrl\":\"https://pauldambra.github.io/images/cardboard.jpg\",\n \"url\":\"https://pauldambra.github.io\",\n \"height\":\"450\",\n \"width\":\"1000\"\n },\n \"mainEntityOfPage\":{\n \"@type\":\"WebPage\",\n \"@id\":\"https://pauldambra.github.io{{ include.link }}\"\n },\n \"articleBody\":\"{{ include.content | strip_html | xml_escape | normalize_whitepace | strip_newlines | strip }}\"\n}\n</script>\n\n\n\nAs with the index page the context and type is set. That determines which properties are relevant. Also best to run the markup through the Google testing tool to make sure that you have included the properties Google requires.\n\nThe above is pasted into an html file in the _includes folder. And added to the layout used to render BlogPosts\n\n\n{%\n include structuredData.html\n headline=page.title\n genre=page.category\n keywords=page.keywords\n content=page.content\n link=page.permalink\n date=page.date\n%}\n\n\n\n\nBlogPosts (i.e. those items in the _posts folder) have a bunch of variables either automatically available or added in the post's YAML frontmatter. These are passed on into the structuredData include and referenced inside it as include.provided_name\n\nThat's not quite everything\n\nThe home page now has some data about the site itself but there's nothing in the markup to indicate that it is made up of a list of the site's published blog posts.\n\nMy solution to this highlights nicely how powerful the liquid templating language can be with a very limited set of operators and filters.\n\n\n<script type=\"application/ld+json\">\n{\n \"@context\": \"http://schema.org\",\n \"@type\": \"ItemList\",\n \"itemListElement\": [\n {{ items }} <!-- see below -->\n ]\n}\n</script>\n\n\n\nThis adds a second script tag to hold the item list of blog postings. It is then necessary to loop over each post in site.posts and add an entry for that item.\n\nBut an array with a trailing comma is not valid JSON+LD and including a template here ends up with a trailing comma.\n\nThe solution is led by two things:\n\n\n Liquid provides the join filter which correctly joins an array without adding a trailing separator\n You can only initialise an array by splitting a string\n\n\nso…\n\n\n {% assign items = \"\" %}\n {% for post in site.posts %}\n {% capture list_item %}\n {%\n include blogListItem.html\n index=forloop.index\n url=post.url\n title=post.title\n date=post.date\n %}\n {% endcapture %}\n {% assign items = items | append: list_item | append: \"|||\" %}\n {% endfor %}\n\n {% assign items = items | split: \"|||\" | join: ',' %}\n\n\n\n\n create an empty string\n loop over site.posts\n for each post capture the result of populating a blogListItem include into a string\n append that to the original string with a known separator\n then split that string\n then join that array with commas\n that can then be output into the script tag above\n\n\nThere may well be a different way of doing that. It seems a bit bonkers but it works…\n\nDo the search engines use it?\n\nI've submitted the site for crawling since adding structured data and Google has so far picked up the home page and four of the articles.\n\n\n\n\n\nSeveral days on and there's no obvious change in how my site places in search rankings or how Google displays it but…\n\n\n their documentation says it can take some time and I've seen other people talking about it taking more than 10 days\n as above this site isn't authoritative for anything so I may not be crossing a threshold of importance for processing or be at the back of a very long queue.\n\n\nAnyway, I think Structured Data, as well as being interesting, is a prerequisite for getting AMP set up and that's next up…\n"
}
</script>
<article>
<header class="flex flex-col border-b-black border-b-2 bg-white text-black">
<div class="heading">
<div class="date">Tue Sep 20 2016</div>
<h1 class="title leading-10 pt-2 mb-0 mt-1">Adding Structured Data to a Jekyll site</h1>
</div>
<div class="meta flex-grow flex flex-row">
<div class="share-this flex self-end space-x-2">
<a
id="facebook-share-link"
class="social-share"
target="_blank"
rel="noopener"
href="https://www.facebook.com/dialog/share?app_id=305449093152216&href=https://pauldambra.dev/structured-data-with-jekyll.html"
>
<img
class="w-8"
src="/images/facebook-black-32.png"
alt="share on facebook"
width="32"
height="32"
/>
</a>
<a
id="twitter-share-link"
class="social-share"
target="_blank"
rel="noopener"
href="https://twitter.com/intent/tweet?text=Adding+Structured+Data+to+a+Jekyll+site&via=pauldambra&url=https://pauldambra.dev/structured-data-with-jekyll.html"
>
<img
class="w-8"
src="/images/twitter-black-32.png"
alt="share on twitter"
width="32"
height="32"
/>
</a>
</div>
<div class="more-like-this text-right content-end flex-grow">
<a
class="post-metadata"
href="/categories.html#structured-data"
>
in: Structured Data
</a>
<div>
<span class="text-slate-500">
<img class="w-6 h-6 inline-block" src="/images/tag.svg" alt="tag-icon" />
<a class="no-underline hover:underline" href="/tags.html#jekyll">
jekyll
</a>
</span>
<span class="text-slate-500">
<img class="w-6 h-6 inline-block" src="/images/tag.svg" alt="tag-icon" />
<a class="no-underline hover:underline" href="/tags.html#recursion">
recursion
</a>
</span>
<span class="text-slate-500">
<img class="w-6 h-6 inline-block" src="/images/tag.svg" alt="tag-icon" />
<a class="no-underline hover:underline" href="/tags.html#seo">
seo
</a>
</span>
<span class="text-slate-500">
<img class="w-6 h-6 inline-block" src="/images/tag.svg" alt="tag-icon" />
<a class="no-underline hover:underline" href="/tags.html#json-ld">
json+ld
</a>
</span>
</div>
</div>
</div>
</header>
<div class="post">
<aside class="mb-2 ml-4 border-l-2 border-l-sky-700 pl-1">
<h1 class="text-base">
This post is part of a series on improving this blog #recursion
</h1>
<div class="flex flex-row">
<div class="flex-grow">
<a href="/using-travis-to-build-jekyll.html">Previous Post</a>
</div>
<div class="flex-grow content-end">
<a href="/2017/testing-static-sites.html">Next Post</a>
</div>
</div>
</aside>
<p>Structured Data is a way of adding context to files served on the web so that computers (primarily but not only search engines) can respond to what your content means.</p>
<p>Google, for example, will alter and improve how your site appears in search results based on the context you give your data. And if your site is considered authoritative can use the data to build the knowledge cards it sits alongside other search results.</p>
<p>This blog is only authoritative for being unread but I've not worked with structured data and thought I'd investigate.</p>
<!--more-->
<p>There are several formats with which you can add structured data which typically use the <a href="http://schema.org/docs/schemas.html">schema.org</a> vocabulary. Google prefers <a href="http://json-ld.org/">JSON-LD</a> which is added to the page in a script tag as opposed to, for example, Microdata which decorates the HTML.</p>
<p>Here is an (<a href="https://schema.org/Movie">example taken from schema.org</a>):</p>
<pre><code class="language-HTML"><div itemscope itemtype="http://schema.org/ScreeningEvent">
<h1 itemprop="name">Jaws 3-D"</h1>
<div itemprop="description">Jaws 3-D shown in 3D.</div>
<p>Location: <div itemprop="location" itemscope itemtype="http://schema.org/MovieTheater">
<span itemprop="name">ACME Cinemas 10</span>
<span itemprop="screenCount">10</span>
</div>
</p>
<div itemprop="workPresented" itemscope itemtype="http://schema.org/Movie">
<span itemprop="name">Jaws 3-D</span>
<link itemprop="sameAs" href="www.imdb.com/title/tt0085750/"/>
</div>
<p>Language: <span itemprop="inLanguage" content="en">English</span></p>
<p>Film format: <span itemprop="videoFormat">3D</span></p>
</div>
</code></pre>
<p>The same markup in JSON-LD:</p>
<pre><code class="language-HTML"><script type="application/ld+json">
{
"@context:": "http://schema.org",
"@type": "ScreeningEvent",
"name": "Jaws 3-D",
"description": "Jaws 3-D shown in 3D."
"location": {
"@type": "MovieTheater",
"name": "ACME Cinemas 10",
"screenCount": 10
},
"workPresented": {
"@type": "Movie",
"name": "Jaws 3-D",
"sameAs": "www.imdb.com/title/tt0085750/"
},
"inLanguage": "en",
"videoFormat": "3D"
}
</script>
</code></pre>
<p>Since the aim is to add this data to a Jekyll site where the HTML is generated from markdown adding Microdata would most likely be a massive faff. But adding a script tag should be trivial.</p>
<h1 id="first-the-home-page">First, the home page.</h1>
<pre><code class="language-HTML"><script type="application/ld+json">
{ "@context": "http://schema.org",
"@type": "Blog",
"keywords": "software engineering agile refactoring c# ruby javascript",
"url": "https://pauldambra.github.io",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://pauldambra.github.io"
},
"author": {
"@type": "Person",
"name": "Paul D'Ambra",
"sameAs": [
"https://twitter.com/pauldambra",
"https://github.com/pauldambra",
"https://plus.google.com/u/0/+PaulDAmbraPlus"
]
}
}
</script>
</code></pre>
<p>The above can be dropped straight into the index.html that the home page is generated from.</p>
<p>It says that it use schema.org, is describing a blog, which has a bunch of keywords and is at a particular URL. It advertises that the main thing about this webpage is the page itself. And lists that I'm the author and that I can be identified at a set of other locations (twitter, github, and google+)</p>
<p>Pretty straightforward since it is totally static information.</p>
<h1 id="next-each-blog-post">Next, each blog post</h1>
<p>There's more to grok here…</p>
<pre><code class="language-HTML">
{% assign wordcount = include.content | number_of_words %}
<script type="application/ld+json">
{
"@context":"http://schema.org",
"@type":"BlogPosting",
"headline":"{{ include.headline }}",
"genre":"{{ include.category }}",
"keywords":"{{ include.keywords }}",
"wordCount":"{{ wordcount }}",
"url":"https://pauldambra.github.io{{ include.link }}",
"datePublished":"{{ include.date | | date: '%Y-%m-%d' }}",
"author":{
"@type":"Person",
"name":"Paul D'Ambra",
"sameAs":[
"https://twitter.com/pauldambra",
"https://github.com/pauldambra",
"https://plus.google.com/u/0/+PaulDAmbraPlus"
]
},
"publisher":{
"@type":"Person",
"name":"Paul D'Ambra",
"sameAs":[
"https://twitter.com/pauldambra",
"https://github.com/pauldambra",
"https://plus.google.com/u/0/+PaulDAmbraPlus"
],
"logo": {
"@type": "ImageObject",
"contentUrl": "https://pauldambra.github.io/images/logo.png",
"url": "https://pauldambra.github.io"
}
},
"image":{
"@type":"ImageObject",
"contentUrl":"https://pauldambra.github.io/images/cardboard.jpg",
"url":"https://pauldambra.github.io",
"height":"450",
"width":"1000"
},
"mainEntityOfPage":{
"@type":"WebPage",
"@id":"https://pauldambra.github.io{{ include.link }}"
},
"articleBody":"{{ include.content | strip_html | xml_escape | normalize_whitepace | strip_newlines | strip }}"
}
</script>
</code></pre>
<p>As with the index page the context and type is set. That determines which properties are relevant. Also best to run the markup through the <a href="https://search.google.com/structured-data/testing-tool">Google testing tool</a> to make sure that you have included the properties Google requires.</p>
<p>The above is pasted into an html file in the _includes folder. And added to the layout used to render BlogPosts</p>
<pre><code class="language-HTML">
{%
include structuredData.html
headline=page.title
genre=page.category
keywords=page.keywords
content=page.content
link=page.permalink
date=page.date
%}
</code></pre>
<p>BlogPosts (i.e. those items in the _posts folder) have a bunch of variables either automatically available or added in the post's YAML frontmatter. These are passed on into the structuredData include and referenced inside it as <code class="language-plaintext highlighter-rouge">include.provided_name</code></p>
<h1 id="thats-not-quite-everything">That's not quite everything</h1>
<p>The home page now has some data about the site itself but there's nothing in the markup to indicate that it is made up of a list of the site's published blog posts.</p>
<p>My solution to this highlights nicely how powerful the <a href="https://shopify.github.io/liquid/">liquid templating language</a> can be with a very limited set of operators and filters.</p>
<pre><code class="language-HTML">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "ItemList",
"itemListElement": [
{{ items }} <!-- see below -->
]
}
</script>
</code></pre>
<p>This adds a second script tag to hold the item list of blog postings. It is then necessary to loop over each post in <code class="language-plaintext highlighter-rouge">site.posts</code> and add an entry for that item.</p>
<p>But an array with a trailing comma is not valid JSON+LD and including a template here ends up with a trailing comma.</p>
<p>The solution is led by two things:</p>
<ul>
<li>Liquid provides the join filter which correctly joins an array without adding a trailing separator</li>
<li>You can only initialise an array by splitting a string</li>
</ul>
<p>so…</p>
<pre><code class="language-HTML">
{% assign items = "" %}
{% for post in site.posts %}
{% capture list_item %}
{%
include blogListItem.html
index=forloop.index
url=post.url
title=post.title
date=post.date
%}
{% endcapture %}
{% assign items = items | append: list_item | append: "|||" %}
{% endfor %}
{% assign items = items | split: "|||" | join: ',' %}
</code></pre>
<ul>
<li>create an empty string</li>
<li>loop over <code class="language-plaintext highlighter-rouge">site.posts</code></li>
<li>for each post capture the result of populating a <code class="language-plaintext highlighter-rouge">blogListItem</code> include into a string</li>
<li>append that to the original string with a known separator</li>
<li>then split that string</li>
<li>then join that array with commas</li>
<li>that can then be output into the script tag above</li>
</ul>
<p>There may well be a different way of doing that. It seems a bit bonkers but it works…</p>
<h1 id="do-the-search-engines-use-it">Do the search engines use it?</h1>
<p>I've submitted the site for crawling since adding structured data and Google has so far picked up the home page and four of the articles.</p>
<p><img src="/images/structured-data-crawled.png" alt="structured data crawled by Google" loading="lazy" /></p>
<!--alex ignore obvious --->
<p>Several days on and there's no obvious change in how my site places in search rankings or how Google displays it but…</p>
<ul>
<li>their documentation says it can take some time and I've seen other people talking about it taking more than 10 days</li>
<li>as above this site isn't authoritative for anything so I may not be crossing a threshold of importance for processing or be at the back of a very long queue.</li>
</ul>
<p>Anyway, I think Structured Data, as well as being interesting, is a prerequisite for getting AMP set up and that's next up…</p>
</div>
<div class="mt-8">
<h1>More like this...</h1>
<div class="grid grid-cols-3 gap-4"></div>
</div>
</article>
</main>
<footer class="w-full h-4 bg-black text-white py-8 px-4">
<a class="no-underline text-white" href="https://pauldambra.dev/feed.xml">
<img class="inline w-6 h-6" src="/images/rss.svg" alt="the rss feed" />
<span>Subscribe to RSS feed</span>
</a>
</footer>
<script>
var tsl = document.getElementById("twitter-share-link");
tsl.addEventListener("click", function () {
ga("send", {
hitType: "social",
socialNetwork: "twitter",
socialAction: "tweet",
socialTarget: "https://pauldambra.dev/structured-data-with-jekyll.html",
});
});
var fsl = document.getElementById("facebook-share-link");
fsl.addEventListener("click", function () {
ga("send", {
hitType: "social",
socialNetwork: "facebook",
socialAction: "share",
socialTarget: "https://pauldambra.dev/structured-data-with-jekyll.html",
});
});
</script>
<script defer src="/register-service-worker.js"></script>
<link
href="https://fonts.googleapis.com/css?family=Khula&display=swap"
rel="stylesheet"
/>
</body>
</html>