-
-
Notifications
You must be signed in to change notification settings - Fork 11.4k
Description
NB: This issue is currently a WIP specification document.
In order to make Ghost more useful as a blogging platform, it should be outputting structured data which allows published content to be more easily machine-readable. This allows content to be easily discoverable in search engines as well as popular social networks where blog posts are typically shared.
There are a couple of contexts for this, the most important of which are listed below - along with a proposed implementation structure.
One additional consideration is that it should be possible for apps to modify and extend this output. Eg. a Disqus app might want to add a commentCount property, and social apps might want to add interactionCount values.
As a part of this issue, Privacy.md should be updated with details of what data is being output and why. There should also be a flag available for config.js which disables automatic structured data from being output at all.
Comments, additions or suggestions on the proposed structure below are welcome.
Tasks
- Update Privacy.md (@JohnONolan)
- Add flag to disable output in config.js
- Implement Schema.org output
- Implement Open Graph output
- Implement Twitter Cards output
- Implement hooks to allow apps to modify output
Schema.org
With the news that Google is ending support for Authorship came the followup of a renewed focus on Schema.org microformats. Until now we've been shy to adopt this despite multiple PR's to add it to Casper, mainly because it makes a horrendous mess of the markup and no clear indication that it was a focus of major search engines.
Now there's a clear focus from a major search engine, and I've also recently discovered the JSON Linked Data (JSON-LD) format which means that not only can we have uncluttered markup - but we can also keep the structured data abstracted from the theme. Meaning Ghost (not the theme) does the implementation. Good news all round.
The following is a proposed sample ouput in {{ghost_head}} on post.hbs following the schema.org Article and BlogPosting specifications.
Should be tested against http://www.google.com/webmasters/tools/richsnippets
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "Article",
"publisher": "{{@blog.title}}",
"author": {
"@type": "Person",
"name": "{{author.name}}",
"image": "{{author.image}}",
"url": "{{author.url}}",
"sameAs": "{{author.website}}"
},
"headline": "{{title}}",
"url": "{{url absolute='true'}}",
"datePublished": "{{date format='YYYY-MM-DDTHH:mm:ssZ'}}",
"dateModified": "{{date updated_at format='YYYY-MM-DDTHH:mm:ssZ'}}",
"image": "{{image}}",
"keywords": "{{tags}}",
"description": "{{meta_description}}"
}
</script>Open Graph
The below represents proposed output for post.hbs in {{ghost_head}} based on the Open Graph protocol specification.
NB: The og:author value has been deliberately omitted due to conflicting information on how it should be correctly used. Based on this it is probably more useful not to specify it explicitly, rather than provide invalid data to one or more services.
Should be tested against https://developers.facebook.com/tools/debug and https://developers.pinterest.com/rich_pins/validator/
<meta property="og:site_name" content="{{@blog.title}}" />
<meta property="og:type" content="article" />
<meta property="og:title" content="{{title}}" />
<meta property="og:description" content="{{meta_description}}..." />
<meta property="og:url" content="{{url absolute='true'}}" />
<meta property="og:image" content="{{image}}" />
<meta property="article:published_time" content="{{date format='YYYY-MM-DDTHH:mm:ssZ'}}" />
<meta property="article:modified_time" content="{{date updated_at format='YYYY-MM-DDTHH:mm:ssZ'}}" />
{{#foreach tags}}
<meta property="article:tag" content="{{name}}" />
{{/foreach}}Twitter Cards
The below represents proposed output for post.hbs in {{ghost_head}} based on the Twitter Cards specification.
Should be tested against https://dev.twitter.com/docs/cards/validation/validator
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{{title}}" />
<meta name="twitter:description" content="{{meta_description}}" />
<meta name="twitter:url" content="{{url absolute='true'}}" />
<meta name="twitter:image:src" content="{{image}}" />NB: all image meta tags should be conditional on whether the post has an image